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

com.cedarsoftware.util.ConcurrentNavigableMapNullSafe Maven / Gradle / Ivy

The newest version!
package com.cedarsoftware.util;

import java.util.*;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.UUID;

/**
 * ConcurrentNavigableMapNullSafe is a thread-safe implementation of ConcurrentNavigableMap
 * that allows null keys and null values by using a unique String sentinel for null keys.
 * From an ordering perspective, null keys are considered last.  This is honored with the
 * ascending and descending views, where ascending view places them last, and descending view
 * place a null key first.
 *
 * @param  The type of keys maintained by this map
 * @param  The type of mapped values
 *           
 * @author John DeRegnaucourt ([email protected])
 *         
* Copyright (c) Cedar Software LLC *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* License *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class ConcurrentNavigableMapNullSafe extends AbstractConcurrentNullSafeMap implements ConcurrentNavigableMap { private final Comparator originalComparator; private static final String NULL_KEY_SENTINEL = "null_" + UUID.randomUUID(); /** * Constructs a new, empty ConcurrentNavigableMapNullSafe with natural ordering of its keys. * All keys inserted must implement the Comparable interface. */ public ConcurrentNavigableMapNullSafe() { this(null); } /** * Constructs a new, empty ConcurrentNavigableMapNullSafe with the specified comparator. * * @param comparator the comparator that will be used to order this map. If null, the natural * ordering of the keys will be used. */ public ConcurrentNavigableMapNullSafe(Comparator comparator) { this(new ConcurrentSkipListMap<>(wrapComparator(comparator)), comparator); } /** * Private constructor that accepts an internal map and the original comparator. * * @param internalMap the internal map to wrap * @param originalComparator the original comparator provided by the user */ private ConcurrentNavigableMapNullSafe(ConcurrentNavigableMap internalMap, Comparator originalComparator) { super(internalMap); this.originalComparator = originalComparator; } /** * Static method to wrap the user-provided comparator to handle sentinel keys and mixed key types. * * @param comparator the user-provided comparator * @return a comparator that handles sentinel keys and mixed key types */ @SuppressWarnings("unchecked") private static Comparator wrapComparator(Comparator comparator) { return (o1, o2) -> { // Handle the sentinel value for null keys boolean o1IsNullSentinel = NULL_KEY_SENTINEL.equals(o1); boolean o2IsNullSentinel = NULL_KEY_SENTINEL.equals(o2); if (o1IsNullSentinel && o2IsNullSentinel) { return 0; } if (o1IsNullSentinel) { return 1; // Null keys are considered greater than any other keys } if (o2IsNullSentinel) { return -1; } // Handle actual nulls (should not occur) if (o1 == null && o2 == null) { return 0; } if (o1 == null) { return -1; } if (o2 == null) { return 1; } // Use the provided comparator if available if (comparator != null) { return comparator.compare((K) o1, (K) o2); } // If keys are of the same class and Comparable, compare them if (o1.getClass() == o2.getClass() && o1 instanceof Comparable) { return ((Comparable) o1).compareTo(o2); } // Compare class names to provide ordering between different types String className1 = o1.getClass().getName(); String className2 = o2.getClass().getName(); int classComparison = className1.compareTo(className2); if (classComparison != 0) { return classComparison; } // If class names are the same but classes are different (rare), compare identity hash codes return Integer.compare(System.identityHashCode(o1.getClass()), System.identityHashCode(o2.getClass())); }; } @Override protected Object maskNullKey(K key) { if (key == null) { return NULL_KEY_SENTINEL; } return key; } @Override @SuppressWarnings("unchecked") protected K unmaskNullKey(Object maskedKey) { if (NULL_KEY_SENTINEL.equals(maskedKey)) { return null; } return (K) maskedKey; } @Override public Comparator comparator() { return originalComparator; } // Implement navigational methods @Override public ConcurrentNavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { ConcurrentNavigableMap subInternal = ((ConcurrentNavigableMap) internalMap).subMap( maskNullKey(fromKey), fromInclusive, maskNullKey(toKey), toInclusive ); return new ConcurrentNavigableMapNullSafe<>(subInternal, this.originalComparator); } @Override public ConcurrentNavigableMap headMap(K toKey, boolean inclusive) { ConcurrentNavigableMap headInternal = ((ConcurrentNavigableMap) internalMap).headMap( maskNullKey(toKey), inclusive ); return new ConcurrentNavigableMapNullSafe<>(headInternal, this.originalComparator); } @Override public ConcurrentNavigableMap tailMap(K fromKey, boolean inclusive) { ConcurrentNavigableMap tailInternal = ((ConcurrentNavigableMap) internalMap).tailMap( maskNullKey(fromKey), inclusive ); return new ConcurrentNavigableMapNullSafe<>(tailInternal, this.originalComparator); } @Override public ConcurrentNavigableMap subMap(K fromKey, K toKey) { return subMap(fromKey, true, toKey, false); } @Override public ConcurrentNavigableMap headMap(K toKey) { return headMap(toKey, false); } @Override public ConcurrentNavigableMap tailMap(K fromKey) { return tailMap(fromKey, true); } @Override public Entry lowerEntry(K key) { Entry entry = ((ConcurrentSkipListMap) internalMap).lowerEntry(maskNullKey(key)); return wrapEntry(entry); } @Override public K lowerKey(K key) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).lowerKey(maskNullKey(key))); } @Override public Entry floorEntry(K key) { Entry entry = ((ConcurrentSkipListMap) internalMap).floorEntry(maskNullKey(key)); return wrapEntry(entry); } @Override public K floorKey(K key) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).floorKey(maskNullKey(key))); } @Override public Entry ceilingEntry(K key) { Entry entry = ((ConcurrentSkipListMap) internalMap).ceilingEntry(maskNullKey(key)); return wrapEntry(entry); } @Override public K ceilingKey(K key) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).ceilingKey(maskNullKey(key))); } @Override public Entry higherEntry(K key) { Entry entry = ((ConcurrentSkipListMap) internalMap).higherEntry(maskNullKey(key)); return wrapEntry(entry); } @Override public K higherKey(K key) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).higherKey(maskNullKey(key))); } @Override public Entry firstEntry() { Entry entry = ((ConcurrentSkipListMap) internalMap).firstEntry(); return wrapEntry(entry); } @Override public Entry lastEntry() { Entry entry = ((ConcurrentSkipListMap) internalMap).lastEntry(); return wrapEntry(entry); } @Override public Entry pollFirstEntry() { Entry entry = ((ConcurrentSkipListMap) internalMap).pollFirstEntry(); return wrapEntry(entry); } @Override public Entry pollLastEntry() { Entry entry = ((ConcurrentSkipListMap) internalMap).pollLastEntry(); return wrapEntry(entry); } @Override public K firstKey() { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).firstKey()); } @Override public K lastKey() { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).lastKey()); } @Override public NavigableSet navigableKeySet() { return keySet(); } @Override public NavigableSet descendingKeySet() { return descendingMap().navigableKeySet(); } @Override public ConcurrentNavigableMap descendingMap() { ConcurrentNavigableMap descInternal = ((ConcurrentNavigableMap) internalMap).descendingMap(); return new ConcurrentNavigableMapNullSafe<>(descInternal, this.originalComparator); } @Override public NavigableSet keySet() { Set internalKeys = internalMap.keySet(); return new KeyNavigableSet(internalKeys); } /** * Inner class implementing NavigableSet for the keySet(). */ private class KeyNavigableSet extends AbstractSet implements NavigableSet { private final Set internalKeys; KeyNavigableSet(Set internalKeys) { this.internalKeys = internalKeys; } @Override public Iterator iterator() { Iterator it = internalKeys.iterator(); return new Iterator() { @Override public boolean hasNext() { return it.hasNext(); } @Override public K next() { return unmaskNullKey(it.next()); } @Override public void remove() { it.remove(); } }; } @Override public int size() { return internalKeys.size(); } @Override public boolean contains(Object o) { return internalMap.containsKey(maskNullKey((K) o)); } @Override public boolean remove(Object o) { return internalMap.remove(maskNullKey((K) o)) != null; } @Override public void clear() { internalMap.clear(); } @Override public K lower(K k) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).lowerKey(maskNullKey(k))); } @Override public K floor(K k) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).floorKey(maskNullKey(k))); } @Override public K ceiling(K k) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).ceilingKey(maskNullKey(k))); } @Override public K higher(K k) { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).higherKey(maskNullKey(k))); } @Override public K pollFirst() { Entry entry = ((ConcurrentSkipListMap) internalMap).pollFirstEntry(); return (entry == null) ? null : unmaskNullKey(entry.getKey()); } @Override public K pollLast() { Entry entry = ((ConcurrentSkipListMap) internalMap).pollLastEntry(); return (entry == null) ? null : unmaskNullKey(entry.getKey()); } @Override public Comparator comparator() { return ConcurrentNavigableMapNullSafe.this.comparator(); } @Override public K first() { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).firstKey()); } @Override public K last() { return unmaskNullKey(((ConcurrentSkipListMap) internalMap).lastKey()); } @Override public NavigableSet descendingSet() { return ConcurrentNavigableMapNullSafe.this.descendingKeySet(); } @Override public Iterator descendingIterator() { Iterator it = ((ConcurrentSkipListMap) internalMap).descendingKeySet().iterator(); return new Iterator() { @Override public boolean hasNext() { return it.hasNext(); } @Override public K next() { return unmaskNullKey(it.next()); } @Override public void remove() { it.remove(); } }; } @Override public NavigableSet subSet(K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { ConcurrentNavigableMap subMap = ConcurrentNavigableMapNullSafe.this.subMap(fromElement, fromInclusive, toElement, toInclusive); return subMap.navigableKeySet(); } @Override public NavigableSet headSet(K toElement, boolean inclusive) { ConcurrentNavigableMap headMap = ConcurrentNavigableMapNullSafe.this.headMap(toElement, inclusive); return headMap.navigableKeySet(); } @Override public NavigableSet tailSet(K fromElement, boolean inclusive) { ConcurrentNavigableMap tailMap = ConcurrentNavigableMapNullSafe.this.tailMap(fromElement, inclusive); return tailMap.navigableKeySet(); } @Override public SortedSet subSet(K fromElement, K toElement) { return subSet(fromElement, true, toElement, false); } @Override public SortedSet headSet(K toElement) { return headSet(toElement, false); } @Override public SortedSet tailSet(K fromElement) { return tailSet(fromElement, true); } } /** * Wraps an internal entry to expose it as an Entry with unmasked keys and values. * * @param internalEntry the internal map entry * @return the wrapped entry, or null if the internal entry is null */ private Entry wrapEntry(Entry internalEntry) { if (internalEntry == null) return null; return new Entry() { @Override public K getKey() { return unmaskNullKey(internalEntry.getKey()); } @Override public V getValue() { return unmaskNullValue(internalEntry.getValue()); } @Override public V setValue(V value) { Object oldValue = internalEntry.setValue(maskNullValue(value)); return unmaskNullValue(oldValue); } @Override public boolean equals(Object o) { if (!(o instanceof Entry)) return false; Entry e = (Entry) o; return Objects.equals(getKey(), e.getKey()) && Objects.equals(getValue(), e.getValue()); } @Override public int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } @Override public String toString() { return getKey() + "=" + getValue(); } }; } }