All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.internal.util.PartitionedIndexMap Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2023, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */
package com.tangosol.internal.util;

import com.tangosol.net.BackingMapContext;
import com.tangosol.net.cache.SimpleMemoryCalculator;
import com.tangosol.net.partition.PartitionSet;

import com.tangosol.util.AbstractKeyBasedMap;
import com.tangosol.util.Base;
import com.tangosol.util.ChainedSet;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.MapIndex;
import com.tangosol.util.SafeSortedMap;
import com.tangosol.util.SimpleMapIndex;
import com.tangosol.util.ValueExtractor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;

import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * A composite view over partition indices for the specified partition set.
 *
 * @param   the type of cache keys
 * @param   the type of cache values
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class PartitionedIndexMap extends AbstractKeyBasedMap, MapIndex>
    {
    // ---- constructors ----------------------------------------------------

    /**
     * Construct {@code PartitionedIndexMap} instance.
     *
     * @param ctx             the {@link BackingMapContext context} associated with the indexed cache
     * @param mapPartitioned  the map of partition indices, keyed by partition number
     * @param partitions      the set of partitions to return an index map for, or {@code null}
     *                        to return an index for all owned partitions
     */
    public PartitionedIndexMap(BackingMapContext ctx,
                               Map, MapIndex>> mapPartitioned,
                               PartitionSet partitions)
        {
        f_ctx            = ctx;
        f_mapPartitioned = mapPartitioned;
        f_partitions     = partitions;
        }

    // ---- public API ------------------------------------------------------

    /**
     * Return a {@link MapIndex} for the specified extractor.
     *
     * @param extractor  the value extractor to get the {@code MapIndex} for
     *
     * @return a {@link MapIndex} for the specified extractor
     */
    public  MapIndex get(ValueExtractor extractor)
        {
        PartitionSet partitions = f_partitions;

        if (hasIndex(extractor))
            {
            return partitions != null && partitions.cardinality() == 1
                   // optimize for single partition
                   ? (MapIndex) f_mapPartitioned.get(partitions.next(0)).get(extractor)
                   : new PartitionedIndex<>(extractor);
            }

        return null;
        }

    private  boolean hasIndex(ValueExtractor extractor)
        {
        return f_mapPartitioned.values().stream().anyMatch(indexMap -> indexMap.containsKey(extractor));
        }

    /**
     * Return the set of partitions this index view is for.
     *
     * @return the set of partitions this index view is for
     */
    public PartitionSet getPartitions()
        {
        return f_partitions;
        }

    // ---- AbstractKeyBasedMap interface -----------------------------------

    @Override
    public MapIndex get(Object oKey)
        {
        return get((ValueExtractor) oKey);
        }

    @Override
    protected Iterator> iterateKeys()
        {
        return f_mapPartitioned.values().stream()
                .flatMap(map -> map.keySet().stream())
                .distinct()
                .iterator();
        }

    // ---- data members ----------------------------------------------------

    /**
     * Provides unified view over multiple partition-level MapIndex instances
     * for a specific index.
     *
     * @param   the type of indexed attribute
     */
    public class PartitionedIndex
            implements MapIndex
        {
        // ---- constructors ------------------------------------------------

        /**
         * Construct {@link PartitionedIndex} instance.
         *
         * @param extractor  the {@code ValueExtractor} for this index
         */
        PartitionedIndex(ValueExtractor extractor)
            {
            f_extractor = extractor;
            f_mapIndex  = new ConcurrentHashMap<>();

            
            // create a stable "snapshot" of indices for the specified partition set,
            // to avoid returning null from the index map if the partition transfers out
            for (Map.Entry, MapIndex>> entry : f_mapPartitioned.entrySet())
                {
                int nPart = entry.getKey();
                if (f_partitions == null || f_partitions.contains(nPart))
                    {
                    Map, MapIndex> indexMap = entry.getValue();
                    MapIndex                            mapIndex = (MapIndex) indexMap.get(extractor);

                    if (mapIndex != null)
                        {
                        f_mapIndex.put(nPart, mapIndex);
                        }
                    }
                }
            }

        // ---- MapIndex interface ------------------------------------------

        @Override
        public ValueExtractor getValueExtractor()
            {
            return f_extractor;
            }

        @Override
        public boolean isOrdered()
            {
            return f_mapIndex.values().stream().anyMatch(MapIndex::isOrdered);
            }

        @Override
        public boolean isPartial()
            {
            return f_mapIndex.values().stream().anyMatch(MapIndex::isPartial);
            }

        @Override
        public Map> getIndexContents()
            {
            return isOrdered()
                   ? new SortedIndexContents(f_mapIndex.values().stream().map(map -> (SortedMap>) map.getIndexContents()).toList(), getComparator())
                   : new IndexContents(f_mapIndex.values().stream().map(MapIndex::getIndexContents).toList());
            }

        @Override
        public Object get(K key)
            {
            int               nPart    = f_ctx.getManagerContext().getKeyPartition(key);
            MapIndex mapIndex = f_mapIndex.get(nPart);

            return mapIndex == null ? NO_VALUE : mapIndex.get(key);
            }

        @Override
        public Comparator getComparator()
            {
            Optional> mapIndex = f_mapIndex.values().stream().findAny();
            return mapIndex.map(MapIndex::getComparator).orElse(null);
            }

        @Override
        public void insert(Entry entry)
            {
            throw new UnsupportedOperationException("PartitionedIndex is read-only");
            }

        @Override
        public void update(Entry entry)
            {
            throw new UnsupportedOperationException("PartitionedIndex is read-only");
            }

        @Override
        public void delete(Entry entry)
            {
            throw new UnsupportedOperationException("PartitionedIndex is read-only");
            }

        @Override
        public long getUnits()
            {
            return Math.round(new UnitCalculator().calculateIndexOverhead() +
                              f_mapIndex.values().stream().mapToLong(MapIndex::getUnits).sum() * 1.1);  // we seem to be under-reporting by 10% or so
            }

        // ---- Object methods ----------------------------------------------

        @Override
        public String toString()
            {
            return toString(false);
            }

        /**
        * Returns a string representation of this PartitionedIndex.  If called in
        * verbose mode, include the contents of the index (the inverse
        * index). Otherwise, just print the number of entries in the index.
        *
        * @param fVerbose  if true then print the content of the index otherwise
        *                  print the number of entries
        *
        * @return a String representation of this SimpleMapIndex
        */
        public String toString(boolean fVerbose)
            {
            return ClassHelper.getSimpleName(getClass())
                   + ": Extractor=" + getValueExtractor()
                   + ", Ordered=" + isOrdered()
                   + ", Footprint=" + Base.toMemorySizeString(getUnits(), false)
                   + ", Content="
                   + (fVerbose ? getIndexContents().keySet() : getIndexContents().size());
            }

        // ---- inner class: IndexCalculator --------------------------------

        private class UnitCalculator
                extends SimpleMapIndex.IndexCalculator
            {
            /**
             * Construct an IndexCalculator which allows for conversion of items into a
             * serialized format. The calculator may use the size of the serialized value
             * representation to approximate the size of indexed values.
             */
            public UnitCalculator()
                {
                super(f_ctx, null);
                }

            public long calculateIndexOverhead()
                {
                int cPart  = f_mapIndex.size();
                return calculateShallowSize(ConcurrentHashMap.class)
                       + (long) (ENTRY_OVERHEAD + calculateShallowSize(ValueExtractor.class) + MAP_OVERHEAD + ENTRY_OVERHEAD) * cPart;
                }
            }

        // ---- inner class: IndexContents ----------------------------------

        /**
         * Provides composite view over the contents/inverse maps of unordered
         * partitioned indices.
         *
         * @param   the type of inverse index Map
         */
        private class IndexContents>>
                implements Map>
            {
            // ---- constructors --------------------------------------------

            /**
             * Construct an instance of {@code IndexContents}.
             *
             * @param colContents  a collection of partition indices covered by
             *                     this view
             */
            IndexContents(Collection colContents)
                {
                f_colContents = colContents;
                f_mapContents = new HashMap<>();
                }

            // ---- Map interface -------------------------------------------

            @Override
            public int size()
                {
                return (int) f_colContents.stream().flatMap(map -> map.keySet().stream()).distinct().count();
                }

            @Override
            public boolean isEmpty()
                {
                return f_colContents.stream().allMatch(Map::isEmpty);
                }

            @Override
            public boolean containsKey(Object oKey)
                {
                return !f_colContents.isEmpty() && f_colContents.stream().anyMatch(map -> map.containsKey(oKey));
                }

            @Override
            public boolean containsValue(Object oValue)
                {
                return values().stream().anyMatch(set -> set.equals(oValue));
                }

            @Override
            public Set get(Object oKey)
                {
                return f_mapContents.computeIfAbsent((E) oKey, k ->
                    {
                    Set[] sets = f_colContents.stream()
                            .map(map -> map.get(k))
                            .filter(Objects::nonNull)
                            .toArray(Set[]::new);
                    
                    return new ChainedSet(sets);
                    });
                }

            @Override
            public Set put(E key, Set value)
                {
                throw new UnsupportedOperationException("PartitionedIndex is read-only");
                }

            @Override
            public Set remove(Object key)
                {
                throw new UnsupportedOperationException("PartitionedIndex is read-only");
                }

            @Override
            @SuppressWarnings("NullableProblems")
            public void putAll(Map> m)
                {
                throw new UnsupportedOperationException("PartitionedIndex is read-only");
                }

            @Override
            public void clear()
                {
                throw new UnsupportedOperationException("PartitionedIndex is read-only");
                }

            @Override
            public Set keySet()
                {
                Set setKeys = m_setKeys;
                if (setKeys == null)
                    {
                    setKeys = m_setKeys = f_colContents.stream().flatMap(map -> map.keySet().stream()).collect(Collectors.toSet());
                    }
                return setKeys;
                }

            @Override
            @SuppressWarnings("SimplifyStreamApiCallChains")
            public Collection> values()
                {
                return entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toSet());
                }

            @Override
            public Set>> entrySet()
                {
                return keySet().stream()
                        .map(Entry::new)
                        .collect(Collectors.toSet());
                }

            // ---- inner class: Entry --------------------------------------

            /**
             * Virtual inverse index Entry.
             */
            class Entry implements Map.Entry>
                {
                /**
                 * Construct an {@code Entry}.
                 *
                 * @param key  the key for this entry
                 */
                Entry(E key)
                    {
                    f_key = key;
                    }

                @Override
                public E getKey()
                    {
                    return f_key;
                    }

                @Override
                public Set getValue()
                    {
                    return get(f_key);
                    }

                @Override
                public Set setValue(Set value)
                    {
                    throw new UnsupportedOperationException("PartitionedIndex is read-only");
                    }

                // ---- data members ----------------------------------------

                /**
                 * The key for this Entry.
                 */
                private final E f_key;
                }

            // ---- data members --------------------------------------------

            /**
             * The collection of partition-level inverse indices covered by
             * this view.
             */
            protected final Collection f_colContents;

            /**
             * Cached key set.
             */
            protected Set m_setKeys;

            /**
             * Cached index contents.
             */
            private final Map> f_mapContents;
            }

        // ---- inner class: SortedIndexContents ----------------------------

        /**
         * Provides composite view over the contents/inverse maps of ordered
         * partitioned indices.
         */
        @SuppressWarnings("Convert2MethodRef")
        class SortedIndexContents
                extends IndexContents>>
                implements SortedMap>
            {
            // ---- constructors --------------------------------------------

            /**
             * Construct an instance of {@code SortedIndexContents}.
             *
             * @param colContents  the collection of partition indices covered by
             *                     this view
             * @param comparator   the Comparator to use for sorting, or
             *                     {@code null} if natural ordering should be used
             */
            SortedIndexContents(Collection>> colContents, Comparator comparator)
                {
                super(colContents);

                f_comparator = comparator;
                }

            // ---- SortedMap interface -------------------------------------

            @Override
            public Comparator comparator()
                {
                return f_comparator;
                }

            @Override
            public SortedMap> subMap(E fromKey, E toKey)
                {
                return (SortedMap>) f_colContents.stream()
                        .flatMap(map -> map.subMap(fromKey, toKey).entrySet().stream())
                        .collect(Collectors.toMap((Map.Entry> e) -> e.getKey(),
                                                  (Map.Entry> e) -> e.getValue(),
                                                  this::chainSets,
                                                  () -> new SafeSortedMap(f_comparator)));
                }

            @Override
            public SortedMap> headMap(E toKey)
                {
                return (SortedMap>) f_colContents.stream()
                        .flatMap(map -> map.headMap(toKey).entrySet().stream())
                        .collect(Collectors.toMap((Map.Entry> e) -> e.getKey(),
                                                  (Map.Entry> e) -> e.getValue(),
                                                  this::chainSets,
                                                  () -> new SafeSortedMap(f_comparator)));
                }

            @Override
            public SortedMap> tailMap(E fromKey)
                {
                return (SortedMap>) f_colContents.stream()
                        .flatMap(map -> map.tailMap(fromKey).entrySet().stream())
                        .collect(Collectors.toMap((Map.Entry> e) -> e.getKey(),
                                                  (Map.Entry> e) -> e.getValue(),
                                                  this::chainSets,
                                                  () -> new SafeSortedMap(f_comparator)));
                }

            @Override
            public Set keySet()
                {
                Set setKeys = m_setKeys;
                if (setKeys == null)
                    {
                    setKeys = m_setKeys = f_colContents.stream()
                            .flatMap(map -> map.keySet().stream())
                            .collect(Collectors.toCollection(() -> new TreeSet<>(f_comparator)));
                    }
                return setKeys;
                }

            @Override
            public E firstKey()
                {
                return ((SortedSet) keySet()).first();
                }

            @Override
            public E lastKey()
                {
                return ((SortedSet) keySet()).last();
                }

            // ---- helpers -------------------------------------------------

            /**
             * Create {@link ChainedSet} from two {@code Set}s or an existing
             * {@code ChainedSet} and another {@code Set}.
             *
             * @param setFirst   the first Set; could be a ChainedSet
             * @param setSecond  the second Set
             *
             * @return a ChainedSet view over specified Sets
             */
            private Set chainSets(Set setFirst, Set setSecond)
                {
                return setFirst instanceof ChainedSet
                       ? new ChainedSet<>((ChainedSet) setFirst, setSecond)
                       : new ChainedSet<>(setFirst, setSecond);
                }

            // ---- data members --------------------------------------------

            /**
             * The Comparator to use for sorting, or  {@code null} if natural
             * ordering should be used.
             */
            private final Comparator f_comparator;
            }

        // ---- data members ------------------------------------------------

        /**
         * The extractor for this index.
         */
        private final ValueExtractor f_extractor;

        /**
         * The stable view of a subset of partition indices, keyed by partition number.
         */
        private final Map> f_mapIndex;
        }

    // ---- data members ----------------------------------------------------

    /**
     * The {@link BackingMapContext context} associated with the indexed cache.
     */
    protected final BackingMapContext f_ctx;

    /**
     * The map of partition indices, keyed by partition number.
     */
    protected final Map, MapIndex>> f_mapPartitioned;

    /**
     * The set of partitions to return an index map for.
     */
    protected final PartitionSet f_partitions;
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy