com.tangosol.util.SafeSortedMap Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* 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.util;
import com.tangosol.util.comparator.SafeComparator;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Implementation of a {@link java.util.SortedMap} extending {@link java.util.concurrent.ConcurrentSkipListMap}
* to support null keys and null values. Note that unlike its super class, this class is not serializable.
*
* @since 23.03
* @author mg
*/
@SuppressWarnings("unchecked")
public class SafeSortedMap
extends ConcurrentSkipListMap
{
// ----- constructors -------------------------------------------------
/**
* Construct a new SafeSortedMap using the natural ordering of the
* Comparable keys in this map.
*/
public SafeSortedMap()
{
this((Comparator) null);
}
/**
* Construct a new SafeSortedMap copying the contents of the specified map.
*
* @param that the map copied
*/
public SafeSortedMap(SortedMap that)
{
this((Comparator) that.comparator());
this.putAll(that);
}
/**
* Construct a new SafeSortedMap with the specified Comparator.
*
* @param comparator the comparator used to sort this map
*/
public SafeSortedMap(Comparator comparator)
{
super((Comparator & Serializable)(o1, o2) -> {
if (comparator != null && !(comparator instanceof SafeComparator))
{
try
{
return comparator.compare(o1, o2);
}
catch (NullPointerException | ClassCastException ignored)
{
// handle mixed NULL/non-NULL sets below
}
}
if (o1 == NULL)
{
return o2 == NULL ? 0 : -1;
}
if (o2 == NULL)
{
return +1;
}
return ((Comparable) o1).compareTo(o2);
});
}
// ----- Map interface ------------------------------------------------
@Override
public V get(Object oKey)
{
return ensureReturnValue(super.get(oKey == null ? NULL : oKey));
}
@Override
public V put(K oKey, V oValue)
{
return ensureReturnValue(super.put(oKey == null ? (K) NULL : oKey,
oValue == null ? (V) NULL : oValue));
}
@Override
public V remove(Object oKey)
{
return ensureReturnValue(super.remove(oKey == null ? NULL : oKey));
}
@Override
public boolean equals(Object oMap)
{
if (oMap == this)
{
return true;
}
if (!(oMap instanceof Map))
{
return false;
}
for (Map.Entry entry : ((Map) oMap).entrySet())
{
Object oKey = entry.getKey();
Object oValue = entry.getValue();
// support null values
if (!Objects.equals(oValue, get(oKey)))
{
return false;
}
}
return true;
}
@Override
public SafeSortedMap clone()
{
return new SafeSortedMap<>(this);
}
@Override
public boolean containsKey(Object oKey)
{
return super.containsKey(oKey == null ? NULL : oKey);
}
@Override
public boolean containsValue(Object oValue)
{
return super.containsValue(oValue == null ? NULL : oValue);
}
@Override
public Set> entrySet()
{
// too costly to check if any value is NULL as map size grows
return new EntrySet<>(super.entrySet());
}
@Override
public NavigableSet keySet()
{
// optimization when the map does not contain a null key which would
// be the first key in the map, so weak consistency is respected
return super.containsKey(NULL)
? new KeySet(super.keySet())
: super.keySet();
}
@Override
public Collection values()
{
return new Values<>(super.values());
}
@Override
public ConcurrentNavigableMap descendingMap()
{
return new SubMap<>(super.descendingMap());
}
@Override
public NavigableSet descendingKeySet()
{
return new KeySet<>(super.descendingKeySet());
}
/**
* Locate a Map.Entry in this map based on its key.
*
* Note: the behaviour of {#setValue} on the returned Entry is undefined in
* the presence of concurrent modifications
*
* @param oKey the key to return an Entry for
*
* @return an Entry corresponding to the specified key, or null if none exists
*/
public Entry getEntry(K oKey)
{
oKey = oKey == null ? (K) NULL : oKey;
ConcurrentNavigableMap mapSub = super.subMap(oKey, true, oKey, true);
return mapSub.isEmpty() ? null : ensureReturnEntry(mapSub.firstEntry());
}
/* ------ SortedMap API methods ------ */
@Override
public K firstKey()
{
return ensureReturnKey(super.firstKey());
}
@Override
public K lastKey()
{
return ensureReturnKey(super.lastKey());
}
@Override
public ConcurrentNavigableMap subMap(K oFromKey,
boolean fFromInclusive,
K oToKey,
boolean fToInclusive)
{
return new SubMap<>(super.subMap(oFromKey == null ? (K) NULL : oFromKey, fFromInclusive,
oToKey == null ? (K) NULL : oToKey, fToInclusive));
}
@Override
public ConcurrentNavigableMap headMap(K oToKey, boolean fInclusive)
{
return new SubMap<>(super.headMap(oToKey == null ? (K) NULL : oToKey, fInclusive));
}
@Override
public ConcurrentNavigableMap tailMap(K oFromKey, boolean fInclusive)
{
return new SubMap<>(super.tailMap(oFromKey == null ? (K) NULL : oFromKey, fInclusive));
}
@Override
public ConcurrentNavigableMap subMap(K oFromKey, K oToKey)
{
return subMap(oFromKey, true, oToKey, false);
}
@Override
public ConcurrentNavigableMap headMap(K oToKey)
{
return headMap(oToKey, false);
}
@Override
public ConcurrentNavigableMap tailMap(K fromKey)
{
return tailMap(fromKey, true);
}
//----- ConcurrentMap methods ----------------------------------------
@Override
public V putIfAbsent(K oKey, V oValue)
{
return ensureReturnValue(super.putIfAbsent(oKey == null ? (K) NULL : oKey,
oValue == null ? (V) NULL : oValue));
}
@Override
public V getOrDefault(Object oKey, V oDefaultValue)
{
return ensureReturnValue(super.getOrDefault(oKey == null ? NULL : oKey,
oDefaultValue == null ? (V) NULL : oDefaultValue));
}
@Override
public void forEach(BiConsumer super K, ? super V> action)
{
super.forEach(action);
}
@Override
public boolean remove(Object oKey, Object oValue)
{
return super.remove(oKey == null ? NULL : oKey,
oValue == null ? NULL : oValue);
}
@Override
public boolean replace(K oKey, V oOldValue, V oNewValue)
{
return super.replace(oKey == null ? (K) NULL : oKey,
oOldValue == null ? (V) NULL : oOldValue,
oNewValue == null ? (V) NULL : oNewValue);
}
@Override
public V replace(K oKey, V oValue)
{
return ensureReturnValue(super.replace(oKey == null ? (K) NULL : oKey,
oValue == null ? (V) NULL : oValue));
}
@Override
public void replaceAll(BiFunction super K, ? super V, ? extends V> function)
{
throw new UnsupportedOperationException();
}
@Override
public V computeIfAbsent(K oKey, Function super K, ? extends V> mappingFunction)
{
throw new UnsupportedOperationException();
}
@Override
public V computeIfPresent(K oKey, BiFunction super K, ? super V, ? extends V> remappingFunction)
{
throw new UnsupportedOperationException();
}
@Override
public V compute(K oKey, BiFunction super K, ? super V, ? extends V> remappingFunction)
{
throw new UnsupportedOperationException();
}
@Override
public V merge(K oKey, V oValue, BiFunction super V, ? super V, ? extends V> remappingFunction)
{
throw new UnsupportedOperationException();
}
// ----- Java Serialization methods -----------------------------------
private void writeObject(ObjectOutputStream out) throws IOException
{
throw new NotSerializableException("SafeSortedMap is not serializable");
}
private void readObject(ObjectInputStream in) throws IOException
{
throw new NotSerializableException("SafeSortedMap is not serializable");
}
// ----- inner class: NullableEntry -----------------------------------
/**
* Map.Entry implementation that supports null key/value placeholders.
*/
protected static class NullableEntry
implements Entry
{
// ----- constructor ----------------------------------------------
/**
* Constructor for a {@code NullableEntry} wrapping the original entry.
*
* @param entry delegated to entry
*/
NullableEntry(Entry entry)
{
m_entry = entry;
}
// ----- Map.Entry interface--------------------------------------
@Override
public K getKey()
{
return m_entry == null
? null
: ensureReturnKey(m_entry.getKey());
}
@Override
public V getValue()
{
return m_entry == null
? null
: ensureReturnValue(m_entry.getValue());
}
@Override
public V setValue(V oValue)
{
if (m_entry == null)
{
throw new NullPointerException();
}
return m_entry.setValue(oValue == null ? (V) NULL : oValue);
}
// ----- data member ---------------------------------------------
/**
* The delegated to entry.
*/
private final Entry m_entry;
}
// ----- inner class: EntrySet ----------------------------------------
/**
* Entry set delegation of the super map implementation.
*/
protected static class EntrySet
extends AbstractSet>
{
// ----- constructor ----------------------------------------------
/**
* Constructor taking the delegated entry set.
*
* @param s delegated to entry set
*/
EntrySet(Set> s)
{
m_set = s;
}
@Override
public int size()
{
return m_set.size();
}
@Override
public Iterator> iterator()
{
return new EntryIterator<>(m_set.iterator());
}
// ----- data member ---------------------------------------------
/**
* The delegated to set.
*/
private final Set> m_set;
}
// ----- inner class: KeySet ----------------------------------------
/**
* Key set delegation of the super map implementation.
*/
protected static class KeySet
extends AbstractSet
implements NavigableSet
{
// ----- constructor ---------------------------------------------
/**
* Constructor taking the delegated key set.
*
* @param s delegated to key set
*/
KeySet(NavigableSet s)
{
m_set = s;
}
@Override
public int size()
{
return m_set.size();
}
@Override
public K lower(K e)
{
return ensureReturnKey(m_set.lower(e));
}
@Override
public K floor(K e)
{
return ensureReturnKey(m_set.floor(e));
}
@Override
public K ceiling(K e)
{
return ensureReturnKey(m_set.ceiling(e));
}
@Override
public K higher(K e)
{
return ensureReturnKey(m_set.higher(e));
}
@Override
public K pollFirst()
{
return ensureReturnKey(m_set.pollFirst());
}
@Override
public K pollLast()
{
return ensureReturnKey(m_set.pollLast());
}
@Override
public NavigableSet descendingSet()
{
return new KeySet<>(m_set.descendingSet());
}
@Override
public Iterator descendingIterator()
{
return new SortedIterator<>(m_set.descendingIterator());
}
@Override
public NavigableSet subSet(K oFromElement, K oToElement)
{
return subSet(oFromElement, true, oToElement, false);
}
@Override
public NavigableSet subSet(K oFromElement, boolean fFromInclusive, K oToElement, boolean fToInclusive)
{
return new KeySet<>(m_set.subSet(oFromElement == null ? (K) NULL : oFromElement, fFromInclusive,
oToElement == null ? (K) NULL : oToElement, fToInclusive));
}
@Override
public NavigableSet headSet(K oToElement, boolean fInclusive)
{
return new KeySet<>(m_set.headSet(oToElement == null ? (K) NULL : oToElement, fInclusive));
}
@Override
public NavigableSet headSet(K oToElement)
{
return headSet(oToElement, false);
}
@Override
public NavigableSet tailSet(K oFromElement, boolean fInclusive)
{
return new KeySet<>(m_set.tailSet(oFromElement == null ? (K) NULL : oFromElement, fInclusive));
}
@Override
public NavigableSet tailSet(K oFromElement)
{
return tailSet(oFromElement == null ? (K) NULL : oFromElement, true);
}
@Override
public Comparator super K> comparator()
{
return m_set.comparator();
}
@Override
public K first()
{
return ensureReturnKey(m_set.first());
}
@Override
public K last()
{
return ensureReturnKey(m_set.last());
}
@Override
public Iterator iterator()
{
return new SortedIterator<>(m_set.iterator());
}
// ----- data member ---------------------------------------------
/**
* The delegated to set.
*/
final NavigableSet m_set;
}
// ----- inner class: Values -----------------------------------------
/**
* Values delegation of the super map implementation.
*/
protected static class Values extends AbstractCollection
{
Values(Collection colValues)
{
m_colValues = colValues;
}
@Override
public Iterator iterator()
{
return new SortedIterator<>(m_colValues.iterator());
}
@Override
public boolean isEmpty()
{
return m_colValues.isEmpty();
}
@Override
public int size()
{
return m_colValues.size();
}
@Override
public boolean contains(Object o)
{
return m_colValues.contains(o == null ? NULL : o);
}
public void clear()
{
m_colValues.clear();
}
public Object[] toArray()
{
return toList().toArray();
}
private List toList()
{
ArrayList list = new ArrayList<>(m_colValues.size());
for (V v : m_colValues)
{
list.add(ensureReturnValue(v));
}
return list;
}
@SuppressWarnings("unchecked")
public Spliterator spliterator()
{
return (Spliterator)iterator();
}
private final Collection m_colValues;
}
/**
* SubMap delegation to manage {@link #NULL} in entry key and/or value.
*
* @param key type
* @param value type
*/
protected static class SubMap
extends AbstractMap
implements ConcurrentNavigableMap, Cloneable, Serializable
{
/**
* Create a submap wrapper
*/
SubMap(ConcurrentNavigableMap oMap)
{
m_map = oMap;
}
// ----- Clone methods -----------------------------------------------
@Override
public SubMap clone()
{
return new SubMap<>(this);
}
// ----- Map methods ------------------------------------------------
@Override
public boolean containsKey(Object oKey)
{
return m_map.containsKey(oKey == null ? NULL : oKey);
}
@Override
public V get(Object oKey)
{
return ensureReturnValue(m_map.get(oKey == null ? NULL : oKey));
}
@Override
public V put(K oKey, V oValue)
{
return ensureReturnValue(m_map.put(oKey == null ? (K) NULL : oKey,
oValue == null ? (V) NULL : oValue));
}
@Override
public V remove(Object oKey)
{
return ensureReturnValue(m_map.remove(oKey == null ? NULL : oKey));
}
@Override
public int size()
{
return m_map.size();
}
@Override
public boolean isEmpty()
{
return m_map.isEmpty();
}
@Override
public boolean containsValue(Object oValue)
{
return m_map.containsValue(oValue == null ? NULL : oValue);
}
@Override
public void clear()
{
m_map.clear();
}
@Override
public ConcurrentNavigableMap subMap(K oFromKey, boolean fFromInclusive,
K oToKey, boolean fToInclusive)
{
return new SubMap<>(m_map.subMap(oFromKey == null ? (K) NULL : oFromKey, fFromInclusive,
oToKey == null ? (K) NULL : oToKey, fToInclusive));
}
@Override
public ConcurrentNavigableMap headMap(K oToKey, boolean fInclusive)
{
return new SubMap<>(m_map.subMap((K) NULL, false,
oToKey == null ? (K) NULL : oToKey, fInclusive));
}
@Override
public ConcurrentNavigableMap tailMap(K oFromKey, boolean fInclusive)
{
return new SubMap<>(m_map.tailMap(oFromKey == null ? (K) NULL : oFromKey, fInclusive));
}
@Override
public Comparator super K> comparator()
{
return m_map.comparator();
}
@Override
public ConcurrentNavigableMap subMap(K oFromKey, K oToKey)
{
return subMap(oFromKey,true, oToKey, false);
}
@Override
public ConcurrentNavigableMap headMap(K oToKey)
{
return headMap(oToKey, false);
}
@Override
public ConcurrentNavigableMap tailMap(K oFromKey)
{
return tailMap(oFromKey, true);
}
@Override
public K firstKey()
{
return ensureReturnKey(m_map.firstKey());
}
@Override
public K lastKey()
{
return ensureReturnKey(m_map.lastKey());
}
@Override
public Entry lowerEntry(K oKey)
{
return ensureReturnEntry(m_map.lowerEntry(oKey == null ? (K) NULL : oKey));
}
@Override
public K lowerKey(K oKey)
{
return ensureReturnKey(m_map.lowerKey(oKey == null ? (K) NULL : oKey));
}
@Override
public Entry floorEntry(K oKey)
{
return ensureReturnEntry(m_map.floorEntry(oKey == null ? (K) NULL : oKey));
}
@Override
public K floorKey(K oKey)
{
return ensureReturnKey(m_map.floorKey(oKey == null ? (K) NULL : oKey));
}
@Override
public Entry ceilingEntry(K oKey)
{
return ensureReturnEntry(m_map.ceilingEntry(oKey == null ? (K) NULL : oKey));
}
@Override
public K ceilingKey(K oKey)
{
return ensureReturnValue(m_map.ceilingKey(oKey == null ? (K) NULL : oKey));
}
@Override
public Entry higherEntry(K oKey)
{
return ensureReturnEntry(m_map.higherEntry(oKey == null ? (K) NULL : oKey));
}
@Override
public K higherKey(K oKey)
{
return ensureReturnKey(m_map.higherKey(oKey == null ? (K) NULL : oKey));
}
@Override
public Entry firstEntry()
{
return ensureReturnEntry(m_map.firstEntry());
}
@Override
public Entry lastEntry()
{
return ensureReturnEntry(m_map.lastEntry());
}
@Override
public Entry pollFirstEntry()
{
return ensureReturnEntry(m_map.pollFirstEntry());
}
@Override
public Entry pollLastEntry()
{
return ensureReturnEntry(m_map.pollLastEntry());
}
@Override
public ConcurrentNavigableMap descendingMap()
{
return new SubMap<>(m_map.descendingMap());
}
//----- Submap Views --------------------------------------------------
@Override
public NavigableSet keySet()
{
return new KeySet<>(m_map.keySet());
}
@Override
public NavigableSet navigableKeySet()
{
return keySet();
}
@Override
public Collection values()
{
return new Values<>(m_map.values());
}
@Override
public Set> entrySet()
{
return new EntrySet<>(m_map.entrySet());
}
@Override
public NavigableSet descendingKeySet()
{
return descendingMap().navigableKeySet();
}
//----- ConcurrentMap methods ----------------------------------------
@Override
public V putIfAbsent(K oKey, V oValue)
{
return ensureReturnValue(m_map.putIfAbsent(oKey == null ? (K) NULL : oKey,
oValue == null ? (V) NULL : oValue));
}
@Override
public V getOrDefault(Object oKey, V oDefaultValue)
{
return ensureReturnValue(m_map.getOrDefault(oKey == null ? NULL : oKey,
oDefaultValue == null ? (V) NULL : oDefaultValue));
}
@Override
public void forEach(BiConsumer super K, ? super V> action)
{
ConcurrentNavigableMap.super.forEach(action);
}
@Override
public boolean remove(Object oKey, Object oValue)
{
return m_map.remove(oKey == null ? NULL : oKey,
oValue == null ? NULL : oValue);
}
@Override
public boolean replace(K oKey, V oOldValue, V oNewValue)
{
return m_map.replace(oKey == null ? (K) NULL : oKey,
oOldValue == null ? (V) NULL : oOldValue,
oNewValue == null ? (V) NULL : oNewValue);
}
@Override
public V replace(K oKey, V oValue)
{
return ensureReturnValue(m_map.replace(oKey == null ? (K) NULL : oKey,
oValue == null ? (V) NULL : oValue));
}
@Override
public void replaceAll(BiFunction super K, ? super V, ? extends V> function)
{
throw new UnsupportedOperationException();
}
@Override
public V computeIfAbsent(K oKey, Function super K, ? extends V> mappingFunction)
{
throw new UnsupportedOperationException();
}
@Override
public V computeIfPresent(K oKey, BiFunction super K, ? super V, ? extends V> remappingFunction)
{
throw new UnsupportedOperationException();
}
@Override
public V compute(K oKey, BiFunction super K, ? super V, ? extends V> remappingFunction)
{
throw new UnsupportedOperationException();
}
@Override
public V merge(K oKey, V oValue, BiFunction super V, ? super V, ? extends V> remappingFunction)
{
throw new UnsupportedOperationException();
}
// ----- data members ------------------------------------------------
/**
* Underlying map
*/
private final ConcurrentNavigableMap m_map;
}
// ----- inner class: SortedIterator -------------------------------------
/**
* Base iterator that supports null values.
*/
private static class SortedIterator
implements Iterator
{
SortedIterator(Iterator iter)
{
m_iter = iter;
}
@Override
public boolean hasNext()
{
return m_iter.hasNext();
}
@Override
public T next()
{
T oObj = m_iter.next();
return oObj == NULL ? null : oObj;
}
@Override
public void remove()
{
m_iter.remove();
}
protected final Iterator m_iter;
}
// ----- inner class: EntryIterator --------------------------------------
/**
* Entry iterator that supports null values.
*/
private static class EntryIterator
extends SortedIterator>
{
EntryIterator(Iterator> iter)
{
super(iter);
}
@Override
public Entry next()
{
return new NullableEntry<>(m_iter.next());
}
}
// ----- helpers --------------------------------------------------
/**
* Ensure that if entry has a {@link #NULL} key or value, return NullableEntry;
*
* @param e an entry
* @return an entry that can contain a null key or null value
*/
private static Entry ensureReturnEntry(Entry e)
{
return e.getKey() == NULL || e.getValue() == NULL
? new NullableEntry<>(e)
: e;
}
/**
* Ensure if {@code oKey} equals {@link #NULL}, return null;
* otherwise, return {@code oKey}.
*
* @param oKey key
* @return Ensure if {@code oKey} equals {@link #NULL}, return null;
* otherwise, return {@code oKey}.
* @param key type
*/
private static K ensureReturnKey(K oKey)
{
return oKey == NULL ? null : oKey;
}
/**
* Ensure if {@code oValue} equals {@link #NULL}, return null;
* otherwise, return {@code oValue}.
*
* @param oValue value
*
* @return Ensure if {@code oValue} equals {@link #NULL}, return null;
* otherwise, return {@code oValue}.
* @param value type
*/
private static V ensureReturnValue(V oValue)
{
return oValue == NULL ? null : oValue;
}
// ----- constant -------------------------------------------------
/**
* Placeholder for a {@code null} key or value.
*/
public static final Object NULL = new Null();
public static class Null {}
}