com.tangosol.internal.util.PartitionedIndexMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* Copyright (c) 2000, 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.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.SegmentedHashMap;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.SafeComparator;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* 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)
{
int nPart = f_partitions == null
? f_mapPartitioned.keySet().stream().findFirst().orElse(-1)
: f_partitions.rnd();
MapIndex mapIndex = getMapIndex(nPart, extractor);
if (mapIndex != null)
{
return f_partitions != null && f_partitions.cardinality() == 1
? mapIndex // optimize for single partition
: new PartitionedIndex(extractor, mapIndex.isOrdered(), mapIndex.getComparator());
}
return null;
}
/**
* Return the set of partitions this index view is for.
*
* @return the set of partitions this index view is for
*/
public Iterable getPartitions()
{
return f_partitions == null ? f_mapPartitioned.keySet() : f_partitions;
}
// ---- helpers ---------------------------------------------------------
/**
* Return {@link MapIndex} for the specified partition and extractor.
*
* @param nPart the partition to get the index for
* @param extractor the extractor to get the index for
*
* @return a {@link MapIndex} for the specified partition and extractor, or
* {@code null} if this view does nopt contain specified partition
* or an index for the specified extractor
*/
protected MapIndex getMapIndex(int nPart, ValueExtractor extractor)
{
if (nPart >= 0 && (f_partitions == null ? f_mapPartitioned.containsKey(nPart) : f_partitions.contains(nPart)))
{
Map, MapIndex> mapPart = f_mapPartitioned.get(nPart);
if (mapPart != null)
{
return (MapIndex) mapPart.get(extractor);
}
}
return null;
}
// ---- 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();
}
@Override
public String toString()
{
return "PartitionedIndexMap{" +
"f_ctx=" + f_ctx +
", f_mapPartitioned=" + f_mapPartitioned +
", f_partitions=" + f_partitions +
'}';
}
// ---- inner class: PartitionedIndex -----------------------------------
/**
* 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, boolean fOrdered, Comparator comparator)
{
f_extractor = extractor;
f_fOrdered = fOrdered;
f_comparator = fOrdered ? ensureSafeComparator(comparator) : null;
}
/**
* Ensures that the comparator used for index sorting supports {@code null} values.
*
* @param comparator the comparator to wrap, if necessary; can be {@code null}, in
* which case a trivial {@code SafeComparator} that uses natural
* ordering will be returned
*
* @return a {@link SafeComparator} instance
*
* @param the type of objects that may be compared by this comparator
*/
private Comparator ensureSafeComparator(Comparator comparator)
{
return comparator == null
? SafeComparator.INSTANCE()
: comparator instanceof SafeComparator
? comparator
: new SafeComparator<>(comparator);
}
// ---- MapIndex interface ------------------------------------------
@Override
public ValueExtractor getValueExtractor()
{
return f_extractor;
}
@Override
public boolean isOrdered()
{
return f_fOrdered;
}
@Override
public boolean isPartial()
{
for (Map, MapIndex> mapPart : f_mapPartitioned.values())
{
MapIndex mapIndex = mapPart.get(f_extractor);
if (mapIndex != null && mapIndex.isPartial())
{
return true;
}
}
return false;
}
@Override
public Map> getIndexContents()
{
return isOrdered()
? new SortedIndexContents()
: new IndexContents();
}
@Override
public Object get(K key)
{
int nPart = f_ctx.getManagerContext().getKeyPartition(key);
MapIndex mapIndex = getMapIndex(nPart, f_extractor);
return mapIndex == null ? NO_VALUE : mapIndex.get(key);
}
@Override
public Comparator getComparator()
{
return f_comparator;
}
@Override
public void insert(Entry extends K, ? extends V> entry)
{
throw new UnsupportedOperationException("PartitionedIndex is read-only");
}
@Override
public void update(Entry extends K, ? extends V> entry)
{
throw new UnsupportedOperationException("PartitionedIndex is read-only");
}
@Override
public void delete(Entry extends K, ? extends V> entry)
{
throw new UnsupportedOperationException("PartitionedIndex is read-only");
}
@Override
public long getUnits()
{
long cUnits = 0;
for (int nPart : getPartitions())
{
MapIndex mapIndex = getMapIndex(nPart, f_extractor);
cUnits += (mapIndex != null ? mapIndex.getUnits() : 0);
}
return Math.round(cUnits * 1.1);
}
// ---- 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 PartitionedIndex
*/
public String toString(boolean fVerbose)
{
return ClassHelper.getSimpleName(getClass())
+ ": Extractor=" + getValueExtractor()
+ ", Ordered=" + isOrdered()
+ ", Footprint=" + Base.toMemorySizeString(getUnits(), false)
+ (fVerbose ? ", Content[" + getIndexContents().size() + "]=" + getIndexContents().keySet() : "");
}
// ---- inner class: IndexContents ----------------------------------
/**
* Provides composite view over the contents/inverse maps of unordered
* partitioned indices.
*/
private class IndexContents
implements Map>
{
// ---- Map interface -------------------------------------------
@Override
public int size()
{
return keySet().size();
}
@Override
public boolean isEmpty()
{
for (int nPart : getPartitions())
{
if (!getIndexContents(nPart).isEmpty())
{
return false;
}
}
return true;
}
@Override
public boolean containsKey(Object oKey)
{
for (int nPart : getPartitions())
{
if (getIndexContents(nPart).containsKey(oKey))
{
return true;
}
}
return false;
}
@Override
public boolean containsValue(Object oValue)
{
return values().contains(oValue);
}
@Override
public Set get(Object oKey)
{
int cCapacity = f_partitions == null
? f_mapPartitioned.size()
: f_partitions.cardinality();
List> listKeySets = new ArrayList(cCapacity);
for (int nPart : getPartitions())
{
Set setKeys = getIndexContents(nPart).get(oKey);
if (setKeys != null && !setKeys.isEmpty())
{
listKeySets.add(setKeys);
}
}
return new ChainedSet<>(listKeySets);
}
@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 extends E, ? extends Set> 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 = instantiateKeySet();
for (int nPart : getPartitions())
{
setKeys.addAll(getIndexContents(nPart).keySet());
}
return setKeys;
}
@Override
public Collection> values()
{
Set setKeys = keySet();
return new AbstractCollection()
{
public Iterator> iterator()
{
Iterator itKeys = setKeys.iterator();
return new Iterator<>()
{
public boolean hasNext()
{
return itKeys.hasNext();
}
public Set next()
{
return get(itKeys.next());
}
};
}
public int size()
{
return setKeys.size();
}
};
}
@Override
public Set>> entrySet()
{
Set setKeys = keySet();
return new AbstractSet()
{
public Iterator>> iterator()
{
Iterator itKeys = setKeys.iterator();
return new Iterator<>()
{
public boolean hasNext()
{
return itKeys.hasNext();
}
public Map.Entry> next()
{
return new Entry(itKeys.next());
}
};
}
public int size()
{
return setKeys.size();
}
};
}
// ---- helpers -------------------------------------------------
/**
* Returns an empty map (immutable).
*
* @return the empty map
*/
protected Map> emptyMap()
{
return (Map>) SegmentedHashMap.EMPTY;
}
/**
* Instantiate a set to collect the keys into.
*
* @return the set to collect the keys into
*/
protected Set instantiateKeySet()
{
return new HashSet<>();
}
/**
* Return the index contents for the specified partition.
*
* @param nPart the partition to get index contents for
*
* @return the index contents for the specified partition
*/
protected Map> getIndexContents(int nPart)
{
MapIndex mapIndex = getMapIndex(nPart, f_extractor);
return mapIndex != null
? mapIndex.getIndexContents()
: emptyMap();
}
// ---- 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;
}
}
// ---- inner class: SortedIndexContents ----------------------------
/**
* Provides composite view over the contents/inverse maps of ordered
* partitioned indices.
*/
@SuppressWarnings("NullableProblems")
class SortedIndexContents
extends IndexContents
implements SortedMap>
{
// ---- SortedMap interface -------------------------------------
@Override
public Comparator super E> comparator()
{
return f_comparator;
}
@Override
public SortedMap> subMap(E fromKey, E toKey)
{
SafeSortedMap mapSorted = new SafeSortedMap(f_comparator);
for (int nPart : getPartitions())
{
Map> subMap = getIndexContents(nPart).subMap(fromKey, toKey);
subMap.forEach((k, v) -> mapSorted.merge(k, v, (v1, v2) -> chainSets((Set) v1, (Set) v2)));
}
return mapSorted;
}
@Override
public SortedMap> headMap(E toKey)
{
SafeSortedMap mapSorted = new SafeSortedMap(f_comparator);
for (int nPart : getPartitions())
{
Map> headMap = getIndexContents(nPart).headMap(toKey);
headMap.forEach((k, v) -> mapSorted.merge(k, v, (v1, v2) -> chainSets((Set) v1, (Set) v2)));
}
return mapSorted;
}
@Override
public SortedMap> tailMap(E fromKey)
{
SafeSortedMap mapSorted = new SafeSortedMap(f_comparator);
for (int nPart : getPartitions())
{
Map> tailMap = getIndexContents(nPart).tailMap(fromKey);
tailMap.forEach((k, v) -> mapSorted.merge(k, v, (v1, v2) -> chainSets((Set) v1, (Set) v2)));
}
return mapSorted;
}
@Override
public SortedSet keySet()
{
return (SortedSet) super.keySet();
}
@Override
public E firstKey()
{
return keySet().first();
}
@Override
public E lastKey()
{
return keySet().last();
}
// ---- helpers -------------------------------------------------
@Override
protected Map> emptyMap()
{
return (Map>) SafeSortedMap.EMPTY;
}
@Override
protected SortedSet instantiateKeySet()
{
return new TreeSet<>(f_comparator);
}
@Override
protected SortedMap> getIndexContents(int nPart)
{
return (SortedMap>) super.getIndexContents(nPart);
}
/**
* 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 extractor for this index.
*/
private final ValueExtractor f_extractor;
/**
* Flag specifying whether this index is ordered.
*/
private final boolean f_fOrdered;
/**
* The Comparator to use for sorting, or {@code null} if natural
* ordering should be used.
*/
private final Comparator f_comparator;
}
// ---- 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 create an index map for. Could be {@code null},
* in which case all partitions owned by the local member should be included
* into this index map.
*/
protected final PartitionSet f_partitions;
}