This is an automated email from the ASF dual-hosted git repository. tabish pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-protonj2.git
The following commit(s) were added to refs/heads/main by this push: new 8e388844 PROTON-2575 Add sub map and navigable map APIs to splay map 8e388844 is described below commit 8e388844dba5030e1c988dcf3e252a8692d40f1d Author: Timothy Bish <tabish...@gmail.com> AuthorDate: Fri Jul 8 19:11:20 2022 -0400 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. --- .../engine/impl/ProtonSessionIncomingWindow.java | 43 +- .../engine/impl/ProtonSessionOutgoingWindow.java | 43 +- .../qpid/protonj2/engine/util/LinkedSplayMap.java | 34 +- .../apache/qpid/protonj2/engine/util/SplayMap.java | 1536 +++++++++++++++++++- .../protonj2/engine/impl/ProtonSenderTest.java | 69 + .../protonj2/engine/util/LinkedSplayMapTest.java | 29 + .../qpid/protonj2/engine/util/SplayMapTest.java | 775 ++++++++++ 7 files changed, 2399 insertions(+), 130 deletions(-) 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 ea6f28ab..5e43a56c 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 @@ public class ProtonSessionIncomingWindow { 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 @@ public class ProtonSessionIncomingWindow { return disposition; } - private void handleRangedDisposition(Disposition disposition) { - final int first = (int) disposition.getFirst(); - final int last = (int) disposition.getLast(); - final boolean settled = disposition.getSettled(); + 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); + } + } - int index = first; - ProtonIncomingDelivery delivery; + private static void handleDispositions(Map<UnsignedInteger, ProtonIncomingDelivery> deliveries, Disposition disposition) { + final boolean settled = disposition.getSettled(); - // 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); + Iterator<ProtonIncomingDelivery> deliveriesIter = deliveries.values().iterator(); + while (deliveriesIter.hasNext()) { + ProtonIncomingDelivery delivery = deliveriesIter.next(); - 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 0c1bcaa7..8fc58548 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 @@ public class ProtonSessionOutgoingWindow { 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 @@ public class ProtonSessionOutgoingWindow { return disposition; } - private void handleRangedDisposition(Disposition disposition) { - final int first = (int) disposition.getFirst(); - final int last = (int) disposition.getLast(); - final boolean settled = disposition.getSettled(); + private static void handleRangedDisposition(NavigableMap<UnsignedInteger, ProtonOutgoingDelivery> unsettled, Disposition disposition) { + final UnsignedInteger first = UnsignedInteger.valueOf(disposition.getFirst()); + final UnsignedInteger last = UnsignedInteger.valueOf(disposition.getLast()); - int index = first; - ProtonOutgoingDelivery delivery; + // 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); + } + } - // 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); + private static void handleDispositions(Map<UnsignedInteger, ProtonOutgoingDelivery> deliveries, Disposition disposition) { + final boolean settled = disposition.getSettled(); - if (delivery != null) { - delivery.getLink().remoteDisposition(disposition, delivery); + Iterator<ProtonOutgoingDelivery> deliveriesIter = deliveries.values().iterator(); + while (deliveriesIter.hasNext()) { + ProtonOutgoingDelivery delivery = deliveriesIter.next(); + + 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 0d8e698f..69f8dfff 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.Collection; 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; @@ -51,14 +52,6 @@ public class LinkedSplayMap<E> extends SplayMap<E> { entries.linkNext = entries.linkPrev = entries; } - @Override - public Set<UnsignedInteger> keySet() { - if (keySet == null) { - keySet = new LinkedSplayMapKeySet(); - } - return keySet; - } - @Override public Collection<E> values() { if (values == null) { @@ -75,6 +68,14 @@ public class LinkedSplayMap<E> extends SplayMap<E> { return entrySet; } + @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,23 +286,13 @@ public class LinkedSplayMap<E> extends SplayMap<E> { } } - private final class LinkedSplayMapKeySet extends AbstractSet<UnsignedInteger> { + private final class LinkedSplayMapKeySet extends SplayMapKeySet { @Override public Iterator<UnsignedInteger> iterator() { return new LinkedSplayMapKeyIterator(entries.linkNext); } - @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 @@ public class LinkedSplayMap<E> extends SplayMap<E> { 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 6e8d97d7..7cffd291 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.NoSuchElementException; 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 @@ import org.apache.qpid.protonj2.types.UnsignedInteger; */ 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); @@ -105,6 +109,31 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } + /** + * 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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. @@ -292,11 +316,21 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { return put(key.intValue(), value); } + @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { return false; } + @Override + public int hashCode() { + int hash = 0; + for (SplayedEntry<E> entry = firstEntry(root); entry != null; entry = successor(entry)) { + hash += entry.hashCode(); + } + return hash; + } + + @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 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. + // 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 Set<UnsignedInteger> keySet; + 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { * */ - 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { * 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } - 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } + /** + * 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { // 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } + 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } - private final class SplayMapKeySet extends AbstractSet<UnsignedInteger> { + protected class SplayMapKeySet extends AbstractSet<UnsignedInteger> implements NavigableSet<UnsignedInteger> { @Override public Iterator<UnsignedInteger> iterator() { @@ -909,6 +1024,11 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { return SplayMap.this.size; } + @Override + public boolean isEmpty() { + return SplayMap.this.size == 0; + } + @Override public boolean contains(Object o) { return SplayMap.this.containsKey(o); @@ -925,10 +1045,133 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { return false; } + @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { * 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } - 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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } } + protected static Comparator<? super UnsignedInteger> reverseComparator() { + return REVERSE_COMPARATOR; + } + //----- Navigable and Sorted Map implementation methods @Override @@ -1136,17 +1381,17 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } @Override - public ImmutableSplayMapEntry pollLastEntry() { + public ImmutableSplayMapEntry<E> pollLastEntry() { SplayedEntry<E> lastEntry = lastEntry(root); if (lastEntry != null) { delete(lastEntry); @@ -1164,23 +1409,37 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { } @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 @@ public class SplayMap<E> implements NavigableMap<UnsignedInteger, E> { @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 66d44bdf..6f3cbcf9 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 @@ -4157,6 +4157,75 @@ public class ProtonSenderTest extends ProtonEngineTestSupport { assertNull(failure); } + @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(); 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 5e59d3e5..04a563f2 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 @@ -107,6 +107,35 @@ public class LinkedSplayMapTest extends SplayMapTest { assertEquals(inputValues.length, counter); } + /** + * Test differs from parent as order is insertion based. + */ + @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. */ 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 edb11b3f..d8192570 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 @@ package org.apache.qpid.protonj2.engine.util; 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 @@ public class SplayMapTest { 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() { @@ -122,6 +139,26 @@ public class SplayMapTest { assertEquals(0, map.size()); } + @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(); @@ -724,6 +761,27 @@ public class SplayMapTest { assertEquals(intValues.length, counter); } + @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(); @@ -813,6 +871,194 @@ public class SplayMapTest { } } + @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 @@ public class SplayMapTest { 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 @@ public class SplayMapTest { 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); @@ -1072,6 +1326,27 @@ public class SplayMapTest { assertNull(map.lastKey()); } + @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(); @@ -1139,6 +1414,28 @@ public class SplayMapTest { }); 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 @@ public class SplayMapTest { 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 @@ public class SplayMapTest { 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 @@ public class SplayMapTest { 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 @@ public class SplayMapTest { 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) { --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org