PROTON-2575 Add sub map and navigable map APIs to splay map
Provide a means of operating over a fixed range in the tracking
map for cases where dispositions arrive with a range of first
to last delivery ids. This allows a more efficient handling of
these ranged dispositions and produces intermediate objects.
Also fixes an issue where the tracking map could be corrupted if
a remove and update operation falls within a very specific range
of values in the map tree.
diff --git a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionIncomingWindow.java b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionIncomingWindow.java
index ea6f28a..5e43a56 100644
--- a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionIncomingWindow.java
+++ b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionIncomingWindow.java
@@ -16,6 +16,10 @@
*/
package org.apache.qpid.protonj2.engine.impl;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+
import org.apache.qpid.protonj2.buffer.ProtonBuffer;
import org.apache.qpid.protonj2.engine.exceptions.ProtocolViolationException;
import org.apache.qpid.protonj2.engine.util.SequenceNumber;
@@ -157,7 +161,7 @@
final int first = (int) disposition.getFirst();
if (disposition.hasLast() && disposition.getLast() != first) {
- handleRangedDisposition(disposition);
+ handleRangedDisposition(unsettled, disposition);
} else {
final ProtonIncomingDelivery delivery = disposition.getSettled() ?
unsettled.remove(first) : unsettled.get(first);
@@ -170,23 +174,34 @@
return disposition;
}
- private void handleRangedDisposition(Disposition disposition) {
- final int first = (int) disposition.getFirst();
- final int last = (int) disposition.getLast();
+ private static void handleRangedDisposition(NavigableMap<UnsignedInteger, ProtonIncomingDelivery> unsettled, Disposition disposition) {
+ final UnsignedInteger first = UnsignedInteger.valueOf(disposition.getFirst());
+ final UnsignedInteger last = UnsignedInteger.valueOf(disposition.getLast());
+
+ // Dispositions cover a contiguous range in the map requires a single sub-map
+ // which we can iterate whereas a range that wraps requires two iterations over
+ // a split between the higher portion and the lower portion of the map.
+ if (first.compareTo(last) <= 0) {
+ handleDispositions(unsettled.subMap(first, true, last, true), disposition);
+ } else {
+ handleDispositions(unsettled.tailMap(first, true), disposition);
+ handleDispositions(unsettled.headMap(last, true), disposition);
+ }
+ }
+
+ private static void handleDispositions(Map<UnsignedInteger, ProtonIncomingDelivery> deliveries, Disposition disposition) {
final boolean settled = disposition.getSettled();
- int index = first;
- ProtonIncomingDelivery delivery;
+ Iterator<ProtonIncomingDelivery> deliveriesIter = deliveries.values().iterator();
+ while (deliveriesIter.hasNext()) {
+ ProtonIncomingDelivery delivery = deliveriesIter.next();
- // TODO: If SplayMap gets a subMap that works we could get the ranged view which would
- // be more efficient.
- do {
- delivery = settled ? unsettled.remove(index) : unsettled.get(index);
-
- if (delivery != null) {
- delivery.getLink().remoteDisposition(disposition, delivery);
+ if (settled) {
+ deliveriesIter.remove();
}
- } while (index++ != last);
+
+ delivery.getLink().remoteDisposition(disposition, delivery);
+ }
}
long updateIncomingWindow() {
diff --git a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionOutgoingWindow.java b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionOutgoingWindow.java
index 0c1bcaa..8fc5854 100644
--- a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionOutgoingWindow.java
+++ b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/impl/ProtonSessionOutgoingWindow.java
@@ -16,12 +16,16 @@
*/
package org.apache.qpid.protonj2.engine.impl;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
import java.util.Set;
import org.apache.qpid.protonj2.buffer.ProtonBuffer;
import org.apache.qpid.protonj2.engine.OutgoingAMQPEnvelope;
import org.apache.qpid.protonj2.engine.util.SplayMap;
import org.apache.qpid.protonj2.types.DeliveryTag;
+import org.apache.qpid.protonj2.types.UnsignedInteger;
import org.apache.qpid.protonj2.types.transport.Begin;
import org.apache.qpid.protonj2.types.transport.Disposition;
import org.apache.qpid.protonj2.types.transport.Flow;
@@ -218,7 +222,7 @@
final int first = (int) disposition.getFirst();
if (disposition.hasLast() && disposition.getLast() != first) {
- handleRangedDisposition(disposition);
+ handleRangedDisposition(unsettled, disposition);
} else {
final ProtonOutgoingDelivery delivery = disposition.getSettled() ?
unsettled.remove(first) : unsettled.get(first);
@@ -231,23 +235,34 @@
return disposition;
}
- private void handleRangedDisposition(Disposition disposition) {
- final int first = (int) disposition.getFirst();
- final int last = (int) disposition.getLast();
+ private static void handleRangedDisposition(NavigableMap<UnsignedInteger, ProtonOutgoingDelivery> unsettled, Disposition disposition) {
+ final UnsignedInteger first = UnsignedInteger.valueOf(disposition.getFirst());
+ final UnsignedInteger last = UnsignedInteger.valueOf(disposition.getLast());
+
+ // Dispositions cover a contiguous range in the map requires a single sub-map
+ // which we can iterate whereas a range that wraps requires two iterations over
+ // a split between the higher portion and the lower portion of the map.
+ if (first.compareTo(last) <= 0) {
+ handleDispositions(unsettled.subMap(first, true, last, true), disposition);
+ } else {
+ handleDispositions(unsettled.tailMap(first, true), disposition);
+ handleDispositions(unsettled.headMap(last, true), disposition);
+ }
+ }
+
+ private static void handleDispositions(Map<UnsignedInteger, ProtonOutgoingDelivery> deliveries, Disposition disposition) {
final boolean settled = disposition.getSettled();
- int index = first;
- ProtonOutgoingDelivery delivery;
+ Iterator<ProtonOutgoingDelivery> deliveriesIter = deliveries.values().iterator();
+ while (deliveriesIter.hasNext()) {
+ ProtonOutgoingDelivery delivery = deliveriesIter.next();
- // TODO: If SplayMap gets a subMap that works we could get the ranged view which would
- // be more efficient.
- do {
- delivery = settled ? unsettled.remove(index) : unsettled.get(index);
-
- if (delivery != null) {
- delivery.getLink().remoteDisposition(disposition, delivery);
+ if (settled) {
+ deliveriesIter.remove();
}
- } while (index++ != last);
+
+ delivery.getLink().remoteDisposition(disposition, delivery);
+ }
}
//----- Handle sender link actions in the session window context
diff --git a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMap.java b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMap.java
index 0d8e698..69f8dff 100644
--- a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMap.java
+++ b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMap.java
@@ -22,6 +22,7 @@
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
+import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
@@ -52,14 +53,6 @@
}
@Override
- public Set<UnsignedInteger> keySet() {
- if (keySet == null) {
- keySet = new LinkedSplayMapKeySet();
- }
- return keySet;
- }
-
- @Override
public Collection<E> values() {
if (values == null) {
values = new LinkedSplayMapValues();
@@ -76,6 +69,14 @@
}
@Override
+ public NavigableSet<UnsignedInteger> navigableKeySet() {
+ if (keySet == null) {
+ keySet = new LinkedSplayMapKeySet();
+ }
+ return keySet;
+ }
+
+ @Override
public void forEach(BiConsumer<? super UnsignedInteger, ? super E> action) {
Objects.requireNonNull(action);
@@ -285,7 +286,7 @@
}
}
- private final class LinkedSplayMapKeySet extends AbstractSet<UnsignedInteger> {
+ private final class LinkedSplayMapKeySet extends SplayMapKeySet {
@Override
public Iterator<UnsignedInteger> iterator() {
@@ -293,16 +294,6 @@
}
@Override
- public int size() {
- return LinkedSplayMap.this.size;
- }
-
- @Override
- public boolean contains(Object o) {
- return LinkedSplayMap.this.containsKey(o);
- }
-
- @Override
public boolean remove(Object target) {
final SplayedEntry<E> root = LinkedSplayMap.this.entries;
@@ -330,11 +321,6 @@
throw new ConcurrentModificationException();
}
}
-
- @Override
- public void clear() {
- LinkedSplayMap.this.clear();
- }
}
private final class LinkedSplayMapEntrySet extends AbstractSet<Entry<UnsignedInteger, E>> {
diff --git a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/SplayMap.java b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/SplayMap.java
index 6e8d97d..7cffd29 100644
--- a/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/SplayMap.java
+++ b/protonj2/src/main/java/org/apache/qpid/protonj2/engine/util/SplayMap.java
@@ -17,8 +17,10 @@
package org.apache.qpid.protonj2.engine.util;
import java.util.AbstractCollection;
+import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
@@ -29,6 +31,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
+import java.util.SortedSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -49,7 +52,8 @@
*/
public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> {
- private static final UnsignedComparator COMPARATOR = new UnsignedComparator();
+ protected static final Comparator<UnsignedInteger> COMPARATOR = new UnsignedComparator();
+ protected static final Comparator<UnsignedInteger> REVERSE_COMPARATOR = Collections.reverseOrder(COMPARATOR);
protected final RingQueue<SplayedEntry<E>> entryPool = new RingQueue<>(64);
@@ -106,6 +110,31 @@
}
/**
+ * Gets the value of the element stored in the {@link Map} with the key (treated as an
+ * unsigned integer for comparison.
+ *
+ * As a side effect of calling this method the tree that comprises the Map can be modified
+ * to bring up the found key or the last accessed key if the key given is not in the {@link Map}.
+ * For entries at the root of the tree that match the given search key the method returns
+ * immediately without modifying the {@link Map}.
+ *
+ * @param key
+ * the integer key value to search for in the {@link SplayMap}.
+ * @param defaultValue
+ * the default value to return if the key is not stored in this {@link Map}.
+ *
+ * @return the value stored for the given key if found the default value if not in the {@link Map}.
+ */
+ public E getOrDefault(int key, E defaultValue) {
+ E result = get(key);
+ if (result == null && root != null && root.key == key) {
+ return null;
+ }
+
+ return defaultValue;
+ }
+
+ /**
* Puts the value into the in the {@link Map} at the entry specified by the given key (treated as an
* unsigned integer for comparison.
*
@@ -151,11 +180,6 @@
return oldValue;
}
- @Override
- public E putIfAbsent(UnsignedInteger key, E value) {
- return putIfAbsent(key.intValue(), value);
- }
-
/**
* If the specified key is not already associated with a value associates it with the given value and
* returns null, otherwise returns the current value.
@@ -293,11 +317,21 @@
}
@Override
+ public E putIfAbsent(UnsignedInteger key, E value) {
+ return putIfAbsent(key.intValue(), value);
+ }
+
+ @Override
public E get(Object key) {
return get(Number.class.cast(key).intValue());
}
@Override
+ public E getOrDefault(Object key, E defaultValue) {
+ return getOrDefault(Number.class.cast(key).intValue(), defaultValue);
+ }
+
+ @Override
public E remove(Object key) {
return remove(Number.class.cast(key).intValue());
}
@@ -332,21 +366,63 @@
return false;
}
- // Once requested we will create an store a single instance to a collection
- // with no state for each of the key, values and entries types. Since the
- // types are stateless the trivial race on create is not important to the
- // eventual outcome of having a cached instance.
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (SplayedEntry<E> entry = firstEntry(root); entry != null; entry = successor(entry)) {
+ hash += entry.hashCode();
+ }
+ return hash;
+ }
- protected Set<UnsignedInteger> keySet;
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Map)) {
+ return false;
+ }
+
+ Map<?,?> m = (Map<?,?>) o;
+ if (m.size() != size()) {
+ return false;
+ }
+
+ try {
+ for (SplayedEntry<E> entry = firstEntry(root); entry != null; entry = successor(entry)) {
+ UnsignedInteger key = entry.getKey();
+ E value = entry.getValue();
+ if (value == null) {
+ if (!(m.get(key) == null && m.containsKey(key))) {
+ return false;
+ }
+ } else {
+ if (!value.equals(m.get(key))) {
+ return false;
+ }
+ }
+ }
+ } catch (ClassCastException | NullPointerException ignored) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // Once requested we will create an store a single instance to a collection
+ // with no state for each of the key, values ,entries types and the descending
+ // Map view. Since the types are stateless the trivial race on create is not
+ // important to the eventual outcome of having a cached instance.
+
+ protected NavigableSet<UnsignedInteger> keySet;
protected Collection<E> values;
protected Set<Entry<UnsignedInteger, E>> entrySet;
+ protected NavigableMap<UnsignedInteger, E> descendingMapView;
@Override
public Set<UnsignedInteger> keySet() {
- if (keySet == null) {
- keySet = new SplayMapKeySet();
- }
- return keySet;
+ return navigableKeySet();
}
@Override
@@ -511,7 +587,7 @@
*
*/
- private SplayedEntry<E> rightRotate(SplayedEntry<E> node) {
+ private static <E> SplayedEntry<E> rightRotate(SplayedEntry<E> node) {
SplayedEntry<E> rotated = node.left;
node.left = rotated.right;
rotated.right = node;
@@ -526,7 +602,7 @@
return rotated;
}
- private SplayedEntry<E> leftRotate(SplayedEntry<E> node) {
+ private static <E> SplayedEntry<E> leftRotate(SplayedEntry<E> node) {
SplayedEntry<E> rotated = node.right;
node.right = rotated.left;
rotated.left = node;
@@ -547,7 +623,7 @@
* as it is likely it will be the next accessed or one of the neighboring nodes which
* reduces the search time for that cluster.
*/
- private SplayedEntry<E> splay(SplayedEntry<E> root, int key) {
+ private static <E> SplayedEntry<E> splay(SplayedEntry<E> root, int key) {
if (root == null || root.key == key) {
return root;
}
@@ -653,6 +729,9 @@
if (node.left != null) {
replacement = splay(node.left, node.key);
replacement.right = node.right;
+ if (replacement.right != null) {
+ replacement.right.parent = replacement;
+ }
}
if (replacement != null) {
@@ -679,7 +758,23 @@
modCount++;
}
- private SplayedEntry<E> firstEntry(SplayedEntry<E> node) {
+ private SplayedEntry<E> findEntry(int key) {
+ if (root == null) {
+ return null;
+ } else if (root.key == key) {
+ return root;
+ } else {
+ root = splay(root, key);
+
+ if (root.key == key) {
+ return root;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private static <E> SplayedEntry<E> firstEntry(SplayedEntry<E> node) {
SplayedEntry<E> firstEntry = node;
if (firstEntry != null) {
while (firstEntry.left != null) {
@@ -690,7 +785,7 @@
return firstEntry;
}
- private SplayedEntry<E> lastEntry(SplayedEntry<E> node) {
+ private static <E> SplayedEntry<E> lastEntry(SplayedEntry<E> node) {
SplayedEntry<E> lastEntry = node;
if (lastEntry != null) {
while (lastEntry.right != null) {
@@ -701,7 +796,7 @@
return lastEntry;
}
- private SplayedEntry<E> successor(SplayedEntry<E> node) {
+ private static <E> SplayedEntry<E> successor(SplayedEntry<E> node) {
if (node == null) {
return null;
} else if (node.right != null) {
@@ -724,7 +819,7 @@
}
}
- private SplayedEntry<E> predecessor(SplayedEntry<E> node) {
+ private static <E> SplayedEntry<E> predecessor(SplayedEntry<E> node) {
if (node == null) {
return null;
} else if (node.left != null) {
@@ -747,6 +842,17 @@
}
}
+ /**
+ * Unsigned comparison of two integer values
+ *
+ * @param lhs
+ * the left hand side value for comparison.
+ * @param rhs
+ * the right hand side value for comparison.
+ *
+ * @return a negative integer, zero, or a positive integer as the first argument is less than,
+ * equal to, or greater than the second.
+ */
private static int compare(int lhs, int rhs) {
return Integer.compareUnsigned(lhs, rhs);
}
@@ -760,10 +866,10 @@
// Base class iterator that can be used for the collections returned from the Map
private abstract class SplayMapIterator<T> implements Iterator<T> {
- private SplayedEntry<E> nextNode;
- private SplayedEntry<E> lastReturned;
+ protected SplayedEntry<E> nextNode;
+ protected SplayedEntry<E> lastReturned;
- private int expectedModCount;
+ protected int expectedModCount;
public SplayMapIterator(SplayedEntry<E> startAt) {
this.nextNode = startAt;
@@ -791,8 +897,6 @@
return lastReturned;
}
- // Unused as of now but can be used for NavigableMap amongst other things
- @SuppressWarnings("unused")
protected SplayedEntry<E> previousNode() {
final SplayedEntry<E> entry = nextNode;
@@ -849,6 +953,17 @@
}
}
+ final class ReverseSplayMapKeyIterator extends SplayMapIterator<UnsignedInteger> {
+ public ReverseSplayMapKeyIterator(SplayedEntry<E> startAt) {
+ super(startAt);
+ }
+
+ @Override
+ public UnsignedInteger next() {
+ return previousNode().getKey();
+ }
+ }
+
private class SplayMapValueIterator extends SplayMapIterator<E> {
public SplayMapValueIterator(SplayedEntry<E> startAt) {
@@ -897,7 +1012,7 @@
}
}
- private final class SplayMapKeySet extends AbstractSet<UnsignedInteger> {
+ protected class SplayMapKeySet extends AbstractSet<UnsignedInteger> implements NavigableSet<UnsignedInteger> {
@Override
public Iterator<UnsignedInteger> iterator() {
@@ -910,6 +1025,11 @@
}
@Override
+ public boolean isEmpty() {
+ return SplayMap.this.size == 0;
+ }
+
+ @Override
public boolean contains(Object o) {
return SplayMap.this.containsKey(o);
}
@@ -926,9 +1046,132 @@
}
@Override
+ public boolean retainAll(Collection<?> c) {
+ Objects.requireNonNull(c);
+ boolean modified = false;
+
+ for (SplayedEntry<E> e = firstEntry(root); e != null;) {
+ if (c.contains(e.getKey())) {
+ e = successor(e);
+ } else {
+ final SplayedEntry<E> target = e;
+ e = successor(e);
+
+ delete(target);
+ modified = true;
+ }
+ }
+
+ return modified;
+ }
+
+ @Override
+ public Object[] toArray() {
+ Object[] result = new Object[size()];
+ int i = 0;
+
+ for (SplayedEntry<E> e = firstEntry(root); e != null; e = successor(e), ++i) {
+ result[i] = e.getKey();
+ }
+
+ return result;
+ }
+
+ @Override
public void clear() {
SplayMap.this.clear();
}
+
+ @Override
+ public Comparator<? super UnsignedInteger> comparator() {
+ return SplayMap.COMPARATOR;
+ }
+
+ @Override
+ public UnsignedInteger first() {
+ return firstKey();
+ }
+
+ @Override
+ public UnsignedInteger last() {
+ return lastKey();
+ }
+
+ @Override
+ public UnsignedInteger lower(UnsignedInteger key) {
+ return lowerKey(key);
+ }
+
+ @Override
+ public UnsignedInteger floor(UnsignedInteger key) {
+ return floorKey(key);
+ }
+
+ @Override
+ public UnsignedInteger ceiling(UnsignedInteger key) {
+ return ceilingKey(key);
+ }
+
+ @Override
+ public UnsignedInteger higher(UnsignedInteger key) {
+ return higherKey(key);
+ }
+
+ @Override
+ public UnsignedInteger pollFirst() {
+ Map.Entry<UnsignedInteger, ?> first = pollFirstEntry();
+ return first == null ? null : first.getKey();
+ }
+
+ @Override
+ public UnsignedInteger pollLast() {
+ Map.Entry<UnsignedInteger, ?> first = pollLastEntry();
+ return first == null ? null : first.getKey();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> descendingSet() {
+ return descendingMap().navigableKeySet();
+ }
+
+ @Override
+ public Iterator<UnsignedInteger> descendingIterator() {
+ return descendingMap().keySet().iterator();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> subSet(UnsignedInteger fromElement, boolean fromInclusive, UnsignedInteger toElement, boolean toInclusive) {
+ return new AscendingSubMap<>(SplayMap.this,
+ false, fromElement.intValue(), fromInclusive,
+ false, toElement.intValue(), toInclusive).navigableKeySet();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> headSet(UnsignedInteger toElement, boolean inclusive) {
+ return new AscendingSubMap<>(SplayMap.this,
+ true, 0, true, false, toElement.intValue(), inclusive).navigableKeySet();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> tailSet(UnsignedInteger fromElement, boolean inclusive) {
+ return new AscendingSubMap<>(SplayMap.this,
+ false, fromElement.intValue(), inclusive, true, 0, true).navigableKeySet();
+ }
+
+ @Override
+ public SortedSet<UnsignedInteger> subSet(UnsignedInteger fromElement, UnsignedInteger toElement) {
+ return subSet(fromElement, true, toElement, true);
+ }
+
+ @Override
+ public SortedSet<UnsignedInteger> headSet(UnsignedInteger toElement) {
+ return headSet(toElement, false);
+ }
+
+ @Override
+ public SortedSet<UnsignedInteger> tailSet(UnsignedInteger fromElement) {
+ return tailSet(fromElement, false);
+ }
}
private final class SplayMapEntrySet extends AbstractSet<Entry<UnsignedInteger, E>> {
@@ -945,13 +1188,11 @@
@Override
public boolean contains(Object o) {
- if (!(o instanceof Map.Entry) || SplayMap.this.root == null) {
- return false;
- }
-
- for (SplayedEntry<E> e = firstEntry(root); e != null; e = successor(e)) {
- if (e.equals(o)) {
- return true;
+ if ((o instanceof Map.Entry) && SplayMap.this.root != null) {
+ for (SplayedEntry<E> e = firstEntry(root); e != null; e = successor(e)) {
+ if (e.equals(o)) {
+ return true;
+ }
}
}
@@ -1067,7 +1308,7 @@
* An immutable {@link Map} entry that can be used when exposing raw entry mappings
* via the {@link Map} API.
*/
- public class ImmutableSplayMapEntry implements Map.Entry<UnsignedInteger, E> {
+ public static class ImmutableSplayMapEntry<E> implements Map.Entry<UnsignedInteger, E> {
private final SplayedEntry<E> entry;
@@ -1104,8 +1345,8 @@
}
}
- protected ImmutableSplayMapEntry export(SplayedEntry<E> entry) {
- return entry == null ? null : new ImmutableSplayMapEntry(entry);
+ protected static <V> ImmutableSplayMapEntry<V> export(SplayedEntry<V> entry) {
+ return entry == null ? null : new ImmutableSplayMapEntry<>(entry);
}
//----- Unsigned Integer comparator for Navigable Maps
@@ -1118,6 +1359,10 @@
}
}
+ protected static Comparator<? super UnsignedInteger> reverseComparator() {
+ return REVERSE_COMPARATOR;
+ }
+
//----- Navigable and Sorted Map implementation methods
@Override
@@ -1136,17 +1381,17 @@
}
@Override
- public ImmutableSplayMapEntry firstEntry() {
+ public ImmutableSplayMapEntry<E> firstEntry() {
return export(firstEntry(root));
}
@Override
- public ImmutableSplayMapEntry lastEntry() {
+ public ImmutableSplayMapEntry<E> lastEntry() {
return export(lastEntry(root));
}
@Override
- public ImmutableSplayMapEntry pollFirstEntry() {
+ public ImmutableSplayMapEntry<E> pollFirstEntry() {
SplayedEntry<E> firstEntry = firstEntry(root);
if (firstEntry != null) {
delete(firstEntry);
@@ -1155,7 +1400,7 @@
}
@Override
- public ImmutableSplayMapEntry pollLastEntry() {
+ public ImmutableSplayMapEntry<E> pollLastEntry() {
SplayedEntry<E> lastEntry = lastEntry(root);
if (lastEntry != null) {
delete(lastEntry);
@@ -1164,23 +1409,37 @@
}
@Override
- public ImmutableSplayMapEntry lowerEntry(UnsignedInteger key) {
- return export(lowerEntry(key.intValue()));
+ public ImmutableSplayMapEntry<E> lowerEntry(UnsignedInteger key) {
+ return export(splayToLowerEntry(key.intValue()));
}
@Override
public UnsignedInteger lowerKey(UnsignedInteger key) {
- final SplayedEntry<E> result = lowerEntry(key.intValue());
+ final SplayedEntry<E> result = splayToLowerEntry(key.intValue());
return result == null ? null : result.getKey();
}
- private SplayedEntry<E> lowerEntry(int key) {
+ /**
+ * Splay to a key-value mapping associated with the greatest key strictly less than the given
+ * key, or null if there is no such key.
+ *
+ * @param key
+ * The key whose next lower entry match is being queried.
+ *
+ * @return the next lower entry or null if no keys lower than the given value.
+ */
+ private SplayedEntry<E> splayToLowerEntry(int key) {
root = splay(root, key);
while (root != null) {
if (compare(root.getIntKey(), key) >= 0) {
- root = predecessor(root);
+ SplayedEntry<E> pred = predecessor(root);
+ if (pred != null) {
+ root = splay(root, pred.key);
+ } else {
+ return null;
+ }
} else {
break;
}
@@ -1190,23 +1449,37 @@
}
@Override
- public ImmutableSplayMapEntry higherEntry(UnsignedInteger key) {
- return export(higherEntry(key.intValue()));
+ public ImmutableSplayMapEntry<E> higherEntry(UnsignedInteger key) {
+ return export(splayToHigherEntry(key.intValue()));
}
@Override
public UnsignedInteger higherKey(UnsignedInteger key) {
- final SplayedEntry<E> result = higherEntry(key.intValue());
+ final SplayedEntry<E> result = splayToHigherEntry(key.intValue());
return result == null ? null : result.getKey();
}
- private SplayedEntry<E> higherEntry(int key) {
+ /**
+ * Splay to a key-value mapping associated with the least key strictly greater than the given
+ * key, or null if there is no such key.
+ *
+ * @param key
+ * The key whose next higher entry match is being queried.
+ *
+ * @return the next highest entry or null if no keys higher than the given value.
+ */
+ private SplayedEntry<E> splayToHigherEntry(int key) {
root = splay(root, key);
while (root != null) {
if (compare(root.getIntKey(), key) <= 0) {
- root = successor(root);
+ SplayedEntry<E> succ = successor(root);
+ if (succ != null) {
+ root = splay(root, succ.key);
+ } else {
+ return null;
+ }
} else {
break;
}
@@ -1216,23 +1489,37 @@
}
@Override
- public ImmutableSplayMapEntry floorEntry(UnsignedInteger key) {
- return export(floorEntry(key.intValue()));
+ public ImmutableSplayMapEntry<E> floorEntry(UnsignedInteger key) {
+ return export(splayToFloorEntry(key.intValue()));
}
@Override
public UnsignedInteger floorKey(UnsignedInteger key) {
- final SplayedEntry<E> result = floorEntry(key.intValue());
+ final SplayedEntry<E> result = splayToFloorEntry(key.intValue());
return result == null ? null : result.getKey();
}
- private SplayedEntry<E> floorEntry(int key) {
+ /**
+ * Splay to a key-value mapping associated with the greatest key less than or equal to
+ * the given key, or null if there is no such key.
+ *
+ * @param key
+ * The key whose floor entry match is being queried.
+ *
+ * @return the entry or next lowest entry or null if no keys less then or equal to the given value.
+ */
+ private SplayedEntry<E> splayToFloorEntry(int key) {
root = splay(root, key);
while (root != null) {
if (compare(root.getIntKey(), key) > 0) {
- root = predecessor(root);
+ SplayedEntry<E> pred = predecessor(root);
+ if (pred != null) {
+ root = splay(root, pred.key);
+ } else {
+ return null;
+ }
} else {
break;
}
@@ -1242,23 +1529,37 @@
}
@Override
- public ImmutableSplayMapEntry ceilingEntry(UnsignedInteger key) {
- return export(ceilingEntry(key.intValue()));
+ public ImmutableSplayMapEntry<E> ceilingEntry(UnsignedInteger key) {
+ return export(splayToCeilingEntry(key.intValue()));
}
@Override
public UnsignedInteger ceilingKey(UnsignedInteger key) {
- final SplayedEntry<E> result = ceilingEntry(key.intValue());
+ final SplayedEntry<E> result = splayToCeilingEntry(key.intValue());
return result == null ? null : result.getKey();
}
- private SplayedEntry<E> ceilingEntry(int key) {
+ /**
+ * Splay to a key-value mapping associated with the least key greater than or equal
+ * to the given key, or null if there is no such key.
+ *
+ * @param key
+ * The key whose ceiling entry match is being queried.
+ *
+ * @return the entry or next highest entry or null if no keys higher than or equal to the given value.
+ */
+ private SplayedEntry<E> splayToCeilingEntry(int key) {
root = splay(root, key);
while (root != null) {
if (compare(root.getIntKey(), key) < 0) {
- root = successor(root);
+ SplayedEntry<E> succ = successor(root);
+ if (succ != null) {
+ root = splay(root, succ.key);
+ } else {
+ return null;
+ }
} else {
break;
}
@@ -1269,55 +1570,1134 @@
@Override
public NavigableMap<UnsignedInteger, E> descendingMap() {
- // TODO Auto-generated method stub
- return null;
+ return (descendingMapView != null) ? descendingMapView :
+ (descendingMapView = new DescendingSubMap<>(this,
+ true, 0, true,
+ true, UnsignedInteger.MAX_VALUE.intValue(), true));
}
@Override
public NavigableSet<UnsignedInteger> navigableKeySet() {
- // TODO Auto-generated method stub
- return null;
+ if (keySet == null) {
+ keySet = new SplayMapKeySet();
+ }
+ return keySet;
}
@Override
public NavigableSet<UnsignedInteger> descendingKeySet() {
- // TODO Auto-generated method stub
- return null;
+ return descendingMap().navigableKeySet();
}
@Override
public NavigableMap<UnsignedInteger, E> subMap(UnsignedInteger fromKey, boolean fromInclusive, UnsignedInteger toKey, boolean toInclusive) {
- // TODO Auto-generated method stub
- return null;
+ return new AscendingSubMap<>(this, false, fromKey.intValue(), fromInclusive, false, toKey.intValue(), toInclusive);
}
@Override
public NavigableMap<UnsignedInteger, E> headMap(UnsignedInteger toKey, boolean inclusive) {
- // TODO Auto-generated method stub
- return null;
+ return new AscendingSubMap<>(this, true, 0, true, false, toKey.intValue(), inclusive);
}
@Override
public NavigableMap<UnsignedInteger, E> tailMap(UnsignedInteger fromKey, boolean inclusive) {
- // TODO Auto-generated method stub
- return null;
+ return new AscendingSubMap<>(this, false, fromKey.intValue(), inclusive, true, UnsignedInteger.MAX_VALUE.intValue(), true);
}
@Override
public SortedMap<UnsignedInteger, E> subMap(UnsignedInteger fromKey, UnsignedInteger toKey) {
- // TODO Auto-generated method stub
- return null;
+ return subMap(fromKey, true, toKey, false);
}
@Override
public SortedMap<UnsignedInteger, E> headMap(UnsignedInteger toKey) {
- // TODO Auto-generated method stub
- return null;
+ return headMap(toKey, false);
}
@Override
public SortedMap<UnsignedInteger, E> tailMap(UnsignedInteger fromKey) {
- // TODO Auto-generated method stub
+ return tailMap(fromKey, true);
+ }
+
+ /**
+ * Gets a key-value mapping associated with the given key or its successor.
+ * This method does not splay the tree so it will not bring the result to the root.
+ *
+ * @param key
+ * The key to search for in the mappings
+ *
+ * @return the entry that matches the search criteria or null if no valid match.
+ */
+ private SplayedEntry<E> getCeilingEntry(int key) {
+ SplayedEntry<E> result = this.root;
+
+ while (result != null) {
+ final int comparison = SplayMap.compare(key, result.key);
+ if (comparison < 0) {
+ // search key is less than current go left to get a smaller value
+ if (result.left != null) {
+ result = result.left;
+ } else {
+ return result; // nothing smaller exists
+ }
+ } else if (comparison > 0) {
+ // search key is greater than current go right to get a bigger one
+ // or go back up to the root of this branch
+ if (result.right != null) {
+ result = result.right;
+ } else {
+ SplayedEntry<E> parent = result.parent;
+ SplayedEntry<E> current = result;
+ while (parent != null && current == parent.right) {
+ current = parent;
+ parent = parent.parent;
+ }
+ return parent;
+ }
+ } else {
+ return result; // Found it.
+ }
+ }
+
return null;
}
+
+ /**
+ * Gets a key-value mapping associated with the given key or its predecessor.
+ * This method does not splay the tree so it will not bring the result to the root.
+ *
+ * @param key
+ * The key to search for in the mappings
+ *
+ * @return the entry that matches the search criteria or null if no valid match.
+ */
+ private SplayedEntry<E> getFloorEntry(int key) {
+ SplayedEntry<E> result = this.root;
+
+ while (result != null) {
+ final int comparison = SplayMap.compare(key, result.key);
+ if (comparison > 0) {
+ // search key is greater than current go right to get a bigger value
+ if (result.right != null) {
+ result = result.right;
+ } else {
+ return result; // nothing bigger exists
+ }
+ } else if (comparison < 0) {
+ // search key is less than current go left to get a smaller one
+ // or go back up to the root of this branch
+ if (result.left != null) {
+ result = result.left;
+ } else {
+ SplayedEntry<E> parent = result.parent;
+ SplayedEntry<E> current = result;
+ while (parent != null && current == parent.left) {
+ current = parent;
+ parent = parent.parent;
+ }
+ return parent;
+ }
+ } else {
+ return result; // Found it.
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets a key-value mapping associated with the next entry higher than the given
+ * key or null if no entries exists with a higher key value. This method does not
+ * splay the tree so it will not bring the result to the root.
+ *
+ * @param key
+ * The key to search for in the mappings
+ *
+ * @return the entry that matches the search criteria or null if no valid match.
+ */
+ private SplayedEntry<E> getHigherEntry(int key) {
+ SplayedEntry<E> result = this.root;
+
+ while (result != null) {
+ final int comparison = SplayMap.compare(key, result.key);
+ if (comparison < 0) {
+ // search key is less than current go left to get a smaller value
+ if (result.left != null) {
+ result = result.left;
+ } else {
+ return result; // nothing smaller exists
+ }
+ } else {
+ // search key is greater than current go right to get a bigger one
+ // or go back up to the root of this branch
+ if (result.right != null) {
+ result = result.right;
+ } else {
+ SplayedEntry<E> parent = result.parent;
+ SplayedEntry<E> current = result;
+ while (parent != null && current == parent.right) {
+ current = parent;
+ parent = parent.parent;
+ }
+ return parent;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets a key-value mapping associated with next smallest entry below the given key
+ * or null if no smaller entries exist. This method does not splay the tree so it
+ * will not bring the result to the root.
+ *
+ * @param key
+ * The key to search for in the mappings
+ *
+ * @return the entry that matches the search criteria or null if no valid match.
+ */
+ private SplayedEntry<E> getLowerEntry(int key) {
+ SplayedEntry<E> result = this.root;
+
+ while (result != null) {
+ final int comparison = SplayMap.compare(key, result.key);
+ if (comparison > 0) {
+ // search key is greater than current go right to get a bigger value
+ if (result.right != null) {
+ result = result.right;
+ } else {
+ return result; // nothing bigger exists
+ }
+ } else {
+ // search key is less than current go left to get a smaller one
+ // or go back up to the root of this branch
+ if (result.left != null) {
+ result = result.left;
+ } else {
+ SplayedEntry<E> parent = result.parent;
+ SplayedEntry<E> current = result;
+ while (parent != null && current == parent.left) {
+ current = parent;
+ parent = parent.parent;
+ }
+ return parent;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected static class NavigableSubMapKeySet extends AbstractSet<UnsignedInteger> implements NavigableSet<UnsignedInteger> {
+
+ private final NavigableSubMap<?> backingMap;
+
+ public NavigableSubMapKeySet(NavigableSubMap<?> backingMap) {
+ this.backingMap = backingMap;
+ }
+
+ @Override
+ public Iterator<UnsignedInteger> iterator() {
+ return backingMap.keyIterator();
+ }
+
+ @Override
+ public Iterator<UnsignedInteger> descendingIterator() {
+ return ((NavigableSubMap<?>)backingMap.descendingMap()).descendingKeyIterator();
+ }
+
+ @Override
+ public int size() {
+ return backingMap.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backingMap.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return backingMap.containsKey(o);
+ }
+
+ @Override
+ public boolean remove(Object target) {
+ int oldSize = backingMap.size();
+ backingMap.remove(target);
+ return oldSize != backingMap.size();
+ }
+
+ @Override
+ public void clear() {
+ backingMap.clear();
+ }
+
+ @Override
+ public Comparator<? super UnsignedInteger> comparator() {
+ return backingMap.comparator();
+ }
+
+ @Override
+ public UnsignedInteger first() {
+ return backingMap.firstKey();
+ }
+
+ @Override
+ public UnsignedInteger last() {
+ return backingMap.lastKey();
+ }
+
+ @Override
+ public UnsignedInteger lower(UnsignedInteger key) {
+ return backingMap.lowerKey(key);
+ }
+
+ @Override
+ public UnsignedInteger floor(UnsignedInteger key) {
+ return backingMap.floorKey(key);
+ }
+
+ @Override
+ public UnsignedInteger ceiling(UnsignedInteger key) {
+ return backingMap.ceilingKey(key);
+ }
+
+ @Override
+ public UnsignedInteger higher(UnsignedInteger key) {
+ return backingMap.higherKey(key);
+ }
+
+ @Override
+ public UnsignedInteger pollFirst() {
+ Map.Entry<UnsignedInteger, ?> first = backingMap.pollFirstEntry();
+ return first == null ? null : first.getKey();
+ }
+
+ @Override
+ public UnsignedInteger pollLast() {
+ Map.Entry<UnsignedInteger, ?> first = backingMap.pollLastEntry();
+ return first == null ? null : first.getKey();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> descendingSet() {
+ return backingMap.descendingMap().navigableKeySet();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> subSet(UnsignedInteger fromElement, boolean fromInclusive, UnsignedInteger toElement, boolean toInclusive) {
+ return backingMap.subMap(fromElement, fromInclusive, toElement, toInclusive).navigableKeySet();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> headSet(UnsignedInteger toElement, boolean inclusive) {
+ return backingMap.headMap(toElement, inclusive).navigableKeySet();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> tailSet(UnsignedInteger fromElement, boolean inclusive) {
+ return backingMap.tailMap(fromElement, inclusive).navigableKeySet();
+ }
+
+ @Override
+ public SortedSet<UnsignedInteger> subSet(UnsignedInteger fromElement, UnsignedInteger toElement) {
+ return subSet(fromElement, true, toElement, true);
+ }
+
+ @Override
+ public SortedSet<UnsignedInteger> headSet(UnsignedInteger toElement) {
+ return headSet(toElement, false);
+ }
+
+ @Override
+ public SortedSet<UnsignedInteger> tailSet(UnsignedInteger fromElement) {
+ return tailSet(fromElement, false);
+ }
+ }
+
+ /**
+ * Utility base class for the {@link SplayMap} that allows access to a sub region of the
+ * backing map.
+ * <p>
+ * If the from start directive is <code>true</code> then the map ignores the start key
+ * and the start key inclusive directive and assigns the start key as zero and the
+ * start key inclusive value to true.
+ * <p>
+ * If the to end directive is <code>true</code> then the map ignores the end key
+ * and the end key inclusive directive and assigns the end key to {@link UnsignedInteger#MAX_VALUE}
+ * and the end key inclusive flag to true.
+ *
+ * @param <E> The value type for this {@link NavigableSubMap}
+ */
+ private abstract static class NavigableSubMap<E> extends AbstractMap<UnsignedInteger, E> implements NavigableMap<UnsignedInteger, E> {
+
+ protected final SplayMap<E> backingMap;
+
+ final int startKey;
+ final int endKey;
+ final boolean fromStart;
+ final boolean toEnd;
+ final boolean startInclusive;
+ final boolean endInclusive;
+
+ private transient NavigableSubMapKeySet navigableSubMapKeySet;
+
+ NavigableSubMap(final SplayMap<E> map,
+ final boolean fromStart, final int start, final boolean startInclusive,
+ final boolean toEnd, final int end, final boolean endInclusive) {
+
+ if (SplayMap.compare(start, end) > 0) {
+ throw new IllegalArgumentException("The start key cannot be greater than the end key");
+ }
+
+ this.backingMap = map;
+ this.fromStart = fromStart;
+ this.toEnd = toEnd;
+ this.startKey = fromStart ? 0 : start;
+ this.endKey = toEnd ? UnsignedInteger.MAX_VALUE.intValue() : end;
+ this.startInclusive = fromStart ? true : startInclusive;
+ this.endInclusive = toEnd ? true : endInclusive;
+ }
+
+ //----- Basic Map implementation that defers to backing when possible
+
+ @Override
+ public boolean isEmpty() {
+ return (fromStart && toEnd) ? backingMap.size == 0 : entrySet().isEmpty();
+ }
+
+ @Override
+ public int size() {
+ return (fromStart && toEnd) ? backingMap.size : entrySet().size();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ Number numericKey = (Number) key;
+ return containsKey(numericKey.intValue());
+ }
+
+ public boolean containsKey(int key) {
+ return isInRange(key) && backingMap.containsKey(key);
+ }
+
+ @Override
+ public final E put(UnsignedInteger key, E value) {
+ return put(key.intValue(), value);
+ }
+
+ public final E put(int key, E value) {
+ if (!isInRange(key)) {
+ throw new IllegalArgumentException("The given key is out of range for this ranged sub-map");
+ }
+
+ return backingMap.put(key, value);
+ }
+
+ @Override
+ public final E get(Object key) {
+ Number numericKey = (Number) key;
+ return get(numericKey.intValue());
+ }
+
+ public final E get(int key) {
+ return !isInRange(key) ? null : backingMap.get(key);
+ }
+
+ @Override
+ public final E remove(Object key) {
+ Number numericKey = (Number) key;
+ return remove(numericKey.intValue());
+ }
+
+ public final E remove(int key) {
+ return !isInRange(key) ? null : backingMap.remove(key);
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> ceilingEntry(UnsignedInteger key) {
+ return SplayMap.export(getCeilingEntry(key.intValue()));
+ }
+
+ @Override
+ public final UnsignedInteger ceilingKey(UnsignedInteger key) {
+ SplayedEntry<E> result = getCeilingEntry(key.intValue());
+ return result == null ? null : result.getKey();
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> higherEntry(UnsignedInteger key) {
+ return SplayMap.export(getHigherEntry(key.intValue()));
+ }
+
+ @Override
+ public final UnsignedInteger higherKey(UnsignedInteger key) {
+ SplayedEntry<E> result = getHigherEntry(key.intValue());
+ return result == null ? null : result.getKey();
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> floorEntry(UnsignedInteger key) {
+ return SplayMap.export(getFloorEntry(key.intValue()));
+ }
+
+ @Override
+ public final UnsignedInteger floorKey(UnsignedInteger key) {
+ SplayedEntry<E> result = getFloorEntry(key.intValue());
+ return result == null ? null : result.getKey();
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> lowerEntry(UnsignedInteger key) {
+ return SplayMap.export(getLowerEntry(key.intValue()));
+ }
+
+ @Override
+ public final UnsignedInteger lowerKey(UnsignedInteger key) {
+ SplayedEntry<E> result = getLowerEntry(key.intValue());
+ return result == null ? null : result.getKey();
+ }
+
+ @Override
+ public final UnsignedInteger firstKey() {
+ SplayedEntry<E> result = getLowestEntry();
+ if (result != null) {
+ return result.getKey();
+ }
+
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public final UnsignedInteger lastKey() {
+ SplayedEntry<E> result = getHighestEntry();
+ if (result != null) {
+ return result.getKey();
+ }
+
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> firstEntry() {
+ return SplayMap.export(getLowestEntry());
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> lastEntry() {
+ return SplayMap.export(getHighestEntry());
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> pollFirstEntry() {
+ SplayedEntry<E> result = getLowestEntry();
+ Map.Entry<UnsignedInteger, E> exported = SplayMap.export(result);
+ if (exported != null) {
+ backingMap.delete(result);
+ }
+
+ return exported;
+ }
+
+ @Override
+ public final Map.Entry<UnsignedInteger, E> pollLastEntry() {
+ SplayedEntry<E> result = getHighestEntry();
+ Map.Entry<UnsignedInteger, E> exported = SplayMap.export(result);
+ if (exported != null) {
+ backingMap.delete(result);
+ }
+
+ return exported;
+ }
+
+ @Override
+ public SortedMap<UnsignedInteger, E> subMap(UnsignedInteger fromKey, UnsignedInteger toKey) {
+ return subMap(fromKey, true, toKey, false);
+ }
+
+ @Override
+ public SortedMap<UnsignedInteger, E> headMap(UnsignedInteger toKey) {
+ return headMap(toKey, false);
+ }
+
+ @Override
+ public SortedMap<UnsignedInteger, E> tailMap(UnsignedInteger fromKey) {
+ return tailMap(fromKey, true);
+ }
+
+ @Override
+ public final Set<UnsignedInteger> keySet() {
+ return navigableKeySet();
+ }
+
+ @Override
+ public NavigableSet<UnsignedInteger> descendingKeySet() {
+ return descendingMap().navigableKeySet();
+ }
+
+ @Override
+ public final NavigableSet<UnsignedInteger> navigableKeySet() {
+ return (navigableSubMapKeySet != null) ?
+ navigableSubMapKeySet : (navigableSubMapKeySet = new NavigableSubMapKeySet(this));
+ }
+
+ //----- The abstract API that sub-classes will define
+
+ /**
+ * Returns an iterator appropriate to the sub map implementation
+ * which may be ascending or descending but must be the inverse of
+ * the direction of iterator returned from the {@link #descendingKeyIterator()}
+ * method.
+ *
+ * @return an iterator that operates over the keys in this sub map range.
+ */
+ abstract Iterator<UnsignedInteger> keyIterator();
+
+ /**
+ * Returns an iterator appropriate to the sub map implementation
+ * which may be ascending or descending but must be the inverse of
+ * the direction of iterator returned from the {@link #keyIterator()}
+ * method.
+ *
+ * @return an iterator that operates over the keys in this sub map range.
+ */
+ abstract Iterator<UnsignedInteger> descendingKeyIterator();
+
+ //----- Sub Map collection types
+
+ /**
+ * Specialized iterator for the sub-map type that iterators on a generic type
+ * but internally contains splayed entries from the splay map tree.
+ *
+ * @param <T>
+ */
+ protected abstract class NavigableSubMapIterator<T> implements Iterator<T> {
+ SplayedEntry<E> lastReturned;
+ SplayedEntry<E> next;
+ final int limitKey;
+ int expectedModCount;
+
+ NavigableSubMapIterator(SplayedEntry<E> start, SplayedEntry<E> limit) {
+ expectedModCount = backingMap.modCount;
+ lastReturned = null;
+ next = start;
+ limitKey = limit != null ? limit.key : UnsignedInteger.MAX_VALUE.intValue();
+ }
+
+ @Override
+ public abstract boolean hasNext();
+
+ @Override
+ public abstract T next();
+
+ @Override
+ public final void remove() {
+ if (lastReturned == null) {
+ throw new IllegalStateException();
+ }
+ if (backingMap.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+
+ backingMap.delete(lastReturned);
+ lastReturned = null;
+ expectedModCount = backingMap.modCount;
+ }
+
+ final boolean hasNextEntry() {
+ return next != null && SplayMap.compare(next.key, limitKey) <= 0;
+ }
+
+ final SplayedEntry<E> nextEntry() {
+ SplayedEntry<E> e = next;
+
+ if (e == null || SplayMap.compare(next.key, limitKey) > 0) {
+ throw new NoSuchElementException();
+ }
+ if (backingMap.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+
+ next = successor(e);
+ lastReturned = e;
+ return e;
+ }
+
+ final boolean hasPrevEntry() {
+ return next != null && SplayMap.compare(next.key, limitKey) >= 0;
+ }
+
+ final SplayedEntry<E> previousEntry() {
+ SplayedEntry<E> e = next;
+
+ if (e == null || SplayMap.compare(next.key, limitKey) < 0) {
+ throw new NoSuchElementException();
+ }
+ if (backingMap.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+
+ next = predecessor(e);
+ lastReturned = e;
+ return e;
+ }
+ }
+
+ final class NavigableSubMapEntryIterator extends NavigableSubMapIterator<Map.Entry<UnsignedInteger, E>> {
+ NavigableSubMapEntryIterator(SplayedEntry<E> first, SplayedEntry<E> fence) {
+ super(first, fence);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasNextEntry();
+ }
+
+ @Override
+ public Map.Entry<UnsignedInteger, E> next() {
+ return nextEntry();
+ }
+ }
+
+ final class DescendingNavigableSubMapEntryIterator extends NavigableSubMapIterator<Map.Entry<UnsignedInteger, E>> {
+ DescendingNavigableSubMapEntryIterator(SplayedEntry<E> first, SplayedEntry<E> fence) {
+ super(first, fence);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasPrevEntry();
+ }
+
+ @Override
+ public Map.Entry<UnsignedInteger, E> next() {
+ return previousEntry();
+ }
+ }
+
+ final class NavigableSubMapKeyIterator extends NavigableSubMapIterator<UnsignedInteger> {
+ NavigableSubMapKeyIterator(SplayedEntry<E> first, SplayedEntry<E> fence) {
+ super(first, fence);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasNextEntry();
+ }
+
+ @Override
+ public UnsignedInteger next() {
+ return nextEntry().getKey();
+ }
+ }
+
+ final class DescendingNavigableSubMapKeyIterator extends NavigableSubMapIterator<UnsignedInteger> {
+ DescendingNavigableSubMapKeyIterator(SplayedEntry<E> first, SplayedEntry<E> fence) {
+ super(first, fence);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return hasPrevEntry();
+ }
+
+ @Override
+ public UnsignedInteger next() {
+ return previousEntry().getKey();
+ }
+ }
+
+ protected abstract class NavigableSubMapEntrySet extends AbstractSet<Map.Entry<UnsignedInteger, E>> {
+ private transient int size = -1;
+ private transient int sizeModCount;
+
+ @Override
+ public int size() {
+ if ((!fromStart || !toEnd) && (size == -1 || sizeModCount != backingMap.modCount)) {
+ sizeModCount = backingMap.modCount;
+ size = 0;
+ Iterator<?> i = iterator();
+ while (i.hasNext()) {
+ size++;
+ i.next();
+ }
+
+ return size;
+ }
+
+ return backingMap.size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ SplayedEntry<E> n = getLowestEntry();
+ return n == null || isToHigh(n.key);
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ if (o instanceof Map.Entry) {
+ Map.Entry<?,?> entry = (Map.Entry<?,?>) o;
+ Number key = Number.class.cast(entry.getKey());
+
+ if (!isInRange(key.intValue())) {
+ return false;
+ }
+
+ SplayedEntry<E> node = backingMap.findEntry(key.intValue());
+
+ if (node != null) {
+ return Objects.equals(node.getValue(), entry.getValue());
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (o instanceof Map.Entry) {
+ Map.Entry<?,?> entry = (Map.Entry<?,?>) o;
+ Number key = Number.class.cast(entry.getKey());
+
+ if (!isInRange(key.intValue())) {
+ return false;
+ }
+
+ SplayedEntry<E> node = backingMap.findEntry(key.intValue());
+
+ if (node != null) {
+ backingMap.delete(node);
+ return Objects.equals(node.getValue(), entry.getValue());
+ }
+ }
+
+ return false;
+ }
+ }
+
+ //----- Abstract access API which will be inverted based on sub map type
+
+ abstract SplayedEntry<E> getLowestEntry();
+
+ abstract SplayedEntry<E> getHighestEntry();
+
+ abstract SplayedEntry<E> getCeilingEntry(int key);
+
+ abstract SplayedEntry<E> getHigherEntry(int key);
+
+ abstract SplayedEntry<E> getFloorEntry(int key);
+
+ abstract SplayedEntry<E> getLowerEntry(int key);
+
+ //----- Internal API that aids this and other sub-classes
+
+ protected final SplayedEntry<E> lowestPossibleEntry() {
+ SplayedEntry<E> e =
+ startInclusive ? backingMap.getCeilingEntry(startKey) : backingMap.getHigherEntry(startKey);
+
+ return (e == null || isToHigh(e.key)) ? null : e;
+ }
+
+ protected final SplayedEntry<E> highestPossibleEntry() {
+ SplayedEntry<E> e =
+ endInclusive ? backingMap.getFloorEntry(endKey) : backingMap.getLowerEntry(endKey);
+
+ return (e == null || isToLow(e.key)) ? null : e;
+ }
+
+ protected final SplayedEntry<E> entryOrSuccessor(int key) {
+ if (isToLow(key)) {
+ return lowestPossibleEntry();
+ }
+ SplayedEntry<E> e = backingMap.getCeilingEntry(key);
+
+ return (e == null || isToHigh(e.key)) ? null : e;
+ }
+
+ protected final SplayedEntry<E> entrySuccessor(int key) {
+ if (isToLow(key)) {
+ return lowestPossibleEntry();
+ }
+ SplayedEntry<E> e = backingMap.getHigherEntry(key);
+
+ return (e == null || isToHigh(e.key)) ? null : e;
+ }
+
+ protected final SplayedEntry<E> entryOrPredecessor(int key) {
+ if (isToHigh(key)) {
+ return highestPossibleEntry();
+ }
+ SplayedEntry<E> e = backingMap.getFloorEntry(key);
+
+ return (e == null || isToLow(e.key)) ? null : e;
+ }
+
+ protected final SplayedEntry<E> entryPredecessor(int key) {
+ if (isToHigh(key)) {
+ return highestPossibleEntry();
+ }
+ SplayedEntry<E> e = backingMap.getLowerEntry(key);
+
+ return (e == null || isToLow(e.key)) ? null : e;
+ }
+
+ protected final void checkInRange(int fromKey, boolean fromInclusive, int toKey, boolean toInclusive) {
+ if (!isInRange(fromKey, fromInclusive)) {
+ throw new IllegalArgumentException("Given from key is out of range of this sub map view: " + fromKey);
+ }
+ if (!isInRange(toKey, toInclusive)) {
+ throw new IllegalArgumentException("Given to key is out of range of this sub map view: " + toKey);
+ }
+ }
+
+ protected final boolean isInRange(int key) {
+ return !isToLow(key) && !isToHigh(key);
+ }
+
+ final boolean isInCapturedRange(int key) {
+ return SplayMap.compare(key, startKey) >= 0 && SplayMap.compare(endKey, key) >= 0;
+ }
+
+ final boolean isInRange(int key, boolean inclusive) {
+ return inclusive ? isInRange(key) : isInCapturedRange(key);
+ }
+
+ protected final boolean isToLow(int key) {
+ int result = SplayMap.compare(key, startKey);
+ if (result < 0 || result == 0 && !startInclusive) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected final boolean isToHigh(int key) {
+ int result = SplayMap.compare(key, endKey);
+ if (result > 0 || result == 0 && !endInclusive) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ protected static final class AscendingSubMap<V> extends NavigableSubMap<V> {
+
+ AscendingSubMap(SplayMap<V> m,
+ boolean fromStart, int fromKey, boolean startInclusive,
+ boolean toEnd, int endKey, boolean endInclusive) {
+ super(m, fromStart, fromKey, startInclusive, toEnd, endKey, endInclusive);
+ }
+
+ private transient NavigableMap<UnsignedInteger, V> descendingMapView;
+ private transient NavigableSubMapEntrySet navigableEntrySet;
+
+ @Override
+ public Comparator<? super UnsignedInteger> comparator() {
+ return COMPARATOR;
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> descendingMap() {
+ return descendingMapView != null ? descendingMapView :
+ (descendingMapView = new DescendingSubMap<>(backingMap,
+ fromStart, startKey, startInclusive, toEnd, endKey, endInclusive));
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> subMap(UnsignedInteger fromKey, boolean fromInclusive,
+ UnsignedInteger toKey, boolean toInclusive) {
+ checkInRange(fromKey.intValue(), fromInclusive, toKey.compareTo(0), toInclusive);
+ return new AscendingSubMap<>(backingMap,
+ false, fromKey.intValue(), fromInclusive, false, toKey.intValue(), toInclusive);
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> headMap(UnsignedInteger toKey, boolean inclusive) {
+ isInRange(toKey.intValue(), inclusive);
+ return new AscendingSubMap<>(backingMap,
+ fromStart, startKey, startInclusive, false, toKey.intValue(), inclusive);
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> tailMap(UnsignedInteger fromKey, boolean inclusive) {
+ isInRange(fromKey.intValue(), inclusive);
+ return new AscendingSubMap<>(backingMap,
+ false, fromKey.intValue(), inclusive, toEnd, endKey, endInclusive);
+ }
+
+ @Override
+ public Set<Entry<UnsignedInteger, V>> entrySet() {
+ return navigableEntrySet != null ?
+ navigableEntrySet : (navigableEntrySet = new AscendingNavigableSubMapEntrySet());
+ }
+
+ @Override
+ Iterator<UnsignedInteger> keyIterator() {
+ return new NavigableSubMapKeyIterator(getLowestEntry(), getHighestEntry());
+ }
+
+ @Override
+ Iterator<UnsignedInteger> descendingKeyIterator() {
+ return new DescendingNavigableSubMapKeyIterator(getLowestEntry(), getHighestEntry());
+ }
+
+ @Override
+ SplayedEntry<V> getLowestEntry() {
+ return super.lowestPossibleEntry();
+ }
+
+ @Override
+ SplayedEntry<V> getHighestEntry() {
+ return super.highestPossibleEntry();
+ }
+
+ @Override
+ SplayedEntry<V> getCeilingEntry(int key) {
+ return super.entryOrSuccessor(key);
+ }
+
+ @Override
+ SplayedEntry<V> getHigherEntry(int key) {
+ return super.entrySuccessor(key);
+ }
+
+ @Override
+ SplayedEntry<V> getFloorEntry(int key) {
+ return super.entryOrPredecessor(key);
+ }
+
+ @Override
+ SplayedEntry<V> getLowerEntry(int key) {
+ return super.entryPredecessor(key);
+ }
+
+ private final class AscendingNavigableSubMapEntrySet extends NavigableSubMapEntrySet {
+
+ @Override
+ public Iterator<Entry<UnsignedInteger, V>> iterator() {
+ return new NavigableSubMapEntryIterator(lowestPossibleEntry(), highestPossibleEntry());
+ }
+ }
+ }
+
+ protected static final class DescendingSubMap<V> extends NavigableSubMap<V> {
+
+ DescendingSubMap(SplayMap<V> m,
+ boolean fromStart, int fromKey, boolean startInclusive,
+ boolean toEnd, int endKey, boolean endInclusive) {
+ super(m, fromStart, fromKey, startInclusive, toEnd, endKey, endInclusive);
+ }
+
+ private transient NavigableMap<UnsignedInteger, V> ascendingMapView;
+ private transient NavigableSubMapEntrySet navigableEntrySet;
+
+ @Override
+ public Comparator<? super UnsignedInteger> comparator() {
+ return REVERSE_COMPARATOR;
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> descendingMap() {
+ return ascendingMapView != null ? ascendingMapView :
+ (ascendingMapView = new AscendingSubMap<>(backingMap,
+ fromStart, startKey, startInclusive, toEnd, endKey, endInclusive));
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> subMap(UnsignedInteger fromKey, boolean fromInclusive,
+ UnsignedInteger toKey, boolean toInclusive) {
+ checkInRange(fromKey.intValue(), fromInclusive, toKey.intValue(), toInclusive);
+ return new DescendingSubMap<>(backingMap,
+ false, toKey.intValue(), toInclusive, false, fromKey.intValue(), fromInclusive);
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> headMap(UnsignedInteger toKey, boolean inclusive) {
+ isInRange(toKey.intValue(), inclusive);
+ return new DescendingSubMap<>(backingMap,
+ false, toKey.intValue(), inclusive, toEnd, endKey, endInclusive);
+ }
+
+ @Override
+ public NavigableMap<UnsignedInteger, V> tailMap(UnsignedInteger fromKey, boolean inclusive) {
+ isInRange(fromKey.intValue(), inclusive);
+ return new DescendingSubMap<>(backingMap,
+ fromStart, startKey, startInclusive, false, fromKey.intValue(), inclusive);
+ }
+
+ @Override
+ public Set<Entry<UnsignedInteger, V>> entrySet() {
+ return navigableEntrySet != null ?
+ navigableEntrySet : (navigableEntrySet = new DescendingNavigableSubMapEntrySet());
+ }
+
+ @Override
+ Iterator<UnsignedInteger> keyIterator() {
+ return new DescendingNavigableSubMapKeyIterator(getHighestEntry(), getLowestEntry());
+ }
+
+ @Override
+ Iterator<UnsignedInteger> descendingKeyIterator() {
+ return new NavigableSubMapKeyIterator(getHighestEntry(), getLowestEntry());
+ }
+
+ @Override
+ SplayedEntry<V> getLowestEntry() {
+ return super.highestPossibleEntry();
+ }
+
+ @Override
+ SplayedEntry<V> getHighestEntry() {
+ return super.lowestPossibleEntry();
+ }
+
+ @Override
+ SplayedEntry<V> getCeilingEntry(int key) {
+ return super.entryPredecessor(key);
+ }
+
+ @Override
+ SplayedEntry<V> getHigherEntry(int key) {
+ return super.entryPredecessor(key);
+ }
+
+ @Override
+ SplayedEntry<V> getFloorEntry(int key) {
+ return super.entryOrSuccessor(key);
+ }
+
+ @Override
+ SplayedEntry<V> getLowerEntry(int key) {
+ return super.entryOrSuccessor(key);
+ }
+
+ @Override
+ public void forEach(BiConsumer<? super UnsignedInteger, ? super V> action) {
+ Objects.requireNonNull(action);
+ for (Map.Entry<UnsignedInteger, V> entry : entrySet()) {
+ UnsignedInteger k;
+ V v;
+ try {
+ k = entry.getKey();
+ v = entry.getValue();
+ } catch (IllegalStateException ise) {
+ // this usually means the entry is no longer in the map.
+ throw new ConcurrentModificationException(ise);
+ }
+ action.accept(k, v);
+ }
+ }
+
+ private final class DescendingNavigableSubMapEntrySet extends NavigableSubMapEntrySet {
+
+ @Override
+ public Iterator<Entry<UnsignedInteger, V>> iterator() {
+ return new DescendingNavigableSubMapEntryIterator(highestPossibleEntry(), lowestPossibleEntry());
+ }
+ }
+ }
}
diff --git a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/ProtonSenderTest.java b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/ProtonSenderTest.java
index 66d44bd..6f3cbcf 100644
--- a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/ProtonSenderTest.java
+++ b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/impl/ProtonSenderTest.java
@@ -4158,6 +4158,75 @@
}
@Test
+ public void testSenderReportsDeliveryUpdatedOnDispositionForMultipleTransfersInsideTheRange() throws Exception {
+ final Engine engine = EngineFactory.PROTON.createNonSaslEngine();
+ engine.errorHandler(result -> failure = result.failureCause());
+ final ProtonTestConnector peer = createTestPeer(engine);
+ final byte[] payload = new byte[] {0, 1, 2, 3, 4};
+
+ peer.expectAMQPHeader().respondWithAMQPHeader();
+ peer.expectOpen().respond().withContainerId("driver");
+ peer.expectBegin().respond();
+ peer.expectAttach().respond();
+ peer.remoteFlow().withLinkCredit(10).queue();
+ for (int i = 0; i < 10; ++i) {
+ peer.expectTransfer().withDeliveryId(i)
+ .withDeliveryTag(new byte[] {(byte) i})
+ .withMore(false)
+ .withPayload(payload);
+ }
+ peer.remoteDisposition().withSettled(true)
+ .withRole(Role.RECEIVER.getValue())
+ .withState().accepted()
+ .withFirst(1)
+ .withLast(3).queue();
+
+ Connection connection = engine.start().open();
+ Session session = connection.session().open();
+ Sender sender = session.sender("test");
+
+ final AtomicInteger dispositionCounter = new AtomicInteger();
+
+ final ArrayList<OutgoingDelivery> deliveries = new ArrayList<>();
+
+ sender.deliveryStateUpdatedHandler(delivery -> {
+ if (delivery.isRemotelySettled()) {
+ dispositionCounter.incrementAndGet();
+ deliveries.add(delivery);
+ delivery.settle();
+ }
+ });
+
+ sender.open();
+
+ for (int i = 0; i < 10; ++i) {
+ OutgoingDelivery delivery = sender.next();
+ delivery.setTag(new byte[] { (byte) i });
+ delivery.writeBytes(ProtonByteBufferAllocator.DEFAULT.wrap(payload));
+ }
+
+ peer.waitForScriptToComplete();
+ peer.expectDetach().respond();
+
+ assertEquals(7, sender.unsettled().size());
+
+ sender.close();
+
+ assertEquals(3, deliveries.size(), "Not all deliveries received dispositions");
+
+ byte deliveryTag = 1;
+
+ for (OutgoingDelivery delivery : deliveries) {
+ assertEquals(deliveryTag++, delivery.getTag().tagBuffer().getByte(0), "Delivery not updated in correct order");
+ assertTrue(delivery.isRemotelySettled(), "Delivery should be marked as remotely settled");
+ }
+
+ peer.waitForScriptToComplete();
+
+ assertNull(failure);
+ }
+
+ @Test
public void testSenderReportsIsSendableAfterOpenedIfRemoteSendsFlowBeforeLocallyOpened() throws Exception {
Engine engine = EngineFactory.PROTON.createNonSaslEngine();
engine.errorHandler(result -> failure = result.failureCause());
diff --git a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMapTest.java b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMapTest.java
index 5e59d3e..04a563f 100644
--- a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMapTest.java
+++ b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/LinkedSplayMapTest.java
@@ -112,6 +112,35 @@
*/
@Override
@Test
+ public void testNavigableKeysIterationFollowsUnsignedOrderingExpectations() {
+ LinkedSplayMap<String> map = createMap();
+
+ final int[] inputValues = {3, 0, -1, 1, -2, 2};
+ final int[] expectedOrder = {3, 0, -1, 1, -2, 2};
+
+ for (int entry : inputValues) {
+ map.put(entry, "" + entry);
+ }
+
+ Collection<UnsignedInteger> keys = map.navigableKeySet();
+ Iterator<UnsignedInteger> iterator = keys.iterator();
+ assertNotNull(iterator);
+ assertTrue(iterator.hasNext());
+
+ int counter = 0;
+ while (iterator.hasNext()) {
+ assertEquals(UnsignedInteger.valueOf(expectedOrder[counter++]), iterator.next());
+ }
+
+ // Check that we really did iterate.
+ assertEquals(inputValues.length, counter);
+ }
+
+ /**
+ * Test differs from parent as order is insertion based.
+ */
+ @Override
+ @Test
public void testEntryIterationFollowsUnsignedOrderingExpectations() {
LinkedSplayMap<String> map = createMap();
diff --git a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/SplayMapTest.java b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/SplayMapTest.java
index edb11b3..d819257 100644
--- a/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/SplayMapTest.java
+++ b/protonj2/src/test/java/org/apache/qpid/protonj2/engine/util/SplayMapTest.java
@@ -18,21 +18,28 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
+import java.util.SortedMap;
import org.apache.qpid.protonj2.logging.ProtonLogger;
import org.apache.qpid.protonj2.logging.ProtonLoggerFactory;
@@ -49,12 +56,22 @@
protected long seed;
protected Random random;
+ protected UnsignedInteger uintArray[] = new UnsignedInteger[1000];
+ protected String objArray[] = new String[1000];
+ protected SplayMap<String> testMap = new SplayMap<>();
@BeforeEach
public void setUp() {
seed = System.nanoTime();
random = new Random();
random.setSeed(seed);
+
+ testMap = new SplayMap<>();
+ for (int i = 0; i < objArray.length; i++) {
+ UnsignedInteger x = uintArray[i] = UnsignedInteger.valueOf(i);
+ String y = objArray[i] = UnsignedInteger.valueOf(i).toString();
+ testMap.put(x, y);
+ }
}
protected <E> SplayMap<E> createMap() {
@@ -123,6 +140,26 @@
}
@Test
+ public void testSizeWithSubMaps() {
+ assertEquals(1000, testMap.size(), "Returned incorrect size");
+ assertEquals(500, testMap.headMap(UnsignedInteger.valueOf(500)).size(), "Returned incorrect size");
+ assertEquals(0, testMap.headMap(UnsignedInteger.valueOf(0)).size(), "Returned incorrect size");
+ assertEquals(1, testMap.headMap(UnsignedInteger.valueOf(1)).size(), "Returned incorrect size");
+ assertEquals(501, testMap.headMap(UnsignedInteger.valueOf(501)).size(), "Returned incorrect size");
+ assertEquals(500, testMap.tailMap(UnsignedInteger.valueOf(500)).size(), "Returned incorrect size");
+ assertEquals(1000, testMap.tailMap(UnsignedInteger.valueOf(0)).size(), "Returned incorrect size");
+ assertEquals(500, testMap.tailMap(UnsignedInteger.valueOf(500)).size(), "Returned incorrect size");
+ assertEquals(100, testMap.subMap(UnsignedInteger.valueOf(500), UnsignedInteger.valueOf(600)).size(), "Returned incorrect size");
+
+ assertThrows(NullPointerException.class, () -> testMap.subMap(null, UnsignedInteger.valueOf(600)));
+ assertThrows(NullPointerException.class, () -> testMap.subMap(UnsignedInteger.valueOf(600), null));
+ assertThrows(NullPointerException.class, () -> testMap.subMap(null, null));
+ assertThrows(NullPointerException.class, () -> testMap.subMap(null, true, null, true));
+ assertThrows(NullPointerException.class, () -> testMap.headMap(null));
+ assertThrows(NullPointerException.class, () -> testMap.tailMap(null));
+ }
+
+ @Test
public void testInsert() {
SplayMap<String> map = createMap();
@@ -725,6 +762,27 @@
}
@Test
+ public void testKeysIterationRemoveContract() {
+ Set<UnsignedInteger> set = testMap.keySet();
+ Iterator<UnsignedInteger> iter = set.iterator();
+ iter.next();
+ iter.remove();
+
+ // No remove allowed again until next is called
+ assertThrows(IllegalStateException.class, () -> iter.remove());
+
+ iter.next();
+ iter.remove();
+
+ assertEquals(998, testMap.size());
+
+ iter.next();
+ assertNotNull(testMap.remove(999));
+
+ assertThrows(ConcurrentModificationException.class, () -> iter.remove());
+ }
+
+ @Test
public void testKeysIteration() {
SplayMap<String> map = createMap();
@@ -814,6 +872,194 @@
}
@Test
+ public void testKeySetRetainAllFromCollection() throws Exception {
+ final Collection<UnsignedInteger> collection = new ArrayList<>();
+ collection.add(UnsignedInteger.valueOf(200));
+
+ assertEquals(1000, testMap.size());
+
+ final Set<UnsignedInteger> keys = testMap.keySet();
+
+ keys.retainAll(collection);
+ assertEquals(1, testMap.size());
+ keys.removeAll(collection);
+ assertEquals(0, testMap.size());
+ testMap.put(1, "one");
+ assertEquals(1, testMap.size());
+ keys.clear();
+ assertEquals(0, testMap.size());
+ }
+
+ @Test
+ public void testNavigableKeySetReturned() {
+ SplayMap<String> map = createMap();
+
+ map.put(0, "zero");
+ map.put(1, "one");
+ map.put(2, "two");
+ map.put(3, "three");
+
+ Set<UnsignedInteger> keys = map.navigableKeySet();
+ assertNotNull(keys);
+ assertEquals(4, keys.size());
+ assertFalse(keys.isEmpty());
+ assertSame(keys, map.keySet());
+ }
+
+ @Test
+ public void testNavigableKeysIterationRemove() {
+ SplayMap<String> map = createMap();
+
+ final int[] intValues = {0, 1, 2, 3};
+
+ for (int entry : intValues) {
+ map.put(entry, "" + entry);
+ }
+
+ Collection<UnsignedInteger> keys = map.navigableKeySet();
+ Iterator<UnsignedInteger> iterator = keys.iterator();
+ assertNotNull(iterator);
+ assertTrue(iterator.hasNext());
+
+ int counter = 0;
+ while (iterator.hasNext()) {
+ assertEquals(UnsignedInteger.valueOf(intValues[counter++]), iterator.next());
+ }
+
+ // Check that we really did iterate.
+ assertEquals(intValues.length, counter);
+ }
+
+ @Test
+ public void testNavigableKeysIterationRemoveContract() {
+ Set<UnsignedInteger> set = testMap.navigableKeySet();
+ Iterator<UnsignedInteger> iter = set.iterator();
+ iter.next();
+ iter.remove();
+
+ // No remove allowed again until next is called
+ assertThrows(IllegalStateException.class, () -> iter.remove());
+
+ iter.next();
+ iter.remove();
+
+ assertEquals(998, testMap.size());
+
+ iter.next();
+ assertNotNull(testMap.remove(999));
+
+ assertThrows(ConcurrentModificationException.class, () -> iter.remove());
+ }
+
+ @Test
+ public void testNavigableKeysIteration() {
+ SplayMap<String> map = createMap();
+
+ final int[] intValues = {0, 1, 2, 3};
+
+ for (int entry : intValues) {
+ map.put(entry, "" + entry);
+ }
+
+ Collection<UnsignedInteger> keys = map.navigableKeySet();
+ Iterator<UnsignedInteger> iterator = keys.iterator();
+ assertNotNull(iterator);
+ assertTrue(iterator.hasNext());
+
+ int counter = 0;
+ while (iterator.hasNext()) {
+ assertEquals(UnsignedInteger.valueOf(intValues[counter++]), iterator.next());
+ iterator.remove();
+ }
+
+ // Check that we really did iterate.
+ assertEquals(intValues.length, counter);
+ assertTrue(map.isEmpty());
+ assertEquals(0, map.size());
+ }
+
+ @Test
+ public void testNavigableKeysIterationFollowsUnsignedOrderingExpectations() {
+ SplayMap<String> map = createMap();
+
+ final int[] inputValues = {3, 0, -1, 1, -2, 2};
+ final int[] expectedOrder = {0, 1, 2, 3, -2, -1};
+
+ for (int entry : inputValues) {
+ map.put(entry, "" + entry);
+ }
+
+ Collection<UnsignedInteger> keys = map.navigableKeySet();
+ Iterator<UnsignedInteger> iterator = keys.iterator();
+ assertNotNull(iterator);
+ assertTrue(iterator.hasNext());
+
+ int counter = 0;
+ while (iterator.hasNext()) {
+ assertEquals(UnsignedInteger.valueOf(expectedOrder[counter++]), iterator.next());
+ }
+
+ // Check that we really did iterate.
+ assertEquals(inputValues.length, counter);
+ }
+
+ @Test
+ public void testNavigableKeysIterationFailsWhenConcurrentlyModified() {
+ SplayMap<String> map = createMap();
+
+ final int[] inputValues = {3, 0, -1, 1, -2, 2};
+
+ for (int entry : inputValues) {
+ map.put(entry, "" + entry);
+ }
+
+ Collection<UnsignedInteger> keys = map.navigableKeySet();
+ Iterator<UnsignedInteger> iterator = keys.iterator();
+ assertNotNull(iterator);
+ assertTrue(iterator.hasNext());
+
+ map.remove(3);
+
+ try {
+ iterator.next();
+ fail("Should not iterate when modified outside of iterator");
+ } catch (ConcurrentModificationException cme) {}
+ }
+
+ @Test
+ public void testNavigableKeysIterationOnEmptyTree() {
+ SplayMap<String> map = createMap();
+ Collection<UnsignedInteger> keys = map.navigableKeySet();
+ Iterator<UnsignedInteger> iterator = keys.iterator();
+
+ assertFalse(iterator.hasNext());
+ try {
+ iterator.next();
+ fail("Should have thrown a NoSuchElementException");
+ } catch (NoSuchElementException nse) {
+ }
+ }
+
+ @Test
+ public void testNavigableKeySetRetainAllFromCollection() throws Exception {
+ final Collection<UnsignedInteger> collection = new ArrayList<>();
+ collection.add(UnsignedInteger.valueOf(200));
+
+ assertEquals(1000, testMap.size());
+
+ final Set<UnsignedInteger> keys = testMap.navigableKeySet();
+
+ keys.retainAll(collection);
+ assertEquals(1, testMap.size());
+ keys.removeAll(collection);
+ assertEquals(0, testMap.size());
+ testMap.put(1, "one");
+ assertEquals(1, testMap.size());
+ keys.clear();
+ assertEquals(0, testMap.size());
+ }
+
+ @Test
public void tesEntrySetReturned() {
SplayMap<String> map = createMap();
@@ -1040,6 +1286,10 @@
map.put(entry, "" + entry);
}
+ assertEquals(UnsignedInteger.valueOf(0), map.firstKey());
+ SortedMap<UnsignedInteger, String> descending = map.descendingMap();
+ assertEquals(UnsignedInteger.valueOf(-1), descending.firstKey());
+
for (int expected : expectedOrder) {
assertEquals(expected, map.pollFirstEntry().getPrimitiveKey());
}
@@ -1064,6 +1314,10 @@
map.put(entry, "" + entry);
}
+ assertEquals(UnsignedInteger.valueOf(-1), map.lastKey());
+ SortedMap<UnsignedInteger, String> descending = map.descendingMap();
+ assertEquals(UnsignedInteger.valueOf(0), descending.lastKey());
+
for (int expected : expectedOrder) {
assertEquals(expected, map.lastKey().intValue());
map.remove(expected);
@@ -1073,6 +1327,27 @@
}
@Test
+ public void testLastKeyAfterSubMap() {
+ SplayMap<String> tm = new SplayMap<String>();
+ tm.put(1, "VAL001");
+ tm.put(3, "VAL003");
+ tm.put(2, "VAL002");
+ SortedMap<UnsignedInteger, String> sm = tm;
+ UnsignedInteger firstKey = sm.firstKey();
+ UnsignedInteger lastKey = null;
+
+ for (int i = 1; i <= tm.size(); i++) {
+ try {
+ lastKey = sm.lastKey();
+ } catch (NoSuchElementException excep) {
+ assertTrue(sm.isEmpty());
+ fail("NoSuchElementException thrown when there are elements in the map");
+ }
+ sm = sm.subMap(firstKey, lastKey);
+ }
+ }
+
+ @Test
public void testLastEntryOnEmptyMap() {
SplayMap<String> map = createMap();
assertNull(map.lastEntry());
@@ -1139,6 +1414,28 @@
});
assertEquals(index.intValue(), inputValues.length);
+
+ NavigableMap<UnsignedInteger, String> descemding = map.descendingMap();
+
+ assertEquals(map.size(), descemding.size());
+
+ descemding.forEach((k, v) -> {
+ int value = index.decrement().intValue();
+ assertEquals(expectedOrder[value], k.intValue());
+ });
+
+ assertEquals(index.intValue(), 0);
+
+ NavigableMap<UnsignedInteger, String> ascending = descemding.descendingMap();
+
+ assertEquals(map.size(), ascending.size());
+
+ ascending.forEach((k, v) -> {
+ int value = index.getAndIncrement().intValue();
+ assertEquals(expectedOrder[value], k.intValue());
+ });
+
+ assertEquals(index.intValue(), expectedOrder.length);
}
@Test
@@ -1339,6 +1636,10 @@
assertEquals(UnsignedInteger.valueOf(2), map.floorEntry(UnsignedInteger.valueOf(2)).getKey());
assertEquals(UnsignedInteger.valueOf(1), map.floorEntry(UnsignedInteger.valueOf(1)).getKey());
assertEquals(UnsignedInteger.valueOf(0), map.floorEntry(UnsignedInteger.valueOf(0)).getKey());
+
+ map.remove(0);
+
+ assertNull(map.floorEntry(UnsignedInteger.valueOf(0)));
}
@Test
@@ -1360,6 +1661,10 @@
assertEquals(UnsignedInteger.valueOf(2), map.floorKey(UnsignedInteger.valueOf(2)));
assertEquals(UnsignedInteger.valueOf(1), map.floorKey(UnsignedInteger.valueOf(1)));
assertEquals(UnsignedInteger.valueOf(0), map.floorKey(UnsignedInteger.valueOf(0)));
+
+ map.remove(0);
+
+ assertNull(map.floorEntry(UnsignedInteger.valueOf(0)));
}
@Test
@@ -1381,6 +1686,10 @@
assertEquals(UnsignedInteger.valueOf(-2), map.ceilingEntry(UnsignedInteger.valueOf(-3)).getKey());
assertEquals(UnsignedInteger.valueOf(-2), map.ceilingEntry(UnsignedInteger.valueOf(-2)).getKey());
assertEquals(UnsignedInteger.valueOf(-1), map.ceilingEntry(UnsignedInteger.valueOf(-1)).getKey());
+
+ map.remove(-1);
+
+ assertNull(map.ceilingEntry(UnsignedInteger.valueOf(-1)));
}
@Test
@@ -1402,6 +1711,472 @@
assertEquals(UnsignedInteger.valueOf(-2), map.ceilingKey(UnsignedInteger.valueOf(-3)));
assertEquals(UnsignedInteger.valueOf(-2), map.ceilingKey(UnsignedInteger.valueOf(-2)));
assertEquals(UnsignedInteger.valueOf(-1), map.ceilingKey(UnsignedInteger.valueOf(-1)));
+
+ map.remove(-1);
+
+ assertNull(map.ceilingKey(UnsignedInteger.valueOf(-1)));
+ }
+
+ @Test
+ public void testHeadMapForBasicTestMap() {
+ Map<UnsignedInteger, String> head = testMap.headMap(UnsignedInteger.valueOf(99));
+ assertEquals(99, head.size(), "Returned map of incorrect size");
+ assertTrue(head.containsKey(UnsignedInteger.valueOf(0)));
+ assertTrue(head.containsValue("1"));
+ assertTrue(head.containsKey(UnsignedInteger.valueOf(10)));
+
+ SortedMap<UnsignedInteger, Integer> intMap;
+ SortedMap<UnsignedInteger, Integer> sub;
+
+ int size = 16;
+ intMap = new SplayMap<Integer>();
+ for (int i = 1; i <= size; i++) {
+ intMap.put(UnsignedInteger.valueOf(i), i);
+ }
+ sub = intMap.headMap(UnsignedInteger.valueOf(0));
+ assertEquals(sub.size(), 0, "size should be zero");
+ assertTrue(sub.isEmpty(), "The SubMap should be empty");
+ try {
+ sub.firstKey();
+ fail("java.util.NoSuchElementException should be thrown");
+ } catch (java.util.NoSuchElementException e) {
+ }
+
+ SplayMap<Integer> t = new SplayMap<Integer>();
+ try {
+ @SuppressWarnings("unused")
+ SortedMap<UnsignedInteger, Integer> th = t.headMap(null);
+ fail("Should throw a NullPointerException");
+ } catch (NullPointerException npe) {
+ // expected
+ }
+
+ try {
+ sub.lastKey();
+ fail("java.util.NoSuchElementException should be thrown");
+ } catch (java.util.NoSuchElementException e) {
+ }
+
+ size = 256;
+ intMap = new SplayMap<Integer>();
+ for (int i = 0; i < size; i++) {
+ intMap.put(UnsignedInteger.valueOf(i), i);
+ }
+ sub = intMap.headMap(UnsignedInteger.valueOf(0));
+ assertEquals(sub.size(), 0, "size should be zero");
+ assertTrue(sub.isEmpty(), "SubMap should be empty");
+ try {
+ sub.firstKey();
+ fail("java.util.NoSuchElementException should be thrown");
+ } catch (java.util.NoSuchElementException e) {
+ }
+
+ try {
+ sub.lastKey();
+ fail("java.util.NoSuchElementException should be thrown");
+ } catch (java.util.NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void testSubMapTwoArgVariant() {
+ SortedMap<UnsignedInteger, String> subMap = testMap.subMap(uintArray[100], uintArray[109]);
+
+ assertEquals(9, subMap.size(), "SubMap returned is of incorrect size");
+ for (int counter = 100; counter < 109; counter++) {
+ assertTrue(subMap.get(uintArray[counter]).equals(objArray[counter]), "SubMap contains incorrect elements");
+ }
+
+ assertThrows(IllegalArgumentException.class, () -> testMap.subMap(uintArray[9], uintArray[1]));
+
+ SortedMap<UnsignedInteger, String> map = new SplayMap<String>();
+ map.put(UnsignedInteger.valueOf(1), "one");
+ map.put(UnsignedInteger.valueOf(2), "two");
+ map.put(UnsignedInteger.valueOf(3), "three");
+ assertEquals(UnsignedInteger.valueOf(3), map.lastKey());
+ SortedMap<UnsignedInteger, String> sub = map.subMap(UnsignedInteger.valueOf(1), UnsignedInteger.valueOf(3));
+ assertEquals(UnsignedInteger.valueOf(2), sub.lastKey());
+
+ SortedMap<UnsignedInteger, String> t = new SplayMap<String>();
+ assertThrows(NullPointerException.class, () -> t.subMap(null, UnsignedInteger.valueOf(1)));
+ }
+
+ @Test
+ public void testSubMapIterator() {
+ SplayMap<String> map = new SplayMap<String>();
+
+ UnsignedInteger[] keys = { UnsignedInteger.valueOf(1), UnsignedInteger.valueOf(2), UnsignedInteger.valueOf(3) };
+ String[] values = { "one", "two", "three" };
+ for (int i = 0; i < keys.length; i++) {
+ map.put(keys[i], values[i]);
+ }
+
+ assertEquals(3, map.size());
+
+ SortedMap<UnsignedInteger, String> subMap = map.subMap(UnsignedInteger.valueOf(0), UnsignedInteger.valueOf(4));
+ assertEquals(3, subMap.size());
+
+ Set<Map.Entry<UnsignedInteger, String>> entrySet = subMap.entrySet();
+ Iterator<Map.Entry<UnsignedInteger, String>> iter = entrySet.iterator();
+ int size = 0;
+ while (iter.hasNext()) {
+ Map.Entry<UnsignedInteger, String> entry = iter.next();
+ assertTrue(map.containsKey(entry.getKey()));
+ assertTrue(map.containsValue(entry.getValue()));
+ size++;
+ }
+ assertEquals(map.size(), size);
+
+ Set<UnsignedInteger> keySet = subMap.keySet();
+ Iterator<UnsignedInteger> keyIter = keySet.iterator();
+ size = 0;
+ while (keyIter.hasNext()) {
+ UnsignedInteger key = keyIter.next();
+ assertTrue(map.containsKey(key));
+ size++;
+ }
+ assertEquals(map.size(), size);
+ }
+
+ @Test
+ public void testTailMapWithSingleArgument() {
+ SortedMap<UnsignedInteger, String> tail = testMap.tailMap(uintArray[900]);
+ assertEquals(tail.size(), (uintArray.length - 900), "Returned map of incorrect size : " + tail.size());
+ for (int i = 900; i < objArray.length; i++) {
+ assertTrue(tail.containsValue(objArray[i]), "Map contains incorrect entries");
+ }
+
+ SortedMap<UnsignedInteger, Integer> intMap;
+
+ int size = 16;
+ intMap = new SplayMap<Integer>();
+
+ for (int i = 0; i < size; i++) {
+ intMap.put(UnsignedInteger.valueOf(i), i);
+ }
+
+ final SortedMap<UnsignedInteger, Integer> sub = intMap.tailMap(UnsignedInteger.valueOf(size));
+ assertEquals(sub.size(),0, "size should be zero");
+ assertTrue(sub.isEmpty(), "SubMap should be empty");
+
+ assertThrows(NoSuchElementException.class, () -> sub.firstKey());
+ assertThrows(NullPointerException.class, () -> new SplayMap<String>().tailMap(null));
+ assertThrows(NoSuchElementException.class, () -> sub.lastKey());
+
+ // Try with larger more complex tree structure.
+
+ size = 256;
+ intMap = new SplayMap<Integer>();
+ for (int i = 0; i < size; i++) {
+ intMap.put(UnsignedInteger.valueOf(i), i);
+ }
+ final SortedMap<UnsignedInteger, Integer> sub1 = intMap.tailMap(UnsignedInteger.valueOf(size));
+
+ assertThrows(NoSuchElementException.class, () -> sub1.firstKey());
+ assertThrows(NullPointerException.class, () -> new SplayMap<String>().tailMap(null));
+ assertThrows(NoSuchElementException.class, () -> sub1.lastKey());
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Test
+ public void testNavigableKeySetOperationInteractions() throws Exception {
+ UnsignedInteger testint9999 = UnsignedInteger.valueOf(9999);
+ UnsignedInteger testint10000 = UnsignedInteger.valueOf(10000);
+ UnsignedInteger testint100 = UnsignedInteger.valueOf(100);
+ UnsignedInteger testint0 = UnsignedInteger.valueOf(0);
+
+ final NavigableSet untypedSet = testMap.navigableKeySet();
+ final NavigableSet<UnsignedInteger> set = testMap.navigableKeySet();
+
+ assertFalse(set.contains(testint9999));
+ testMap.put(testint9999, testint9999.toString());
+ assertTrue(set.contains(testint9999));
+ testMap.remove(testint9999);
+ assertFalse(set.contains(testint9999));
+
+ assertThrows(UnsupportedOperationException.class, () -> untypedSet.add(new Object()));
+ assertThrows(UnsupportedOperationException.class, () -> untypedSet.add(null));
+ assertThrows(NullPointerException.class, () -> untypedSet.addAll(null));
+
+ final Collection collection = new ArrayList();
+ set.addAll(collection);
+ try {
+ collection.add(new Object());
+ set.addAll(collection);
+ fail("should throw UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ set.remove(testint100);
+ assertFalse(testMap.containsKey(testint100));
+ assertTrue(testMap.containsKey(testint0));
+
+ final Iterator iter = set.iterator();
+ iter.next();
+ iter.remove();
+ assertFalse(testMap.containsKey(testint0));
+ collection.add(UnsignedInteger.valueOf(2));
+ set.retainAll(collection);
+ assertEquals(1, testMap.size());
+ set.removeAll(collection);
+ assertEquals(0, testMap.size());
+ testMap.put(testint10000, testint10000.toString());
+ assertEquals(1, testMap.size());
+ set.clear();
+ assertEquals(0, testMap.size());
+ }
+
+ @Test
+ public void testEmptySubMap() throws Exception {
+ SplayMap<List<Integer>> tm = new SplayMap<>();
+ SortedMap<UnsignedInteger, List<Integer>> sm = tm.tailMap(UnsignedInteger.valueOf(1));
+ assertTrue(sm.values().size() == 0);
+
+ NavigableMap<UnsignedInteger, List<Integer>> sm1 = tm.descendingMap();
+ assertTrue(sm1.values().size() == 0);
+
+ NavigableMap<UnsignedInteger, List<Integer>> sm2 = sm1.tailMap(UnsignedInteger.valueOf(1), true);
+ assertTrue(sm2.values().size() == 0);
+ }
+
+ @Test
+ public void testValuesOneEntrySubMap() {
+ SplayMap<String> tm = new SplayMap<String>();
+ tm.put(1, "VAL001");
+ tm.put(3, "VAL003");
+ tm.put(2, "VAL002");
+
+ UnsignedInteger firstKey = tm.firstKey();
+ SortedMap<UnsignedInteger, String> subMap = tm.subMap(firstKey, firstKey);
+ Iterator<String> iter = subMap.values().iterator();
+ assertNotNull(iter);
+ }
+
+ @Test
+ public void testDescendingMapSubMap() throws Exception {
+ SplayMap<Object> tm = new SplayMap<>();
+ for (int i = 0; i < 10; ++i) {
+ tm.put(i, new Object());
+ }
+
+ NavigableMap<UnsignedInteger, Object> descMap = tm.descendingMap();
+ assertEquals(7, descMap.subMap(UnsignedInteger.valueOf(8), true, UnsignedInteger.valueOf(1), false).size());
+ assertEquals(4, descMap.headMap(UnsignedInteger.valueOf(6), true).size());
+ assertEquals(2, descMap.tailMap(UnsignedInteger.valueOf(2), false).size());
+
+ // sub map of sub map of descendingMap
+ NavigableMap<UnsignedInteger, Object> mapUIntObj = new SplayMap<Object>();
+ for (int i = 0; i < 10; ++i) {
+ mapUIntObj.put(UnsignedInteger.valueOf(i), new Object());
+ }
+ mapUIntObj = mapUIntObj.descendingMap();
+ NavigableMap<UnsignedInteger, Object> subMapUIntObj =
+ mapUIntObj.subMap(UnsignedInteger.valueOf(9), true, UnsignedInteger.valueOf(5), false);
+
+ assertEquals(4, subMapUIntObj.size());
+ subMapUIntObj = subMapUIntObj.subMap(UnsignedInteger.valueOf(9), true, UnsignedInteger.valueOf(5), false);
+ assertEquals(4, subMapUIntObj.size());
+ subMapUIntObj = subMapUIntObj.subMap(UnsignedInteger.valueOf(6), false, UnsignedInteger.valueOf(5), false);
+ assertEquals(0, subMapUIntObj.size());
+
+ subMapUIntObj = mapUIntObj.headMap(UnsignedInteger.valueOf(5), false);
+ assertEquals(4, subMapUIntObj.size());
+ subMapUIntObj = subMapUIntObj.headMap(UnsignedInteger.valueOf(5), false);
+ assertEquals(4, subMapUIntObj.size());
+ subMapUIntObj = subMapUIntObj.tailMap(UnsignedInteger.valueOf(5), false);
+ assertEquals(0, subMapUIntObj.size());
+
+ subMapUIntObj = mapUIntObj.tailMap(UnsignedInteger.valueOf(5), false);
+ assertEquals(5, subMapUIntObj.size());
+ subMapUIntObj = subMapUIntObj.tailMap(UnsignedInteger.valueOf(5), false);
+ assertEquals(5, subMapUIntObj.size());
+ subMapUIntObj = subMapUIntObj.headMap(UnsignedInteger.valueOf(5), false);
+ assertEquals(0, subMapUIntObj.size());
+ }
+
+ @Test
+ public void testEqualsJDKMapTypes() throws Exception {
+ // comparing SplayMap with different object types
+ Map<UnsignedInteger, String> m1 = new SplayMap<>();
+ Map<UnsignedInteger, String> m2 = new SplayMap<>();
+
+ m1.put(UnsignedInteger.valueOf(1), "val1");
+ m1.put(UnsignedInteger.valueOf(2), "val2");
+ m2.put(UnsignedInteger.valueOf(3), "val1");
+ m2.put(UnsignedInteger.valueOf(4), "val2");
+
+ assertNotEquals(m1, m2, "Maps should not be equal 1");
+ assertNotEquals(m2, m1, "Maps should not be equal 2");
+
+ // comparing SplayMap with HashMap with equal values
+ m1 = new SplayMap<>();
+ m2 = new HashMap<>();
+ m1.put(UnsignedInteger.valueOf(1), "val");
+ m2.put(UnsignedInteger.valueOf(2), "val");
+ assertNotEquals(m1, m2, "Maps should not be equal 3");
+ assertNotEquals(m2, m1, "Maps should not be equal 4");
+
+ // comparing SplayMap with differing objects inside values
+ m1 = new SplayMap<>();
+ m2 = new SplayMap<>();
+ m1.put(UnsignedInteger.valueOf(1), "val1");
+ m2.put(UnsignedInteger.valueOf(1), "val2");
+ assertNotEquals(m1, m2, "Maps should not be equal 5");
+ assertNotEquals(m2, m1, "Maps should not be equal 6");
+
+ // comparing SplayMap with same objects inside values
+ m1 = new SplayMap<>();
+ m2 = new SplayMap<>();
+ m1.put(UnsignedInteger.valueOf(1), "val1");
+ m2.put(UnsignedInteger.valueOf(1), "val1");
+ assertTrue(m1.equals(m2), "Maps should be equal 7");
+ assertTrue(m2.equals(m1), "Maps should be equal 7");
+ }
+
+ @Test
+ public void testEntrySetContains() throws Exception {
+ SplayMap<String> first = new SplayMap<>();
+ SplayMap<String> second = new SplayMap<>();
+
+ first.put(UnsignedInteger.valueOf(1), "one");
+ Object[] entry = first.entrySet().toArray();
+ assertFalse(second.entrySet().contains(entry[0]),
+ "Empty map should not contain anything from first map");
+
+ Map<UnsignedInteger, String> submap = second.subMap(UnsignedInteger.valueOf(0), UnsignedInteger.valueOf(1));
+ entry = first.entrySet().toArray();
+ assertFalse(submap.entrySet().contains(entry[0]),
+ "Empty SubMap should not contain the first map's entry");
+
+ second.put(UnsignedInteger.valueOf(1), "one");
+ assertTrue(second.entrySet().containsAll(first.entrySet()),
+ "entrySet().containsAll(...) should work with values");
+
+ first.clear();
+ first.put(UnsignedInteger.valueOf(1), "two");
+ entry = first.entrySet().toArray();
+ assertFalse(second.entrySet().contains(entry[0]),
+ "new valued entry should not equal old valued entry");
+ }
+
+ @Test
+ public void testValues() {
+ Collection<String> vals = testMap.values();
+ vals.iterator();
+ assertEquals(vals.size(), objArray.length, "Returned collection of incorrect size");
+ for (String element : objArray) {
+ assertTrue(vals.contains(element), "Collection contains incorrect elements");
+ }
+
+ assertEquals(1000, vals.size());
+ int j = 0;
+ for (Iterator<String> iter = vals.iterator(); iter.hasNext(); j++) {
+ String element = iter.next();
+ assertNotNull(element);
+ }
+ assertEquals(1000, j);
+
+ vals = testMap.descendingMap().values();
+ assertNotNull(vals.iterator());
+ assertEquals(vals.size(), objArray.length, "Returned collection of incorrect size");
+ for (String element : objArray) {
+ assertTrue(vals.contains(element), "Collection contains incorrect elements");
+ }
+ assertEquals(1000, vals.size());
+ j = 0;
+ for (Iterator<String> iter = vals.iterator(); iter.hasNext(); j++) {
+ String element = iter.next();
+ assertNotNull(element);
+ }
+ assertEquals(1000, j);
+
+ SplayMap<String> myMap = new SplayMap<String>();
+ for (int i = 0; i < 100; i++) {
+ myMap.put(uintArray[i], objArray[i]);
+ }
+ Collection<String> values = myMap.values();
+ values.remove(UnsignedInteger.ZERO.toString());
+ assertTrue(!myMap.containsKey(UnsignedInteger.ZERO), "Removing from the values collection should remove from the original map");
+ assertTrue(!myMap.containsValue(UnsignedInteger.ZERO.toString()), "Removing from the values collection should remove from the original map");
+ assertEquals(99, values.size());
+ j = 0;
+ for (Iterator<String> iter = values.iterator(); iter.hasNext(); j++) {
+ iter.next();
+ }
+ assertEquals(99, j);
+ }
+
+ @Test
+ public void testSubMapValuesSizeMetrics() {
+ SplayMap<String> myMap = new SplayMap<>();
+ for (int i = 0; i < 1000; i++) {
+ myMap.put(i, objArray[i]);
+ }
+
+ // Test for method values() in subMaps
+ Collection<String> vals = myMap.subMap(UnsignedInteger.valueOf(200), UnsignedInteger.valueOf(400)).values();
+ assertEquals(200, vals.size(), "Returned collection of incorrect size");
+ for (int i = 200; i < 400; i++) {
+ assertTrue(vals.contains(objArray[i]), "Collection contains incorrect elements" + i);
+ }
+ assertEquals(200, vals.toArray().length);
+ vals.remove(objArray[300]);
+ assertTrue(!myMap.containsValue(objArray[300]),
+ "Removing from the values collection should remove from the original map");
+ assertTrue(vals.size() == 199, "Returned collection of incorrect size");
+ assertEquals(199, vals.toArray().length);
+
+ myMap.put(300, objArray[300]);
+ // Test for method values() in subMaps
+ vals = myMap.headMap(UnsignedInteger.valueOf(400)).values();
+ assertEquals(vals.size(), 400, "Returned collection of incorrect size");
+ for (int i = 0; i < 400; i++) {
+ assertTrue(vals.contains(objArray[i]), "Collection contains incorrect elements " + i);
+ }
+ assertEquals(400,vals.toArray().length);
+ vals.remove(objArray[300]);
+ assertTrue(!myMap.containsValue(objArray[300]), "Removing from the values collection should remove from the original map");
+ assertEquals(vals.size(), 399, "Returned collection of incorrect size");
+ assertEquals(399, vals.toArray().length);
+
+ myMap.put(300, objArray[300]);
+ // Test for method values() in subMaps
+ vals = myMap.tailMap(UnsignedInteger.valueOf(400)).values();
+ assertEquals(vals.size(), 600, "Returned collection of incorrect size");
+ for (int i = 400; i < 1000; i++) {
+ assertTrue(vals.contains(objArray[i]), "Collection contains incorrect elements " + i);
+ }
+ assertEquals(600, vals.toArray().length);
+ vals.remove(objArray[600]);
+ assertTrue(!myMap.containsValue(objArray[600]), "Removing from the values collection should remove from the original map");
+ assertEquals(vals.size(), 599, "Returned collection of incorrect size");
+ assertEquals(599,vals.toArray().length);
+
+ myMap.put(600, objArray[600]);
+ // Test for method values() in subMaps
+ vals = myMap.descendingMap().headMap(UnsignedInteger.valueOf(400)).values();
+ assertEquals(vals.size(), 599, "Returned collection of incorrect size");
+ for (int i = 401; i < 1000; i++) {
+ assertTrue(vals.contains(objArray[i]), "Collection contains incorrect elements " + i);
+ }
+ assertEquals(599,vals.toArray().length);
+ vals.remove(objArray[600]);
+ assertTrue(!myMap.containsValue(objArray[600]), "Removing from the values collection should remove from the original map");
+ assertEquals(vals.size(), 598, "Returned collection of incorrect size");
+ assertEquals(598,vals.toArray().length);
+
+ myMap.put(600, objArray[600]);
+ // Test for method values() in subMaps
+ vals = myMap.descendingMap().tailMap(UnsignedInteger.valueOf(400)).values();
+ assertEquals(vals.size(), 401, "Returned collection of incorrect size");
+ for (int i = 0; i <= 400; i++) {
+ assertTrue(vals.contains(objArray[i]), "Collection contains incorrect elements " + i);
+ }
+ assertEquals(401, vals.toArray().length);
+ vals.remove(objArray[300]);
+ assertTrue(!myMap.containsValue(objArray[300]), "Removing from the values collection should remove from the original map");
+ assertEquals(vals.size(), 400, "Returned collection of incorrect size");
+ assertEquals(400,vals.toArray().length);
}
protected void dumpRandomDataSet(int iterations, boolean bounded) {