summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorJesse Wilson <jessewilson@google.com>2010-04-05 23:27:51 -0700
committerJesse Wilson <jessewilson@google.com>2010-04-15 14:02:26 -0700
commit984dc62f58d1f9611ebccc2598f714c15242a6eb (patch)
tree19465a8f78391ad848b0682650846b1b6413a672 /luni
parentd4ade6970a3dfd471cd4f8b233c7f0e7528cd3c4 (diff)
downloadlibcore-984dc62f58d1f9611ebccc2598f714c15242a6eb.zip
libcore-984dc62f58d1f9611ebccc2598f714c15242a6eb.tar.gz
libcore-984dc62f58d1f9611ebccc2598f714c15242a6eb.tar.bz2
From scratch implementation of a Navigable TreeMap.
Between Java 5 and Java 6, Harmony's implementation ballooned from ~2400 lines to ~5900 lines. This implementation is a more compact ~1700 lines. This implementation and its views have been rigorously tested using Google Collections' test framework in addition to the tests of our own suite.
Diffstat (limited to 'luni')
-rw-r--r--luni/src/main/java/java/util/AbstractMap.java375
-rw-r--r--luni/src/main/java/java/util/TreeMap.java3424
-rw-r--r--luni/src/main/java/java/util/TreeSet.java9
-rw-r--r--luni/src/test/java/java/util/AllTests.java1
-rw-r--r--luni/src/test/java/java/util/SerializableTester.java98
-rw-r--r--luni/src/test/java/java/util/TreeMapTest.java274
6 files changed, 1810 insertions, 2371 deletions
diff --git a/luni/src/main/java/java/util/AbstractMap.java b/luni/src/main/java/java/util/AbstractMap.java
index 86b4740..4789b14 100644
--- a/luni/src/main/java/java/util/AbstractMap.java
+++ b/luni/src/main/java/java/util/AbstractMap.java
@@ -20,9 +20,15 @@ package java.util;
import java.io.Serializable;
/**
- * This class is an abstract implementation of the {@code Map} interface. This
- * implementation does not support adding. A subclass must implement the
- * abstract method entrySet().
+ * A base class for {@code Map} implementations.
+ *
+ * <p>Subclasses that permit new mappings to be added must override {@link
+ * #put}.
+ *
+ * <p>The default implementations of many methods are inefficient for large
+ * maps. For example in the default implementation, each call to {@link #get}
+ * performs a linear iteration of the entry set. Subclasses should override such
+ * methods to improve their performance.
*
* @since 1.2
*/
@@ -34,89 +40,48 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
Collection<V> valuesCollection;
/**
- * An immutable key-value mapping.
- *
- * @param <K>
- * the type of key
- * @param <V>
- * the type of value
+ * An immutable key-value mapping. Despite the name, this class is non-final
+ * and its subclasses may be mutable.
*
* @since 1.6
*/
- public static class SimpleImmutableEntry<K, V> implements Map.Entry<K, V>,
- Serializable {
-
+ public static class SimpleImmutableEntry<K, V>
+ implements Map.Entry<K, V>, Serializable {
private static final long serialVersionUID = 7138329143949025153L;
- private K key;
-
- private V value;
+ private final K key;
+ private final V value;
- /**
- * Constructs a new instance by key and value.
- *
- * @param theKey
- * the key
- * @param theValue
- * the value
- */
public SimpleImmutableEntry(K theKey, V theValue) {
key = theKey;
value = theValue;
}
/**
- * Constructs a new instance by an entry
- *
- * @param entry
- * the entry
+ * Constructs an instance with the key and value of {@code copyFrom}.
*/
- public SimpleImmutableEntry(Map.Entry<? extends K, ? extends V> entry) {
- key = entry.getKey();
- value = entry.getValue();
+ public SimpleImmutableEntry(Map.Entry<? extends K, ? extends V> copyFrom) {
+ key = copyFrom.getKey();
+ value = copyFrom.getValue();
}
- /**
- * {@inheritDoc}
- *
- * @see java.util.Map.Entry#getKey()
- */
public K getKey() {
return key;
}
- /**
- * {@inheritDoc}
- *
- * @see java.util.Map.Entry#getValue()
- */
public V getValue() {
return value;
}
/**
- * Throws an UnsupportedOperationException.
- *
- * @param object
- * new value
- * @return (Does not)
- * @throws UnsupportedOperationException
- * always
- *
- * @see java.util.Map.Entry#setValue(java.lang.Object)
+ * This base implementation throws {@code UnsupportedOperationException}
+ * always.
*/
public V setValue(V object) {
throw new UnsupportedOperationException();
}
- /**
- * Returns whether the object is equal to this entry. This works across
- * all kinds of the Map.Entry interface.
- *
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object object) {
+ @Override public boolean equals(Object object) {
if (this == object) {
return true;
}
@@ -130,108 +95,56 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
return false;
}
- /**
- * Returns the hash code of this entry.
- *
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
+ @Override public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
- /**
- * Returns a String representation of this entry.
- *
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return key + "=" + value; //$NON-NLS-1$
+ @Override public String toString() {
+ return key + "=" + value;
}
}
/**
- * A key-value mapping.
- *
- * @param <K>
- * the type of key
- * @param <V>
- * the type of value
+ * A key-value mapping with mutable values.
*
* @since 1.6
*/
- public static class SimpleEntry<K, V> implements Map.Entry<K, V>,
- Serializable {
-
+ public static class SimpleEntry<K, V>
+ implements Map.Entry<K, V>, Serializable {
private static final long serialVersionUID = -8499721149061103585L;
- private K key;
-
+ private final K key;
private V value;
- /**
- * Constructs a new instance by key and value.
- *
- * @param theKey
- * the key
- * @param theValue
- * the value
- */
public SimpleEntry(K theKey, V theValue) {
key = theKey;
value = theValue;
}
/**
- * Constructs a new instance by an entry
- *
- * @param entry
- * the entry
+ * Constructs an instance with the key and value of {@code copyFrom}.
*/
- public SimpleEntry(Map.Entry<? extends K, ? extends V> entry) {
- key = entry.getKey();
- value = entry.getValue();
+ public SimpleEntry(Map.Entry<? extends K, ? extends V> copyFrom) {
+ key = copyFrom.getKey();
+ value = copyFrom.getValue();
}
- /**
- * {@inheritDoc}
- *
- * @see java.util.Map.Entry#getKey()
- */
public K getKey() {
return key;
}
- /**
- * {@inheritDoc}
- *
- * @see java.util.Map.Entry#getValue()
- */
public V getValue() {
return value;
}
- /**
- * {@inheritDoc}
- *
- * @see java.util.Map.Entry#setValue(java.lang.Object)
- */
public V setValue(V object) {
V result = value;
value = object;
return result;
}
- /**
- * Returns whether the object is equal to this entry. This works across
- * all kinds of the Map.Entry interface.
- *
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object object) {
+ @Override public boolean equals(Object object) {
if (this == object) {
return true;
}
@@ -245,54 +158,34 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
return false;
}
- /**
- * Returns the hash code of this entry.
- *
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
+ @Override public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
- /**
- * Returns a String representation of this entry.
- *
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return key + "=" + value; //$NON-NLS-1$
+ @Override public String toString() {
+ return key + "=" + value;
}
}
- /**
- * Constructs a new instance of this {@code AbstractMap}.
- */
protected AbstractMap() {
super();
}
/**
- * Removes all elements from this map, leaving it empty.
+ * {@inheritDoc}
*
- * @throws UnsupportedOperationException
- * if removing from this map is not supported.
- * @see #isEmpty()
- * @see #size()
+ * <p>This implementation calls {@code entrySet().clear()}.
*/
public void clear() {
entrySet().clear();
}
/**
- * Returns whether this map contains the specified key.
+ * {@inheritDoc}
*
- * @param key
- * the key to search for.
- * @return {@code true} if this map contains the specified key,
- * {@code false} otherwise.
+ * <p>This implementation iterates its key set, looking for a key that
+ * {@code key} equals.
*/
public boolean containsKey(Object key) {
Iterator<Map.Entry<K, V>> it = entrySet().iterator();
@@ -313,12 +206,10 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Returns whether this map contains the specified value.
+ * {@inheritDoc}
*
- * @param value
- * the value to search for.
- * @return {@code true} if this map contains the specified value,
- * {@code false} otherwise.
+ * <p>This implementation iterates its entry set, looking for an entry with
+ * a value that {@code value} equals.
*/
public boolean containsValue(Object value) {
Iterator<Map.Entry<K, V>> it = entrySet().iterator();
@@ -338,28 +229,18 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
return false;
}
- /**
- * Returns a set containing all of the mappings in this map. Each mapping is
- * an instance of {@link Map.Entry}. As the set is backed by this map,
- * changes in one will be reflected in the other.
- *
- * @return a set of the mappings.
- */
public abstract Set<Map.Entry<K, V>> entrySet();
/**
- * Compares the specified object to this instance, and returns {@code true}
- * if the specified object is a map and both maps contain the same mappings.
+ * {@inheritDoc}
*
- * @param object
- * the object to compare with this object.
- * @return boolean {@code true} if the object is the same as this object,
- * and {@code false} if it is different from this object.
- * @see #hashCode()
- * @see #entrySet()
+ * <p>This implementation first checks the structure of {@code object}. If
+ * it is not a map or of a different size, this returns false. Otherwise it
+ * iterates its own entry set, looking up each entry's key in {@code
+ * object}. If any value does not equal the other map's value for the same
+ * key, this returns false. Otherwise it returns true.
*/
- @Override
- public boolean equals(Object object) {
+ @Override public boolean equals(Object object) {
if (this == object) {
return true;
}
@@ -393,12 +274,10 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Returns the value of the mapping with the specified key.
+ * {@inheritDoc}
*
- * @param key
- * the key.
- * @return the value of the mapping with the specified key, or {@code null}
- * if no mapping for the specified key is found.
+ * <p>This implementation iterates its entry set, looking for an entry with
+ * a key that {@code key} equals.
*/
public V get(Object key) {
Iterator<Map.Entry<K, V>> it = entrySet().iterator();
@@ -421,14 +300,12 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Returns the hash code for this object. Objects which are equal must
- * return the same value for this method.
+ * {@inheritDoc}
*
- * @return the hash code of this object.
- * @see #equals(Object)
+ * <p>This implementation iterates its entry set, summing the hashcodes of
+ * its entries.
*/
- @Override
- public int hashCode() {
+ @Override public int hashCode() {
int result = 0;
Iterator<Map.Entry<K, V>> it = entrySet().iterator();
while (it.hasNext()) {
@@ -438,41 +315,34 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Returns whether this map is empty.
+ * {@inheritDoc}
*
- * @return {@code true} if this map has no elements, {@code false}
- * otherwise.
- * @see #size()
+ * <p>This implementation compares {@code size()} to 0.
*/
public boolean isEmpty() {
return size() == 0;
}
/**
- * Returns a set of the keys contained in this map. The set is backed by
- * this map so changes to one are reflected by the other. The returned set
- * does not support adding.
+ * {@inheritDoc}
*
- * @return a set of the keys.
+ * <p>This implementation returns a view that calls through this to map. Its
+ * iterator transforms this map's entry set iterator to return keys.
*/
public Set<K> keySet() {
if (keySet == null) {
keySet = new AbstractSet<K>() {
- @Override
- public boolean contains(Object object) {
+ @Override public boolean contains(Object object) {
return containsKey(object);
}
- @Override
- public int size() {
+ @Override public int size() {
return AbstractMap.this.size();
}
- @Override
- public Iterator<K> iterator() {
+ @Override public Iterator<K> iterator() {
return new Iterator<K>() {
- Iterator<Map.Entry<K, V>> setIterator = entrySet()
- .iterator();
+ Iterator<Map.Entry<K, V>> setIterator = entrySet().iterator();
public boolean hasNext() {
return setIterator.hasNext();
@@ -493,44 +363,19 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Maps the specified key to the specified value.
+ * {@inheritDoc}
*
- * @param key
- * the key.
- * @param value
- * the value.
- * @return the value of any previous mapping with the specified key or
- * {@code null} if there was no mapping.
- * @throws UnsupportedOperationException
- * if adding to this map is not supported.
- * @throws ClassCastException
- * if the class of the key or value is inappropriate for this
- * map.
- * @throws IllegalArgumentException
- * if the key or value cannot be added to this map.
- * @throws NullPointerException
- * if the key or value is {@code null} and this Map does not
- * support {@code null} keys or values.
+ * <p>This base implementation throws {@code UnsupportedOperationException}.
*/
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
/**
- * Copies every mapping in the specified map to this map.
+ * {@inheritDoc}
*
- * @param map
- * the map to copy mappings from.
- * @throws UnsupportedOperationException
- * if adding to this map is not supported.
- * @throws ClassCastException
- * if the class of a key or value is inappropriate for this
- * map.
- * @throws IllegalArgumentException
- * if a key or value cannot be added to this map.
- * @throws NullPointerException
- * if a key or value is {@code null} and this map does not
- * support {@code null} keys or values.
+ * <p>This implementation iterates through {@code map}'s entry set, calling
+ * {@code put()} for each.
*/
public void putAll(Map<? extends K, ? extends V> map) {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
@@ -539,14 +384,10 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Removes a mapping with the specified key from this Map.
+ * {@inheritDoc}
*
- * @param key
- * the key of the mapping to remove.
- * @return the value of the removed mapping or {@code null} if no mapping
- * for the specified key was found.
- * @throws UnsupportedOperationException
- * if removing from this map is not supported.
+ * <p>This implementation iterates its entry set, removing the entry with
+ * a key that {@code key} equals.
*/
public V remove(Object key) {
Iterator<Map.Entry<K, V>> it = entrySet().iterator();
@@ -571,23 +412,24 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Returns the number of elements in this map.
+ * {@inheritDoc}
*
- * @return the number of elements in this map.
+ * <p>This implementation returns its entry set's size.
*/
public int size() {
return entrySet().size();
}
/**
- * Returns the string representation of this map.
+ * {@inheritDoc}
*
- * @return the string representation of this map.
+ * <p>This implementation composes a string by iterating its entry set. If
+ * this map contains itself as a key or a value, the string "(this Map)"
+ * will appear in its place.
*/
- @Override
- public String toString() {
+ @Override public String toString() {
if (isEmpty()) {
- return "{}"; //$NON-NLS-1$
+ return "{}";
}
StringBuilder buffer = new StringBuilder(size() * 28);
@@ -599,17 +441,17 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
if (key != this) {
buffer.append(key);
} else {
- buffer.append("(this Map)"); //$NON-NLS-1$
+ buffer.append("(this Map)");
}
buffer.append('=');
Object value = entry.getValue();
if (value != this) {
buffer.append(value);
} else {
- buffer.append("(this Map)"); //$NON-NLS-1$
+ buffer.append("(this Map)");
}
if (it.hasNext()) {
- buffer.append(", "); //$NON-NLS-1$
+ buffer.append(", ");
}
}
buffer.append('}');
@@ -617,42 +459,25 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
}
/**
- * Returns a collection of the values contained in this map. The collection
- * is backed by this map so changes to one are reflected by the other. The
- * collection supports remove, removeAll, retainAll and clear operations,
- * and it does not support add or addAll operations.
- * <p>
- * This method returns a collection which is the subclass of
- * AbstractCollection. The iterator method of this subclass returns a
- * "wrapper object" over the iterator of map's entrySet(). The {@code size}
- * method wraps the map's size method and the {@code contains} method wraps
- * the map's containsValue method.
- * <p>
- * The collection is created when this method is called for the first time
- * and returned in response to all subsequent calls. This method may return
- * different collections when multiple concurrent calls occur to this
- * method, since no synchronization is performed.
+ * {@inheritDoc}
*
- * @return a collection of the values contained in this map.
+ * <p>This implementation returns a view that calls through this to map. Its
+ * iterator transforms this map's entry set iterator to return values.
*/
public Collection<V> values() {
if (valuesCollection == null) {
valuesCollection = new AbstractCollection<V>() {
- @Override
- public int size() {
+ @Override public int size() {
return AbstractMap.this.size();
}
- @Override
- public boolean contains(Object object) {
+ @Override public boolean contains(Object object) {
return containsValue(object);
}
- @Override
- public Iterator<V> iterator() {
+ @Override public Iterator<V> iterator() {
return new Iterator<V>() {
- Iterator<Map.Entry<K, V>> setIterator = entrySet()
- .iterator();
+ Iterator<Map.Entry<K, V>> setIterator = entrySet().iterator();
public boolean hasNext() {
return setIterator.hasNext();
@@ -672,18 +497,8 @@ public abstract class AbstractMap<K, V> implements Map<K, V> {
return valuesCollection;
}
- /**
- * Returns a new instance of the same class as this instance, whose slots
- * have been filled in with the values of the slots of this instance.
- *
- * @return a shallow copy of this object.
- * @throws CloneNotSupportedException
- * if the receiver's class does not implement the interface
- * {@code Cloneable}.
- */
- @Override
@SuppressWarnings("unchecked")
- protected Object clone() throws CloneNotSupportedException {
+ @Override protected Object clone() throws CloneNotSupportedException {
AbstractMap<K, V> result = (AbstractMap<K, V>) super.clone();
result.keySet = null;
result.valuesCollection = null;
diff --git a/luni/src/main/java/java/util/TreeMap.java b/luni/src/main/java/java/util/TreeMap.java
index bfacc38..98680d0 100644
--- a/luni/src/main/java/java/util/TreeMap.java
+++ b/luni/src/main/java/java/util/TreeMap.java
@@ -1,2410 +1,1664 @@
/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
+ * Copyright (C) 2010 The Android Open Source Project
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package java.util;
import java.io.IOException;
import java.io.ObjectInputStream;
+import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
import java.io.Serializable;
+import static java.util.TreeMap.Bound.EXCLUSIVE;
+import static java.util.TreeMap.Bound.INCLUSIVE;
+import static java.util.TreeMap.Bound.NO_BOUND;
+import static java.util.TreeMap.Relation.CEILING;
+import static java.util.TreeMap.Relation.EQUAL;
+import static java.util.TreeMap.Relation.FLOOR;
+import static java.util.TreeMap.Relation.HIGHER;
+import static java.util.TreeMap.Relation.LOWER;
/**
- * TreeMap is an implementation of SortedMap. All optional operations (adding
- * and removing) are supported. The values can be any objects. The keys can be
- * any objects which are comparable to each other either using their natural
- * order or a specified Comparator.
+ * A map whose entries are sorted by their keys. All optional operations such as
+ * {@link #put} and {@link #remove} are supported.
+ *
+ * <p>This map sorts keys using either a user-supplied comparator or the key's
+ * natural order:
+ * <ul>
+ * <li>User supplied comparators must be able to compare any pair of keys in
+ * this map. If a user-supplied comparator is in use, it will be returned
+ * by {@link #comparator}.
+ * <li>If no user-supplied comparator is supplied, keys will be sorted by
+ * their natural order. Keys must be <i>mutually comparable</i>: they must
+ * implement {@link Comparable} and {@link Comparable#compareTo
+ * compareTo()} must be able to compare each key with any other key in
+ * this map. In this case {@link #comparator} will return null.
+ * </ul>
+ * With either a comparator or a natural ordering, comparisons should be
+ * <i>consistent with equals</i>. An ordering is consistent with equals if for
+ * every pair of keys {@code a} and {@code b}, {@code a.equals(b)} if and only
+ * if {@code compare(a, b) == 0}.
+ *
+ * <p>When the ordering is not consistent with equals the behavior of this
+ * class is well defined but does not honor the contract specified by {@link
+ * Map}. Consider a tree map of case-insensitive strings, an ordering that is
+ * not consistent with equals: <pre> {@code
+ * TreeMap<String, String> map = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+ * map.put("a", "android");
+ *
+ * // The Map API specifies that the next line should print "null" because
+ * // "a".equals("A") is false and there is no mapping for upper case "A".
+ * // But the case insensitive ordering says compare("a", "A") == 0. TreeMap
+ * // uses only comparators/comparable on keys and so this prints "android".
+ * System.out.println(map.get("A"));
+ * }</pre>
*
* @since 1.2
*/
-public class TreeMap <K, V> extends AbstractMap<K, V> implements SortedMap<K, V>,
- Cloneable, Serializable {
- private static final long serialVersionUID = 919286545866124006L;
+public class TreeMap<K, V> extends AbstractMap<K, V>
+ implements SortedMap<K, V>, NavigableMap<K, V>, Cloneable, Serializable {
- transient int size;
-
- private Comparator<? super K> comparator;
-
- transient int modCount;
-
- transient Set<Map.Entry<K, V>> entrySet;
-
- transient Node<K, V> root;
-
-class MapEntry implements Map.Entry<K, V>, Cloneable {
-
- final int offset;
- final Node<K, V> node;
- final K key;
-
- MapEntry(Node<K, V> node, int offset) {
- this.node = node;
- this.offset = offset;
- key = node.keys[offset];
- }
-
- @Override
- public Object clone() {
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- throw new AssertionError(e); // android-changed
- }
- }
-
- @Override
- public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
- if (object instanceof Map.Entry) {
- Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
- V value = getValue();
- return (key == null ? entry.getKey() == null : key.equals(entry
- .getKey()))
- && (value == null ? entry.getValue() == null : value
- .equals(entry.getValue()));
- }
- return false;
- }
-
- public K getKey() {
- return key;
- }
-
- public V getValue() {
- if (node.keys[offset] == key) {
- return node.values[offset];
- }
- if (containsKey(key)) {
- return get(key);
- }
- throw new IllegalStateException();
- }
-
- @Override
- public int hashCode() {
- V value = getValue();
- return (key == null ? 0 : key.hashCode())
- ^ (value == null ? 0 : value.hashCode());
- }
-
- public V setValue(V object) {
- if (node.keys[offset] == key) {
- V res = node.values[offset];
- node.values[offset] = object;
- return res;
- }
- if (containsKey(key)) {
- return put(key, object);
- }
- throw new IllegalStateException();
- }
-
- @Override
- public String toString() {
- return key + "=" + getValue();
- }
- }
-
- static class Node <K,V> implements Cloneable {
- static final int NODE_SIZE = 64;
- Node<K, V> prev, next;
- Node<K, V> parent, left, right;
- V[] values;
- K[] keys;
- int left_idx = 0;
- int right_idx = -1;
- int size = 0;
- boolean color;
-
- public Node() {
- keys = (K[]) new Object[NODE_SIZE];
- values = (V[]) new Object[NODE_SIZE];
- }
-
- @SuppressWarnings("unchecked")
- Node<K, V> clone(Node<K, V> parent) throws CloneNotSupportedException {
- Node<K, V> clone = (Node<K, V>) super.clone();
- clone.keys = (K[]) new Object[NODE_SIZE];
- clone.values = (V[]) new Object[NODE_SIZE];
- System.arraycopy(keys, 0, clone.keys, 0, keys.length);
- System.arraycopy(values, 0, clone.values, 0, values.length);
- clone.left_idx = left_idx;
- clone.right_idx = right_idx;
- clone.parent = parent;
- if (left != null) {
- clone.left = left.clone(clone);
- }
- if (right != null) {
- clone.right = right.clone(clone);
- }
- clone.prev = null;
- clone.next = null;
- return clone;
+ @SuppressWarnings("unchecked") // to avoid Comparable<Comparable<Comparable<...>>>
+ private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
+ public int compare(Comparable a, Comparable b) {
+ return a.compareTo(b);
}
+ };
+
+ Comparator<? super K> comparator;
+ Node<K, V> root;
+ int size = 0;
+ int modCount = 0;
+
+ /**
+ * Create a natural order, empty tree map whose keys must be mutually
+ * comparable and non-null.
+ */
+ @SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
+ public TreeMap() {
+ this.comparator = (Comparator<? super K>) NATURAL_ORDER;
}
- @SuppressWarnings("unchecked")
- private static <T> Comparable<T> toComparable(T obj) {
- if (obj == null) {
- throw new NullPointerException();
+ /**
+ * Create a natural order tree map populated with the key/value pairs of
+ * {@code copyFrom}. This map's keys must be mutually comparable and
+ * non-null.
+ *
+ * <p>Even if {@code copyFrom} is a {@code SortedMap}, the constructed map
+ * <strong>will not</strong> use {@code copyFrom}'s ordering. This
+ * constructor always creates a naturally-ordered map. Because the {@code
+ * TreeMap} constructor overloads are ambiguous, prefer to construct a map
+ * and populate it in two steps: <pre> {@code
+ * TreeMap<String, Integer> customOrderedMap
+ * = new TreeMap<String, Integer>(copyFrom.comparator());
+ * customOrderedMap.putAll(copyFrom);
+ * }</pre>
+ */
+ public TreeMap(Map<? extends K, ? extends V> copyFrom) {
+ this();
+ for (Map.Entry<? extends K, ? extends V> entry : copyFrom.entrySet()) {
+ putInternal(entry.getKey(), entry.getValue());
}
- return (Comparable) obj;
}
- static class AbstractMapIterator <K,V> {
- TreeMap<K, V> backingMap;
- int expectedModCount;
- Node<K, V> node;
- Node<K, V> lastNode;
- int offset;
- int lastOffset;
+ /**
+ * Create a tree map ordered by {@code comparator}. This map's keys may only
+ * be null if {@code comparator} permits.
+ *
+ * @parameter comparator the comparator to order elements with, or {@code
+ * null} to use the natural ordering.
+ */
+ @SuppressWarnings("unchecked") // unsafe! if comparator is null, this assumes K is comparable
+ public TreeMap(Comparator<? super K> comparator) {
+ if (comparator != null) {
+ this.comparator = comparator;
+ } else {
+ this.comparator = (Comparator<? super K>) NATURAL_ORDER;
+ }
+ }
- AbstractMapIterator(TreeMap<K, V> map, Node<K, V> startNode, int startOffset) {
- backingMap = map;
- expectedModCount = map.modCount;
- node = startNode;
- offset = startOffset;
+ /**
+ * Create a tree map with the ordering and key/value pairs of {@code
+ * copyFrom}. This map's keys may only be null if the {@code copyFrom}'s
+ * ordering permits.
+ *
+ * <p>The constructed map <strong>will always use</strong> {@code
+ * copyFrom}'s ordering. Because the {@code TreeMap} constructor overloads
+ * are ambigous, prefer to construct a map and populate it in two steps:
+ * <pre> {@code
+ * TreeMap<String, Integer> customOrderedMap
+ * = new TreeMap<String, Integer>(copyFrom.comparator());
+ * customOrderedMap.putAll(copyFrom);
+ * }</pre>
+ */
+ @SuppressWarnings("unchecked") // if copyFrom's keys are comparable this map's keys must be also
+ public TreeMap(SortedMap<K, ? extends V> copyFrom) {
+ Comparator<? super K> sourceComparator = copyFrom.comparator();
+ if (sourceComparator != null) {
+ this.comparator = sourceComparator;
+ } else {
+ this.comparator = (Comparator<? super K>) NATURAL_ORDER;
}
+ for (Map.Entry<K, ? extends V> entry : copyFrom.entrySet()) {
+ putInternal(entry.getKey(), entry.getValue());
+ }
+ }
- AbstractMapIterator(TreeMap<K, V> map, Node<K, V> startNode) {
- this(map, startNode, startNode != null ?
- startNode.right_idx - startNode.left_idx : 0);
+ @Override public Object clone() {
+ try {
+ @SuppressWarnings("unchecked") // super.clone() must return the same type
+ TreeMap<K, V> map = (TreeMap<K, V>) super.clone();
+ map.root = root.copy(null);
+ map.entrySet = null;
+ map.keySet = null;
+ return map;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
}
+ }
+
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public boolean isEmpty() {
+ return size == 0;
+ }
+
+ @Override public V get(Object key) {
+ Entry<K, V> entry = findByObject(key);
+ return entry != null ? entry.getValue() : null;
+ }
+
+ @Override public boolean containsKey(Object key) {
+ return findByObject(key) != null;
+ }
+
+ @Override public V put(K key, V value) {
+ return putInternal(key, value);
+ }
+
+ @Override public void clear() {
+ root = null;
+ size = 0;
+ modCount++;
+ }
- AbstractMapIterator(TreeMap<K, V> map) {
- this(map, minimum(map.root));
+ @Override public V remove(Object key) {
+ Node<K, V> node = removeInternalByKey(key);
+ return node != null ? node.value : null;
+ }
+
+ /*
+ * AVL methods
+ */
+
+ enum Relation {
+ LOWER,
+ FLOOR,
+ EQUAL,
+ CREATE,
+ CEILING,
+ HIGHER;
+
+ /**
+ * Returns a possibly-flipped relation for use in descending views.
+ *
+ * @param ascending false to flip; true to return this.
+ */
+ Relation forOrder(boolean ascending) {
+ if (ascending) {
+ return this;
+ }
+
+ switch (this) {
+ case LOWER:
+ return HIGHER;
+ case FLOOR:
+ return CEILING;
+ case EQUAL:
+ return EQUAL;
+ case CEILING:
+ return FLOOR;
+ case HIGHER:
+ return LOWER;
+ default:
+ throw new IllegalStateException();
+ }
}
+ }
- public boolean hasNext() {
- return node != null;
+ V putInternal(K key, V value) {
+ Node<K, V> created = find(key, Relation.CREATE);
+ V result = created.value;
+ created.value = value;
+ return result;
+ }
+
+ /**
+ * Returns the node at or adjacent to the given key, creating it if requested.
+ *
+ * @throws ClassCastException if {@code key} and the tree's keys aren't mutually comparable.
+ */
+ Node<K, V> find(K key, Relation relation) {
+ if (root == null) {
+ if (relation == Relation.CREATE) {
+ if (comparator == NATURAL_ORDER && key == null) {
+ throw new NullPointerException();
+ }
+ root = new Node<K, V>(null, key);
+ size = 1;
+ modCount++;
+ return root;
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * Micro-optimization: avoid polymorphic calls to Comparator.compare().
+ * This is 10% faster for naturally ordered trees.
+ */
+ @SuppressWarnings("unchecked") // will throw a ClassCastException below if there's trouble
+ Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
+ ? (Comparable<Object>) key
+ : null;
+
+ Node<K, V> nearest = root;
+ while (true) {
+ int comparison = (comparableKey != null)
+ ? comparableKey.compareTo(nearest.key)
+ : comparator.compare(key, nearest.key);
+
+ /*
+ * We found the requested key.
+ */
+ if (comparison == 0) {
+ switch (relation) {
+ case LOWER:
+ return nearest.prev();
+ case FLOOR:
+ case EQUAL:
+ case CREATE:
+ case CEILING:
+ return nearest;
+ case HIGHER:
+ return nearest.next();
+ }
+ }
+
+ Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;
+ if (child != null) {
+ nearest = child;
+ continue;
+ }
+
+ /*
+ * We found a nearest node. Every key not in the tree has up to two
+ * nearest nodes, one lower and one higher.
+ */
+
+ if (comparison < 0) { // nearest.key is higher
+ switch (relation) {
+ case LOWER:
+ case FLOOR:
+ return nearest.prev();
+ case CEILING:
+ case HIGHER:
+ return nearest;
+ case EQUAL:
+ return null;
+ case CREATE:
+ Node<K, V> created = new Node<K, V>(nearest, key);
+ nearest.left = created;
+ size++;
+ modCount++;
+ rebalance(nearest, true);
+ return created;
+ }
+ } else { // comparison > 0, nearest.key is lower
+ switch (relation) {
+ case LOWER:
+ case FLOOR:
+ return nearest;
+ case CEILING:
+ case HIGHER:
+ return nearest.next();
+ case EQUAL:
+ return null;
+ case CREATE:
+ Node<K, V> created = new Node<K, V>(nearest, key);
+ nearest.right = created;
+ size++;
+ modCount++;
+ rebalance(nearest, true);
+ return created;
+ }
+ }
}
+ }
- final void makeNext() {
- if (expectedModCount != backingMap.modCount) {
- throw new ConcurrentModificationException();
- } else if (node == null) {
- throw new NoSuchElementException();
+ @SuppressWarnings("unchecked") // this method throws ClassCastExceptions!
+ Node<K, V> findByObject(Object key) {
+ return find((K) key, EQUAL);
+ }
+
+ /**
+ * Returns this map's entry that has the same key and value as {@code
+ * entry}, or null if this map has no such entry.
+ *
+ * <p>This method uses the comparator for key equality rather than {@code
+ * equals}. If this map's comparator isn't consistent with equals (such as
+ * {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code
+ * contains()} will violate the collections API.
+ */
+ Node<K, V> findByEntry(Entry<?, ?> entry) {
+ Node<K, V> mine = findByObject(entry.getKey());
+ boolean valuesEqual = mine != null && (mine.value != null
+ ? mine.value.equals(entry.getValue())
+ : entry.getValue() == null);
+ return valuesEqual ? mine : null;
+ }
+
+ /**
+ * Removes {@code node} from this tree, rearranging the tree's structure as
+ * necessary.
+ */
+ void removeInternal(Node<K, V> node) {
+ Node<K, V> left = node.left;
+ Node<K, V> right = node.right;
+ Node<K, V> originalParent = node.parent;
+ if (left != null && right != null) {
+
+ /*
+ * To remove a node with both left and right subtrees, move an
+ * adjacent node from one of those subtrees into this node's place.
+ *
+ * Removing the adjacent node may change this node's subtrees. This
+ * node may no longer have two subtrees once the adjacent node is
+ * gone!
+ */
+
+ Node<K, V> adjacent = (left.height > right.height) ? left.last() : right.first();
+ removeInternal(adjacent); // takes care of rebalance and size--
+
+ int leftHeight = 0;
+ left = node.left;
+ if (left != null) {
+ leftHeight = left.height;
+ adjacent.left = left;
+ left.parent = adjacent;
+ node.left = null;
}
- lastNode = node;
- lastOffset = offset;
- if (offset != 0) {
- offset--;
+ int rightHeight = 0;
+ right = node.right;
+ if (right != null) {
+ rightHeight = right.height;
+ adjacent.right = right;
+ right.parent = adjacent;
+ node.right = null;
+ }
+ adjacent.height = Math.max(leftHeight, rightHeight) + 1;
+ replaceInParent(node, adjacent);
+ return;
+ } else if (left != null) {
+ replaceInParent(node, left);
+ node.left = null;
+ } else if (right != null) {
+ replaceInParent(node, right);
+ node.right = null;
+ } else {
+ replaceInParent(node, null);
+ }
+
+ rebalance(originalParent, false);
+ size--;
+ modCount++;
+ }
+
+ Node<K, V> removeInternalByKey(Object key) {
+ Node<K, V> node = findByObject(key);
+ if (node != null) {
+ removeInternal(node);
+ }
+ return node;
+ }
+
+ private void replaceInParent(Node<K, V> node, Node<K, V> replacement) {
+ Node<K, V> parent = node.parent;
+ node.parent = null;
+ if (replacement != null) {
+ replacement.parent = parent;
+ }
+
+ if (parent != null) {
+ if (parent.left == node) {
+ parent.left = replacement;
} else {
- node = node.next;
- if (node != null) {
- offset = node.right_idx - node.left_idx;
- }
+ assert (parent.right == node);
+ parent.right = replacement;
}
+ } else {
+ root = replacement;
}
+ }
+
+ /**
+ * Rebalances the tree by making any AVL rotations necessary between the
+ * newly-unbalanced node and the tree's root.
+ *
+ * @param insert true if the node was unbalanced by an insert; false if it
+ * was by a removal.
+ */
+ private void rebalance(Node<K, V> unbalanced, boolean insert) {
+ for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
+ Node<K, V> left = node.left;
+ Node<K, V> right = node.right;
+ int leftHeight = left != null ? left.height : 0;
+ int rightHeight = right != null ? right.height : 0;
+
+ int delta = leftHeight - rightHeight;
+ if (delta == -2) {
+ Node<K, V> rightLeft = right.left;
+ Node<K, V> rightRight = right.right;
+ int rightRightHeight = rightRight != null ? rightRight.height : 0;
+ int rightLeftHeight = rightLeft != null ? rightLeft.height : 0;
+
+ int rightDelta = rightLeftHeight - rightRightHeight;
+ if (rightDelta == -1 || (rightDelta == 0 && !insert)) {
+ rotateLeft(node); // AVL right right
+ } else {
+ assert (rightDelta == 1);
+ rotateRight(right); // AVL right left
+ rotateLeft(node);
+ }
+ if (insert) {
+ break; // no further rotations will be necessary
+ }
- final public void remove() {
- if (expectedModCount == backingMap.modCount) {
- if (lastNode != null) {
- int idx = lastNode.right_idx - lastOffset;
- backingMap.removeFromIterator(lastNode, idx);
- lastNode = null;
- expectedModCount++;
+ } else if (delta == 2) {
+ Node<K, V> leftLeft = left.left;
+ Node<K, V> leftRight = left.right;
+ int leftRightHeight = leftRight != null ? leftRight.height : 0;
+ int leftLeftHeight = leftLeft != null ? leftLeft.height : 0;
+
+ int leftDelta = leftLeftHeight - leftRightHeight;
+ if (leftDelta == 1 || (leftDelta == 0 && !insert)) {
+ rotateRight(node); // AVL left left
} else {
- throw new IllegalStateException();
+ assert (leftDelta == -1);
+ rotateLeft(left); // AVL left right
+ rotateRight(node);
+ }
+ if (insert) {
+ break; // no further rotations will be necessary
+ }
+
+ } else if (delta == 0) {
+ node.height = leftHeight + 1; // leftHeight == rightHeight
+ if (insert) {
+ break; // the insert caused balance, so rebalancing is done!
}
+
} else {
- throw new ConcurrentModificationException();
+ assert (delta == -1 || delta == 1);
+ node.height = Math.max(leftHeight, rightHeight) + 1;
+ if (!insert) {
+ break; // the height hasn't changed, so rebalancing is done!
+ }
}
}
}
- static class UnboundedEntryIterator <K, V> extends AbstractMapIterator<K, V>
- implements Iterator<Map.Entry<K, V>> {
+ /**
+ * Rotates the subtree so that its root's right child is the new root.
+ */
+ private void rotateLeft(Node<K, V> root) {
+ Node<K, V> left = root.left;
+ Node<K, V> pivot = root.right;
+ Node<K, V> pivotLeft = pivot.left;
+ Node<K, V> pivotRight = pivot.right;
- UnboundedEntryIterator(TreeMap<K, V> map, Node<K, V> startNode, int startOffset) {
- super(map, startNode, startOffset);
+ // move the pivot's left child to the root's right
+ root.right = pivotLeft;
+ if (pivotLeft != null) {
+ pivotLeft.parent = root;
}
- UnboundedEntryIterator(TreeMap<K, V> map) {
- super(map);
- }
+ replaceInParent(root, pivot);
- public Map.Entry<K, V> next() {
- makeNext();
- int idx = lastNode.right_idx - lastOffset;
- return backingMap.new MapEntry(lastNode, idx);
- }
+ // move the root to the pivot's left
+ pivot.left = root;
+ root.parent = pivot;
+
+ // fix heights
+ root.height = Math.max(left != null ? left.height : 0,
+ pivotLeft != null ? pivotLeft.height : 0) + 1;
+ pivot.height = Math.max(root.height,
+ pivotRight != null ? pivotRight.height : 0) + 1;
}
- static class UnboundedKeyIterator <K, V> extends AbstractMapIterator<K, V>
- implements Iterator<K> {
+ /**
+ * Rotates the subtree so that its root's left child is the new root.
+ */
+ private void rotateRight(Node<K, V> root) {
+ Node<K, V> pivot = root.left;
+ Node<K, V> right = root.right;
+ Node<K, V> pivotLeft = pivot.left;
+ Node<K, V> pivotRight = pivot.right;
- UnboundedKeyIterator(TreeMap<K, V> map, Node<K, V> startNode, int startOffset) {
- super(map, startNode, startOffset);
+ // move the pivot's right child to the root's left
+ root.left = pivotRight;
+ if (pivotRight != null) {
+ pivotRight.parent = root;
}
- UnboundedKeyIterator(TreeMap<K, V> map) {
- super(map);
+ replaceInParent(root, pivot);
+
+ // move the root to the pivot's right
+ pivot.right = root;
+ root.parent = pivot;
+
+ // fixup heights
+ root.height = Math.max(right != null ? right.height : 0,
+ pivotRight != null ? pivotRight.height : 0) + 1;
+ pivot.height = Math.max(root.height,
+ pivotLeft != null ? pivotLeft.height : 0) + 1;
+ }
+
+ /*
+ * Navigable methods.
+ */
+
+ public Entry<K, V> firstEntry() {
+ return root == null ? null : root.first();
+ }
+
+ public Entry<K, V> pollFirstEntry() {
+ if (root == null) {
+ return null;
}
+ Node<K, V> result = root.first();
+ removeInternal(result);
+ return result;
+ }
- public K next() {
- makeNext();
- return lastNode.keys[lastNode.right_idx - lastOffset];
+ public K firstKey() {
+ if (root == null) {
+ throw new NoSuchElementException();
}
+ return root.first().getKey();
}
- static class UnboundedValueIterator <K, V> extends AbstractMapIterator<K, V>
- implements Iterator<V> {
+ public Entry<K, V> lastEntry() {
+ return root == null ? null : root.last();
+ }
- UnboundedValueIterator(TreeMap<K, V> map, Node<K, V> startNode, int startOffset) {
- super(map, startNode, startOffset);
+ public Entry<K, V> pollLastEntry() {
+ if (root == null) {
+ return null;
}
+ Node<K, V> result = root.last();
+ removeInternal(result);
+ return result;
+ }
- UnboundedValueIterator(TreeMap<K, V> map) {
- super(map);
+ public K lastKey() {
+ if (root == null) {
+ throw new NoSuchElementException();
}
+ return root.last().getKey();
+ }
- public V next() {
- makeNext();
- return lastNode.values[lastNode.right_idx - lastOffset];
- }
+ public Entry<K, V> lowerEntry(K key) {
+ return find(key, LOWER);
}
- static class BoundedMapIterator <K, V> extends AbstractMapIterator<K, V> {
+ public K lowerKey(K key) {
+ Entry<K, V> entry = find(key, LOWER);
+ return entry != null ? entry.getKey() : null;
+ }
- Node<K, V> finalNode;
- int finalOffset;
+ public Entry<K, V> floorEntry(K key) {
+ return find(key, FLOOR);
+ }
- BoundedMapIterator(Node<K, V> startNode, int startOffset, TreeMap<K, V> map,
- Node<K, V> finalNode, int finalOffset) {
- super(map, finalNode==null? null : startNode, startOffset);
- this.finalNode = finalNode;
- this.finalOffset = finalOffset;
- }
+ public K floorKey(K key) {
+ Entry<K, V> entry = find(key, FLOOR);
+ return entry != null ? entry.getKey() : null;
+ }
- BoundedMapIterator(Node<K, V> startNode, TreeMap<K, V> map,
- Node<K, V> finalNode, int finalOffset) {
- this(startNode, startNode != null ?
- startNode.right_idx - startNode.left_idx : 0,
- map, finalNode, finalOffset);
- }
+ public Entry<K, V> ceilingEntry(K key) {
+ return find(key, CEILING);
+ }
- BoundedMapIterator(Node<K, V> startNode, int startOffset,
- TreeMap<K, V> map, Node<K, V> finalNode) {
- this(startNode, startOffset, map, finalNode,
- finalNode.right_idx - finalNode.left_idx);
- }
+ public K ceilingKey(K key) {
+ Entry<K, V> entry = find(key, CEILING);
+ return entry != null ? entry.getKey() : null;
+ }
- void makeBoundedNext() {
- makeNext();
- if (lastNode == finalNode && lastOffset == finalOffset) {
- node = null;
- }
- }
+ public Entry<K, V> higherEntry(K key) {
+ return find(key, HIGHER);
}
- static class BoundedEntryIterator <K, V> extends BoundedMapIterator<K, V>
- implements Iterator<Map.Entry<K, V>> {
+ public K higherKey(K key) {
+ Entry<K, V> entry = find(key, HIGHER);
+ return entry != null ? entry.getKey() : null;
+ }
- public BoundedEntryIterator(Node<K, V> startNode, int startOffset, TreeMap<K, V> map,
- Node<K, V> finalNode, int finalOffset) {
- super(startNode, startOffset, map, finalNode, finalOffset);
- }
+ public Comparator<? super K> comparator() {
+ return comparator != NATURAL_ORDER ? comparator : null;
+ }
- public Map.Entry<K, V> next() {
- makeBoundedNext();
- int idx = lastNode.right_idx - lastOffset;
- return backingMap.new MapEntry(lastNode, idx);
- }
+ /*
+ * View factory methods.
+ */
+
+ private EntrySet entrySet;
+ private KeySet keySet;
+
+ @Override public Set<Entry<K, V>> entrySet() {
+ EntrySet result = entrySet;
+ return result != null ? result : (entrySet = new EntrySet());
}
- static class BoundedKeyIterator <K, V> extends BoundedMapIterator<K, V>
- implements Iterator<K> {
+ @Override public Set<K> keySet() {
+ KeySet result = keySet;
+ return result != null ? result : (keySet = new KeySet());
+ }
- public BoundedKeyIterator(Node<K, V> startNode, int startOffset, TreeMap<K, V> map,
- Node<K, V> finalNode, int finalOffset) {
- super(startNode, startOffset, map, finalNode, finalOffset);
- }
+ public NavigableSet<K> navigableKeySet() {
+ KeySet result = keySet;
+ return result != null ? result : (keySet = new KeySet());
+ }
- public K next() {
- makeBoundedNext();
- return lastNode.keys[lastNode.right_idx - lastOffset];
- }
+ public NavigableMap<K, V> subMap(K from, boolean fromInclusive, K to, boolean toInclusive) {
+ Bound fromBound = fromInclusive ? INCLUSIVE : EXCLUSIVE;
+ Bound toBound = toInclusive ? INCLUSIVE : EXCLUSIVE;
+ return new BoundedMap(true, from, fromBound, to, toBound);
}
- static class BoundedValueIterator <K, V> extends BoundedMapIterator<K, V>
- implements Iterator<V> {
+ public SortedMap<K, V> subMap(K fromInclusive, K toExclusive) {
+ return new BoundedMap(true, fromInclusive, INCLUSIVE, toExclusive, EXCLUSIVE);
+ }
- public BoundedValueIterator(Node<K, V> startNode, int startOffset, TreeMap<K, V> map,
- Node<K, V> finalNode, int finalOffset) {
- super(startNode, startOffset, map, finalNode, finalOffset);
- }
+ public NavigableMap<K, V> headMap(K to, boolean inclusive) {
+ Bound toBound = inclusive ? INCLUSIVE : EXCLUSIVE;
+ return new BoundedMap(true, null, NO_BOUND, to, toBound);
+ }
- public V next() {
- makeBoundedNext();
- return lastNode.values[lastNode.right_idx - lastOffset];
- }
+ public SortedMap<K, V> headMap(K toExclusive) {
+ return new BoundedMap(true, null, NO_BOUND, toExclusive, EXCLUSIVE);
}
- static final class SubMap <K,V> extends AbstractMap<K, V>
- implements SortedMap<K, V>, Serializable {
- private static final long serialVersionUID = -6520786458950516097L;
+ public NavigableMap<K, V> tailMap(K from, boolean inclusive) {
+ Bound fromBound = inclusive ? INCLUSIVE : EXCLUSIVE;
+ return new BoundedMap(true, from, fromBound, null, NO_BOUND);
+ }
- private TreeMap<K, V> backingMap;
+ public SortedMap<K, V> tailMap(K fromInclusive) {
+ return new BoundedMap(true, fromInclusive, INCLUSIVE, null, NO_BOUND);
+ }
- boolean hasStart, hasEnd;
- K startKey, endKey;
- transient Set<Map.Entry<K, V>> entrySet = null;
- transient int firstKeyModCount = -1;
- transient int lastKeyModCount = -1;
- transient Node<K, V> firstKeyNode;
- transient int firstKeyIndex;
- transient Node<K, V> lastKeyNode;
- transient int lastKeyIndex;
+ public NavigableMap<K, V> descendingMap() {
+ return new BoundedMap(false, null, NO_BOUND, null, NO_BOUND);
+ }
- SubMap(K start, TreeMap<K, V> map) {
- backingMap = map;
- hasStart = true;
- startKey = start;
- }
+ public NavigableSet<K> descendingKeySet() {
+ return new BoundedMap(false, null, NO_BOUND, null, NO_BOUND).navigableKeySet();
+ }
- SubMap(K start, TreeMap<K, V> map, K end) {
- backingMap = map;
- hasStart = hasEnd = true;
- startKey = start;
- endKey = end;
- }
+ static class Node<K, V> implements Map.Entry<K, V> {
+ Node<K, V> parent;
+ Node<K, V> left;
+ Node<K, V> right;
+ final K key;
+ V value;
+ int height;
- SubMap(TreeMap<K, V> map, K end) {
- backingMap = map;
- hasEnd = true;
- endKey = end;
+ Node(Node<K, V> parent, K key) {
+ this.parent = parent;
+ this.key = key;
+ this.height = 1;
}
- private void checkRange(K key) {
- Comparator<? super K> cmp = backingMap.comparator;
- if (cmp == null) {
- Comparable<K> object = toComparable(key);
- if (hasStart && object.compareTo(startKey) < 0) {
- throw new IllegalArgumentException();
- }
- if (hasEnd && object.compareTo(endKey) > 0) {
- throw new IllegalArgumentException();
- }
- } else {
- if (hasStart
- && backingMap.comparator().compare(key, startKey) < 0) {
- throw new IllegalArgumentException();
- }
- if (hasEnd && backingMap.comparator().compare(key, endKey) > 0) {
- throw new IllegalArgumentException();
- }
+ Node<K, V> copy(Node<K, V> parent) {
+ Node<K, V> result = new Node<K, V>(parent, key);
+ if (left != null) {
+ result.left = left.copy(result);
}
- }
-
- private boolean isInRange(K key) {
- Comparator<? super K> cmp = backingMap.comparator;
- if (cmp == null) {
- Comparable<K> object = toComparable(key);
- if (hasStart && object.compareTo(startKey) < 0) {
- return false;
- }
- if (hasEnd && object.compareTo(endKey) >= 0) {
- return false;
- }
- } else {
- if (hasStart && cmp.compare(key, startKey) < 0) {
- return false;
- }
- if (hasEnd && cmp.compare(key, endKey) >= 0) {
- return false;
- }
+ if (right != null) {
+ result.right = right.copy(result);
}
- return true;
+ result.value = value;
+ result.height = height;
+ return result;
}
- private boolean checkUpperBound(K key) {
- if (hasEnd) {
- Comparator<? super K> cmp = backingMap.comparator;
- if (cmp == null) {
- return (toComparable(key).compareTo(endKey) < 0);
- }
- return (cmp.compare(key, endKey) < 0);
- }
- return true;
+ public K getKey() {
+ return key;
}
- private boolean checkLowerBound(K key) {
- if (hasStart) {
- Comparator<? super K> cmp = backingMap.comparator;
- if (cmp == null) {
- return (toComparable(key).compareTo(startKey) >= 0);
- }
- return (cmp.compare(key, startKey) >= 0);
- }
- return true;
+ public V getValue() {
+ return value;
}
- public Comparator<? super K> comparator() {
- return backingMap.comparator();
+ public V setValue(V value) {
+ throw new UnsupportedOperationException(); // per the spec
}
- @SuppressWarnings("unchecked")
- @Override
- public boolean containsKey(Object key) {
- if (isInRange((K) key)) {
- return backingMap.containsKey(key);
+ @Override public boolean equals(Object o) {
+ if (o instanceof Map.Entry) {
+ Map.Entry other = (Map.Entry) o;
+ return (key == null ? other.getKey() == null : key.equals(other.getKey()))
+ && (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
return false;
}
- @Override
- public void clear() {
- keySet().clear();
+ @Override public int hashCode() {
+ return (key == null ? 0 : key.hashCode())
+ ^ (value == null ? 0 : value.hashCode());
}
- @Override
- public boolean containsValue(Object value) {
- Iterator<V> it = values().iterator();
- if (value != null) {
- while (it.hasNext()) {
- if (value.equals(it.next())) {
- return true;
- }
- }
- } else {
- while (it.hasNext()) {
- if (it.next() == null) {
- return true;
- }
- }
- }
- return false;
+ @Override public String toString() {
+ return key + "=" + value;
}
- @Override
- public Set<Map.Entry<K, V>> entrySet() {
- if (entrySet == null) {
- entrySet = new SubMapEntrySet<K, V>(this);
- }
- return entrySet;
- }
-
- private void setFirstKey() {
- if (firstKeyModCount == backingMap.modCount) {
- return;
- }
- Comparable<K> object = backingMap.comparator == null ?
- toComparable((K) startKey) : null;
- K key = (K) startKey;
- Node<K, V> node = backingMap.root;
- Node<K, V> foundNode = null;
- int foundIndex = -1;
- TOP_LOOP:
- while (node != null) {
- K[] keys = node.keys;
- int left_idx = node.left_idx;
- int result = backingMap.cmp(object, key, keys[left_idx]);
- if (result < 0) {
- foundNode = node;
- foundIndex = left_idx;
- node = node.left;
- } else if (result == 0) {
- foundNode = node;
- foundIndex = left_idx;
- break;
- } else {
- int right_idx = node.right_idx;
- if (left_idx != right_idx) {
- result = backingMap.cmp(object, key, keys[right_idx]);
- }
- if (result > 0) {
- node = node.right;
- } else if (result == 0) {
- foundNode = node;
- foundIndex = right_idx;
- break;
- } else { /*search in node*/
- foundNode = node;
- foundIndex = right_idx;
- int low = left_idx + 1, mid = 0, high = right_idx - 1;
- while (low <= high) {
- mid = (low + high) >>> 1;
- result = backingMap.cmp(object, key, keys[mid]);
- if (result > 0) {
- low = mid + 1;
- } else if (result == 0) {
- foundNode = node;
- foundIndex = mid;
- break TOP_LOOP;
- } else {
- foundNode = node;
- foundIndex = mid;
- high = mid - 1;
- }
- }
- break TOP_LOOP;
- }
- }
- }
- if (foundNode != null && !checkUpperBound(foundNode.keys[foundIndex])) {
- foundNode = null;
+ /**
+ * Returns the next node in an inorder traversal, or null if this is the
+ * last node in the tree.
+ */
+ Node<K, V> next() {
+ if (right != null) {
+ return right.first();
}
- firstKeyNode = foundNode;
- firstKeyIndex = foundIndex;
- firstKeyModCount = backingMap.modCount;
- }
- public K firstKey() {
- if (backingMap.size > 0) {
- if (!hasStart) {
- Node<K, V> node = minimum(backingMap.root);
- if (node != null && checkUpperBound(node.keys[node.left_idx])) {
- return node.keys[node.left_idx];
- }
- } else {
- setFirstKey();
- if (firstKeyNode != null) {
- return firstKeyNode.keys[firstKeyIndex];
- }
+ Node<K, V> node = this;
+ Node<K, V> parent = node.parent;
+ while (parent != null) {
+ if (parent.left == node) {
+ return parent;
}
+ node = parent;
+ parent = node.parent;
}
- throw new NoSuchElementException();
+ return null;
}
+ /**
+ * Returns the previous node in an inorder traversal, or null if this is
+ * the first node in the tree.
+ */
+ public Node<K, V> prev() {
+ if (left != null) {
+ return left.last();
+ }
- @SuppressWarnings("unchecked")
- @Override
- public V get(Object key) {
- if (isInRange((K) key)) {
- return backingMap.get(key);
+ Node<K, V> node = this;
+ Node<K, V> parent = node.parent;
+ while (parent != null) {
+ if (parent.right == node) {
+ return parent;
+ }
+ node = parent;
+ parent = node.parent;
}
return null;
}
- public SortedMap<K, V> headMap(K endKey) {
- checkRange(endKey);
- if (hasStart) {
- return new SubMap<K, V>(startKey, backingMap, endKey);
+ /**
+ * Returns the first node in this subtree.
+ */
+ public Node<K, V> first() {
+ Node<K, V> node = this;
+ Node<K, V> child = node.left;
+ while (child != null) {
+ node = child;
+ child = node.left;
}
- return new SubMap<K, V>(backingMap, endKey);
+ return node;
}
- @Override
- public boolean isEmpty() {
- if (hasStart) {
- setFirstKey();
- return firstKeyNode == null;
- } else {
- setLastKey();
- return lastKeyNode == null;
+ /**
+ * Returns the last node in this subtree.
+ */
+ public Node<K, V> last() {
+ Node<K, V> node = this;
+ Node<K, V> child = node.right;
+ while (child != null) {
+ node = child;
+ child = node.right;
}
+ return node;
}
+ }
- @Override
- public Set<K> keySet() {
- if (keySet == null) {
- keySet = new SubMapKeySet<K, V>(this);
- }
- return keySet;
+ /**
+ * Walk the nodes of the tree left-to-right or right-to-left. Note that in
+ * descending iterations, {@code next} will return the previous node.
+ */
+ abstract class MapIterator<T> implements Iterator<T> {
+ protected Node<K, V> next;
+ protected Node<K, V> last;
+ protected int expectedModCount = modCount;
+
+ MapIterator(Node<K, V> next) {
+ this.next = next;
}
- private void setLastKey() {
- if (lastKeyModCount == backingMap.modCount) {
- return;
- }
- Comparable<K> object = backingMap.comparator == null ?
- toComparable((K) endKey) : null;
- K key = (K) endKey;
- Node<K, V> node = backingMap.root;
- Node<K, V> foundNode = null;
- int foundIndex = -1;
- TOP_LOOP:
- while (node != null) {
- K[] keys = node.keys;
- int left_idx = node.left_idx;
- int result = backingMap.cmp(object, key, keys[left_idx]);
- if (result <= 0) {
- node = node.left;
- } else {
- int right_idx = node.right_idx;
- if (left_idx != right_idx) {
- result = backingMap.cmp(object, key, keys[right_idx]);
- }
- if (result > 0) {
- foundNode = node;
- foundIndex = right_idx;
- node = node.right;
- } else if (result == 0) {
- if (node.left_idx == node.right_idx) {
- foundNode = node.prev;
- if (foundNode != null) {
- foundIndex = foundNode.right_idx - 1;
- }
- } else {
- foundNode = node;
- foundIndex = right_idx - 1;
- }
- break;
- } else { /*search in node*/
- foundNode = node;
- foundIndex = left_idx;
- int low = left_idx + 1, mid = 0, high = right_idx - 1;
- while (low <= high) {
- mid = (low + high) >>> 1;
- result = backingMap.cmp(object, key, keys[mid]);
- if (result > 0) {
- foundNode = node;
- foundIndex = mid;
- low = mid + 1;
- } else if (result == 0) {
- foundNode = node;
- foundIndex = mid - 1;
- break TOP_LOOP;
- } else {
- high = mid - 1;
- }
- }
- break TOP_LOOP;
- }
- }
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ protected Node<K, V> stepForward() {
+ if (next == null) {
+ throw new NoSuchElementException();
}
- if (foundNode != null && !checkLowerBound(foundNode.keys[foundIndex])) {
- foundNode = null;
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
}
- lastKeyNode = foundNode;
- lastKeyIndex = foundIndex;
- lastKeyModCount = backingMap.modCount;
+ last = next;
+ next = next.next();
+ return last;
}
- public K lastKey() {
- if (backingMap.size > 0) {
- if (!hasEnd) {
- Node<K, V> node = maximum(backingMap.root);
- if (node != null && checkLowerBound(node.keys[node.right_idx])) {
- return node.keys[node.right_idx];
- }
- } else {
- setLastKey();
- if (lastKeyNode != null) {
- return lastKeyNode.keys[lastKeyIndex];
- }
- }
+ protected Node<K, V> stepBackward() {
+ if (next == null) {
+ throw new NoSuchElementException();
}
- throw new NoSuchElementException();
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ last = next;
+ next = next.prev();
+ return last;
}
-
- @Override
- public V put(K key, V value) {
- if (isInRange(key)) {
- return backingMap.put(key, value);
+ public void remove() {
+ if (last == null) {
+ throw new IllegalStateException();
}
- throw new IllegalArgumentException();
+ removeInternal(last);
+ expectedModCount = modCount;
+ last = null;
}
+ }
- @SuppressWarnings("unchecked")
- @Override
- public V remove(Object key) {
- if (isInRange((K) key)) {
- return backingMap.remove(key);
- }
- return null;
+ /*
+ * View implementations.
+ */
+
+ class EntrySet extends AbstractSet<Map.Entry<K, V>> {
+ @Override public int size() {
+ return size;
}
- public SortedMap<K, V> subMap(K startKey, K endKey) {
- checkRange(startKey);
- checkRange(endKey);
- Comparator<? super K> c = backingMap.comparator();
- if (c == null) {
- if (toComparable(startKey).compareTo(endKey) <= 0) {
- return new SubMap<K, V>(startKey, backingMap, endKey);
- }
- } else {
- if (c.compare(startKey, endKey) <= 0) {
- return new SubMap<K, V>(startKey, backingMap, endKey);
+ @Override public Iterator<Entry<K, V>> iterator() {
+ return new MapIterator<Entry<K, V>>((Node<K, V>) firstEntry()) {
+ public Entry<K, V> next() {
+ return stepForward();
}
- }
- throw new IllegalArgumentException();
+ };
}
- public SortedMap<K, V> tailMap(K startKey) {
- checkRange(startKey);
- if (hasEnd) {
- return new SubMap<K, V>(startKey, backingMap, endKey);
- }
- return new SubMap<K, V>(startKey, backingMap);
+ @Override public boolean contains(Object o) {
+ return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
}
- @Override
- public Collection<V> values() {
- if (valuesCollection == null) {
- valuesCollection = new SubMapValuesCollection<K, V>(this);
+ @Override public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
}
- return valuesCollection;
- }
- public int size() {
- Node<K, V> from, to;
- int fromIndex, toIndex;
- if (hasStart) {
- setFirstKey();
- from = firstKeyNode;
- fromIndex = firstKeyIndex;
- } else {
- from = minimum(backingMap.root);
- fromIndex = from == null ? 0 : from.left_idx;
- }
- if (from == null) {
- return 0;
- }
- if (hasEnd) {
- setLastKey();
- to = lastKeyNode;
- toIndex = lastKeyIndex;
- } else {
- to = maximum(backingMap.root);
- toIndex = to == null ? 0 : to.right_idx;
- }
- if (to == null) {
- return 0;
- }
- if (from == to) {
- return toIndex - fromIndex + 1;
+ Node<K, V> node = findByEntry((Entry<?, ?>) o);
+ if (node == null) {
+ return false;
}
- int sum = 0;
- while (from != to) {
- sum += (from.right_idx - fromIndex + 1);
- from = from.next;
- fromIndex = from.left_idx;
- }
- return sum + toIndex - fromIndex + 1;
+ removeInternal(node);
+ return true;
}
- private void readObject(ObjectInputStream stream) throws IOException,
- ClassNotFoundException {
- stream.defaultReadObject();
- firstKeyModCount = -1;
- lastKeyModCount = -1;
+ @Override public void clear() {
+ TreeMap.this.clear();
}
}
- static class SubMapEntrySet <K,V> extends AbstractSet<Map.Entry<K, V>>
- implements Set<Map.Entry<K, V>> {
- SubMap<K, V> subMap;
+ class KeySet extends AbstractSet<K> implements NavigableSet<K> {
+ @Override public int size() {
+ return size;
+ }
- SubMapEntrySet(SubMap<K, V> map) {
- subMap = map;
+ @Override public Iterator<K> iterator() {
+ return new MapIterator<K>((Node<K, V>) firstEntry()) {
+ public K next() {
+ return stepForward().key;
+ }
+ };
}
- @Override
- public boolean isEmpty() {
- return subMap.isEmpty();
+ public Iterator<K> descendingIterator() {
+ return new MapIterator<K>((Node<K, V>) lastEntry()) {
+ public K next() {
+ return stepBackward().key;
+ }
+ };
}
- public Iterator<Map.Entry<K, V>> iterator() {
- Node<K, V> from;
- int fromIndex;
- if (subMap.hasStart) {
- subMap.setFirstKey();
- from = subMap.firstKeyNode;
- fromIndex = subMap.firstKeyIndex;
- } else {
- from = minimum(subMap.backingMap.root);
- fromIndex = from != null ? from.left_idx : 0;
- }
- if (!subMap.hasEnd) {
- return new UnboundedEntryIterator<K, V>(subMap.backingMap, from, from == null ? 0 : from.right_idx - fromIndex);
- }
- subMap.setLastKey();
- Node<K, V> to = subMap.lastKeyNode;
- int toIndex = subMap.lastKeyIndex;
- return new BoundedEntryIterator<K, V>(from, from == null ? 0 : from.right_idx - fromIndex, subMap.backingMap, to, to == null ? 0 : to.right_idx - toIndex);
+ @Override public boolean contains(Object o) {
+ return containsKey(o);
}
- @Override
- public int size() {
- return subMap.size();
+ @Override public boolean remove(Object key) {
+ return removeInternalByKey(key) != null;
}
- @SuppressWarnings("unchecked")
- @Override
- public boolean contains(Object object) {
- if (object instanceof Map.Entry) {
- Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
- K key = entry.getKey();
- if (subMap.isInRange(key)) {
- V v1 = subMap.get(key), v2 = entry.getValue();
- return v1 == null ? ( v2 == null && subMap.containsKey(key) ) : v1.equals(v2);
- }
- }
- return false;
+ @Override public void clear() {
+ TreeMap.this.clear();
}
- @Override
- public boolean remove(Object object) {
- if (contains(object)) {
- Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
- K key = entry.getKey();
- subMap.remove(key);
- return true;
- }
- return false;
+ public Comparator<? super K> comparator() {
+ return TreeMap.this.comparator();
}
- }
- static class SubMapKeySet <K,V> extends AbstractSet<K> implements Set<K> {
- SubMap<K, V> subMap;
+ /*
+ * Navigable methods.
+ */
- SubMapKeySet(SubMap<K, V> map) {
- subMap = map;
+ public K first() {
+ return firstKey();
}
- @Override
- public boolean contains(Object object) {
- return subMap.containsKey(object);
+ public K last() {
+ return lastKey();
}
- @Override
- public boolean isEmpty() {
- return subMap.isEmpty();
+ public K lower(K key) {
+ return lowerKey(key);
}
- @Override
- public int size() {
- return subMap.size();
+ public K floor(K key) {
+ return floorKey(key);
}
- @Override
- public boolean remove(Object object) {
- if (subMap.containsKey(object)) {
- subMap.remove(object);
- return true;
- }
- return false;
+ public K ceiling(K key) {
+ return ceilingKey(key);
}
- public Iterator<K> iterator() {
- Node<K, V> from;
- int fromIndex;
- if (subMap.hasStart) {
- subMap.setFirstKey();
- from = subMap.firstKeyNode;
- fromIndex = subMap.firstKeyIndex;
- } else {
- from = minimum(subMap.backingMap.root);
- fromIndex = from != null ? from.left_idx : 0;
- }
- if (!subMap.hasEnd) {
- return new UnboundedKeyIterator<K, V>(subMap.backingMap, from,
- from == null ? 0 : from.right_idx - fromIndex);
- }
- subMap.setLastKey();
- Node<K, V> to = subMap.lastKeyNode;
- int toIndex = subMap.lastKeyIndex;
- return new BoundedKeyIterator<K, V>(from,
- from == null ? 0 : from.right_idx - fromIndex, subMap.backingMap, to,
- to == null ? 0 : to.right_idx - toIndex);
+ public K higher(K key) {
+ return higherKey(key);
}
- }
-
- static class SubMapValuesCollection <K,V> extends AbstractCollection<V> {
- SubMap<K, V> subMap;
- public SubMapValuesCollection(SubMap<K, V> subMap) {
- this.subMap = subMap;
+ public K pollFirst() {
+ Entry<K, V> entry = pollFirstEntry();
+ return entry != null ? entry.getKey() : null;
}
- @Override
- public boolean isEmpty() {
- return subMap.isEmpty();
+ public K pollLast() {
+ Entry<K, V> entry = pollLastEntry();
+ return entry != null ? entry.getKey() : null;
}
- @Override
- public Iterator<V> iterator() {
- Node<K, V> from;
- int fromIndex;
- if (subMap.hasStart) {
- subMap.setFirstKey();
- from = subMap.firstKeyNode;
- fromIndex = subMap.firstKeyIndex;
- } else {
- from = minimum(subMap.backingMap.root);
- fromIndex = from != null ? from.left_idx : 0;
- }
- if (!subMap.hasEnd) {
- return new UnboundedValueIterator<K, V>(subMap.backingMap, from,
- from == null ? 0 : from.right_idx - fromIndex);
- }
- subMap.setLastKey();
- Node<K, V> to = subMap.lastKeyNode;
- int toIndex = subMap.lastKeyIndex;
- return new BoundedValueIterator<K, V>(from,
- from == null ? 0 : from.right_idx - fromIndex, subMap.backingMap, to,
- to == null ? 0 : to.right_idx - toIndex);
+ /*
+ * View factory methods.
+ */
+
+ public NavigableSet<K> subSet(K from, boolean fromInclusive, K to, boolean toInclusive) {
+ return TreeMap.this.subMap(from, fromInclusive, to, toInclusive).navigableKeySet();
}
- @Override
- public int size() {
- return subMap.size();
+ public SortedSet<K> subSet(K fromInclusive, K toExclusive) {
+ return TreeMap.this.subMap(fromInclusive, true, toExclusive, false).navigableKeySet();
}
- }
- /**
- * Constructs a new empty {@code TreeMap} instance.
- */
- public TreeMap() {
- }
+ public NavigableSet<K> headSet(K to, boolean inclusive) {
+ return TreeMap.this.headMap(to, inclusive).navigableKeySet();
+ }
- /**
- * Constructs a new empty {@code TreeMap} instance with the specified
- * comparator.
- *
- * @param comparator
- * the comparator to compare keys with.
- */
- public TreeMap(Comparator<? super K> comparator) {
- this.comparator = comparator;
- }
+ public SortedSet<K> headSet(K toExclusive) {
+ return TreeMap.this.headMap(toExclusive, false).navigableKeySet();
+ }
- /**
- * Constructs a new {@code TreeMap} instance containing the mappings from
- * the specified map and using natural ordering.
- *
- * @param map
- * the mappings to add.
- * @throws ClassCastException
- * if a key in the specified map does not implement the
- * Comparable interface, or if the keys in the map cannot be
- * compared.
- */
- public TreeMap(Map<? extends K, ? extends V> map) {
- putAll(map);
- }
+ public NavigableSet<K> tailSet(K from, boolean inclusive) {
+ return TreeMap.this.tailMap(from, inclusive).navigableKeySet();
+ }
- /**
- * Constructs a new {@code TreeMap} instance containing the mappings from
- * the specified SortedMap and using the same comparator.
- *
- * @param map
- * the mappings to add.
- */
- public TreeMap(SortedMap<K, ? extends V> map) {
- this(map.comparator());
- Node<K, V> lastNode = null;
- Iterator<? extends Map.Entry<K, ? extends V>> it = map.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<K, ? extends V> entry = it.next();
- lastNode = addToLast(lastNode, entry.getKey(), entry.getValue());
+ public SortedSet<K> tailSet(K fromInclusive) {
+ return TreeMap.this.tailMap(fromInclusive, true).navigableKeySet();
}
- }
- Node<K, V> addToLast(Node<K, V> last, K key, V value) {
- if (last == null) {
- root = last = createNode(key, value);
- size = 1;
- } else if (last.size == Node.NODE_SIZE) {
- Node<K, V> newNode = createNode(key, value);
- attachToRight(last, newNode);
- balance(newNode);
- size++;
- last = newNode;
- } else {
- appendFromRight(last, key, value);
- size++;
+ public NavigableSet<K> descendingSet() {
+ return new BoundedMap(false, null, NO_BOUND, null, NO_BOUND).navigableKeySet();
}
- return last;
}
- /**
- * Removes all mappings from this TreeMap, leaving it empty.
- *
- * @see Map#isEmpty()
- * @see #size()
+ /*
+ * Bounded views implementations.
*/
- @Override
- public void clear() {
- root = null;
- size = 0;
- modCount++;
+
+ enum Bound {
+ INCLUSIVE,
+ EXCLUSIVE,
+ NO_BOUND
}
/**
- * Returns a new {@code TreeMap} with the same mappings, size and comparator
- * as this instance.
- *
- * @return a shallow copy of this instance.
- * @see java.lang.Cloneable
+ * A map with optional limits on its range.
*/
- @SuppressWarnings("unchecked")
- @Override
- public Object clone() {
- try {
- TreeMap<K, V> clone = (TreeMap<K, V>) super.clone();
- clone.entrySet = null;
- if (root != null) {
- clone.root = root.clone(null);
- // restore prev/next chain
- Node<K, V> node = minimum(clone.root);
- while (true) {
- Node<K, V> nxt = successor(node);
- if (nxt == null) {
- break;
- }
- nxt.prev = node;
- node.next = nxt;
- node = nxt;
+ final class BoundedMap extends AbstractMap<K, V> implements NavigableMap<K, V>, Serializable {
+ private final boolean ascending;
+ private final K from;
+ private final Bound fromBound;
+ private final K to;
+ private final Bound toBound;
+
+ BoundedMap(boolean ascending, K from, Bound fromBound, K to, Bound toBound) {
+ /*
+ * Validate the bounds. In addition to checking that from <= to, we
+ * verify that the comparator supports our bound objets.
+ */
+ if (fromBound != NO_BOUND && toBound != NO_BOUND) {
+ if (comparator.compare(from, to) > 0) {
+ throw new IllegalArgumentException(from + " > " + to);
}
+ } else if (fromBound != NO_BOUND) {
+ comparator.compare(from, from);
+ } else if (toBound != NO_BOUND) {
+ comparator.compare(to, to);
}
- return clone;
- } catch (CloneNotSupportedException e) {
- throw new AssertionError(e); // android-changed
+
+ this.ascending = ascending;
+ this.from = from;
+ this.fromBound = fromBound;
+ this.to = to;
+ this.toBound = toBound;
}
- }
- static private <K, V> Node<K, V> successor(Node<K, V> x) {
- if (x.right != null) {
- return minimum(x.right);
+ @Override public int size() {
+ return count(entrySet().iterator());
}
- Node<K, V> y = x.parent;
- while (y != null && x == y.right) {
- x = y;
- y = y.parent;
+
+ @Override public boolean isEmpty() {
+ return firstEntry() == null;
}
- return y;
- }
- /**
- * Returns the comparator used to compare elements in this map.
- *
- * @return the comparator or {@code null} if the natural ordering is used.
- */
- public Comparator<? super K> comparator() {
- return comparator;
- }
+ @Override public V get(Object key) {
+ return isInBounds(key) ? TreeMap.this.get(key) : null;
+ }
- /**
- * Returns whether this map contains the specified key.
- *
- * @param key
- * the key to search for.
- * @return {@code true} if this map contains the specified key,
- * {@code false} otherwise.
- * @throws ClassCastException
- * if the specified key cannot be compared with the keys in this
- * map.
- * @throws NullPointerException
- * if the specified key is {@code null} and the comparator
- * cannot handle {@code null} keys.
- */
- @Override
- public boolean containsKey(Object key) {
- Comparable<K> object = comparator == null ? toComparable((K) key) : null;
- K keyK = (K) key;
- Node<K, V> node = root;
- while (node != null) {
- K[] keys = node.keys;
- int left_idx = node.left_idx;
- int result = cmp(object, keyK, keys[left_idx]);
- if (result < 0) {
- node = node.left;
- } else if (result == 0) {
- return true;
- } else {
- int right_idx = node.right_idx;
- if (left_idx != right_idx) {
- result = cmp(object, keyK, keys[right_idx]);
- }
- if (result > 0) {
- node = node.right;
- } else if (result == 0) {
- return true;
- } else { /*search in node*/
- int low = left_idx + 1, mid = 0, high = right_idx - 1;
- while (low <= high) {
- mid = (low + high) >>> 1;
- result = cmp(object, keyK, keys[mid]);
- if (result > 0) {
- low = mid + 1;
- } else if (result == 0) {
- return true;
- } else {
- high = mid - 1;
- }
- }
- return false;
- }
+ @Override public boolean containsKey(Object key) {
+ return isInBounds(key) && TreeMap.this.containsKey(key);
+ }
+
+ @Override public V put(K key, V value) {
+ if (!isInBounds(key)) {
+ throw new IllegalArgumentException();
}
+ return putInternal(key, value);
}
- return false;
- }
- /**
- * Returns whether this map contains the specified value.
- *
- * @param value
- * the value to search for.
- * @return {@code true} if this map contains the specified value,
- * {@code false} otherwise.
- */
- @Override
- public boolean containsValue(Object value) {
- if (root == null) {
- return false;
+ @Override public V remove(Object key) {
+ return isInBounds(key) ? TreeMap.this.remove(key) : null;
}
- Node<K, V> node = minimum(root);
- if (value != null) {
- while (node != null) {
- int to = node.right_idx;
- V[] values = node.values;
- for (int i = node.left_idx; i <= to; i++) {
- if (value.equals(values[i])) {
- return true;
- }
+
+ /**
+ * Returns true if the key is in bounds.
+ */
+ @SuppressWarnings("unchecked") // this method throws ClassCastExceptions!
+ private boolean isInBounds(Object key) {
+ return isInBounds((K) key, fromBound, toBound);
+ }
+
+ /**
+ * Returns true if the key is in bounds. Use this overload with
+ * NO_BOUND to skip bounds checking on either end.
+ */
+ private boolean isInBounds(K key, Bound fromBound, Bound toBound) {
+ if (fromBound == INCLUSIVE) {
+ if (comparator.compare(key, from) < 0) {
+ return false; // less than from
}
- node = node.next;
- }
- } else {
- while (node != null) {
- int to = node.right_idx;
- V[] values = node.values;
- for (int i = node.left_idx; i <= to; i++) {
- if (values[i] == null) {
- return true;
- }
+ } else if (fromBound == EXCLUSIVE) {
+ if (comparator.compare(key, from) <= 0) {
+ return false; // less than or equal to from
}
- node = node.next;
}
- }
- return false;
- }
- /**
- * Returns a set containing all of the mappings in this map. Each mapping is
- * an instance of {@link Map.Entry}. As the set is backed by this map,
- * changes in one will be reflected in the other. It does not support adding
- * operations.
- *
- * @return a set of the mappings.
- */
- @Override
- public Set<Map.Entry<K, V>> entrySet() {
- if (entrySet == null) {
- entrySet = new AbstractSet<Map.Entry<K, V>>() {
- @Override
- public int size() {
- return size;
+ if (toBound == INCLUSIVE) {
+ if (comparator.compare(key, to) > 0) {
+ return false; // greater than 'to'
}
-
- @Override
- public void clear() {
- TreeMap.this.clear();
+ } else if (toBound == EXCLUSIVE) {
+ if (comparator.compare(key, to) >= 0) {
+ return false; // greater than or equal to 'to'
}
+ }
- @SuppressWarnings("unchecked")
- @Override
- public boolean contains(Object object) {
- if (object instanceof Map.Entry) {
- Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
- K key = entry.getKey();
- Object v1 = TreeMap.this.get(key), v2 = entry.getValue();
- return v1 == null ? ( v2 == null && TreeMap.this.containsKey(key) ) : v1.equals(v2);
- }
- return false;
- }
+ return true;
+ }
- @Override
- public boolean remove(Object object) {
- if (contains(object)) {
- Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
- K key = entry.getKey();
- TreeMap.this.remove(key);
- return true;
- }
- return false;
- }
+ /**
+ * Returns the entry if it is in bounds, or null if it is out of bounds.
+ */
+ private Entry<K, V> bound(Entry<K, V> node, Bound fromBound, Bound toBound) {
+ return node != null && isInBounds(node.getKey(), fromBound, toBound) ? node : null;
+ }
- @Override
- public Iterator<Map.Entry<K, V>> iterator() {
- return new UnboundedEntryIterator<K, V>(TreeMap.this);
- }
- };
+ private Entry<K, V> bound(Entry<K, V> node) {
+ return bound(node, fromBound, toBound);
}
- return entrySet;
- }
- /**
- * Returns the first key in this map.
- *
- * @return the first key in this map.
- * @throws NoSuchElementException
- * if this map is empty.
- */
- public K firstKey() {
- if (root != null) {
- Node<K, V> node = minimum(root);
- return node.keys[node.left_idx];
+ /*
+ * Navigable methods.
+ */
+
+ public Entry<K, V> firstEntry() {
+ return endpoint(true);
}
- throw new NoSuchElementException();
- }
+ public Entry<K, V> pollFirstEntry() {
+ Node<K, V> result = (Node<K, V>) firstEntry();
+ if (result != null) {
+ removeInternal(result);
+ }
+ return result;
+ }
- /**
- * Returns the value of the mapping with the specified key.
- *
- * @param key
- * the key.
- * @return the value of the mapping with the specified key.
- * @throws ClassCastException
- * if the key cannot be compared with the keys in this map.
- * @throws NullPointerException
- * if the key is {@code null} and the comparator cannot handle
- * {@code null}.
- */
- @Override
- public V get(Object key) {
- Comparable<K> object = comparator == null ? toComparable((K) key) : null;
- K keyK = (K) key;
- Node<K, V> node = root;
- while (node != null) {
- K[] keys = node.keys;
- int left_idx = node.left_idx;
- int result = cmp(object, keyK, keys[left_idx]);
- if (result < 0) {
- node = node.left;
- } else if (result == 0) {
- return node.values[left_idx];
- } else {
- int right_idx = node.right_idx;
- if (left_idx != right_idx) {
- result = cmp(object, keyK, keys[right_idx]);
- }
- if (result > 0) {
- node = node.right;
- } else if (result == 0) {
- return node.values[right_idx];
- } else { /*search in node*/
- int low = left_idx + 1, mid = 0, high = right_idx - 1;
- while (low <= high) {
- mid = (low + high) >>> 1;
- result = cmp(object, keyK, keys[mid]);
- if (result > 0) {
- low = mid + 1;
- } else if (result == 0) {
- return node.values[mid];
- } else {
- high = mid - 1;
- }
- }
- return null;
- }
+ public K firstKey() {
+ Entry<K, V> entry = firstEntry();
+ if (entry == null) {
+ throw new NoSuchElementException();
}
+ return entry.getKey();
}
- return null;
- }
- private int cmp(Comparable<K> object, K key1, K key2) {
- return object != null ?
- object.compareTo(key2) : comparator.compare(key1, key2);
- }
+ public Entry<K, V> lastEntry() {
+ return endpoint(false);
+ }
- /**
- * Returns a sorted map over a range of this sorted map with all keys that
- * are less than the specified {@code endKey}. Changes to the returned
- * sorted map are reflected in this sorted map and vice versa.
- * <p>
- * Note: The returned map will not allow an insertion of a key outside the
- * specified range.
- *
- * @param endKey
- * the high boundary of the range specified.
- * @return a sorted map where the keys are less than {@code endKey}.
- * @throws ClassCastException
- * if the specified key cannot be compared with the keys in this
- * map.
- * @throws NullPointerException
- * if the specified key is {@code null} and the comparator
- * cannot handle {@code null} keys.
- * @throws IllegalArgumentException
- * if this map is itself a sorted map over a range of another
- * map and the specified key is outside of its range.
- */
- public SortedMap<K, V> headMap(K endKey) {
- // Check for errors
- if (comparator == null) {
- toComparable(endKey).compareTo(endKey);
- } else {
- comparator.compare(endKey, endKey);
+ public Entry<K, V> pollLastEntry() {
+ Node<K, V> result = (Node<K, V>) lastEntry();
+ if (result != null) {
+ removeInternal(result);
+ }
+ return result;
}
- return new SubMap<K, V>(this, endKey);
- }
- /**
- * Returns a set of the keys contained in this map. The set is backed by
- * this map so changes to one are reflected by the other. The set does not
- * support adding.
- *
- * @return a set of the keys.
- */
- @Override
- public Set<K> keySet() {
- if (keySet == null) {
- keySet = new AbstractSet<K>() {
- @Override
- public boolean contains(Object object) {
- return TreeMap.this.containsKey(object);
- }
+ public K lastKey() {
+ Entry<K, V> entry = lastEntry();
+ if (entry == null) {
+ throw new NoSuchElementException();
+ }
+ return entry.getKey();
+ }
- @Override
- public int size() {
- return TreeMap.this.size;
+ /**
+ * @param first true for the first element, false for the last.
+ */
+ private Entry<K, V> endpoint(boolean first) {
+ Entry<K, V> entry;
+ if (ascending == first) {
+ switch (fromBound) {
+ case NO_BOUND:
+ entry = TreeMap.this.firstEntry();
+ break;
+ case INCLUSIVE:
+ entry = TreeMap.this.ceilingEntry(from);
+ break;
+ case EXCLUSIVE:
+ entry = TreeMap.this.higherEntry(from);
+ break;
+ default:
+ throw new AssertionError();
}
-
- @Override
- public void clear() {
- TreeMap.this.clear();
+ return bound(entry, NO_BOUND, toBound);
+ } else {
+ switch (toBound) {
+ case NO_BOUND:
+ entry = TreeMap.this.lastEntry();
+ break;
+ case INCLUSIVE:
+ entry = TreeMap.this.floorEntry(to);
+ break;
+ case EXCLUSIVE:
+ entry = TreeMap.this.lowerEntry(to);
+ break;
+ default:
+ throw new AssertionError();
}
+ return bound(entry, fromBound, NO_BOUND);
+ }
+ }
- @Override
- public boolean remove(Object object) {
- if (contains(object)) {
- TreeMap.this.remove(object);
- return true;
- }
- return false;
- }
+ public Entry<K, V> lowerEntry(K key) {
+ return bound(find(key, LOWER.forOrder(ascending)));
+ }
- @Override
- public Iterator<K> iterator() {
- return new UnboundedKeyIterator<K, V>(TreeMap.this);
- }
- };
+ public K lowerKey(K key) {
+ Entry<K, V> entry = bound(find(key, LOWER.forOrder(ascending)));
+ return entry != null ? entry.getKey() : null;
}
- return keySet;
- }
- /**
- * Returns the last key in this map.
- *
- * @return the last key in this map.
- * @throws NoSuchElementException
- * if this map is empty.
- */
- public K lastKey() {
- if (root != null) {
- Node<K, V> node = maximum(root);
- return node.keys[node.right_idx];
+ public Entry<K, V> floorEntry(K key) {
+ return bound(find(key, FLOOR.forOrder(ascending)));
}
- throw new NoSuchElementException();
- }
- static <K,V> Node<K, V> minimum(Node<K, V> x) {
- if (x == null) {
- return null;
+ public K floorKey(K key) {
+ Entry<K, V> entry = bound(find(key, FLOOR.forOrder(ascending)));
+ return entry != null ? entry.getKey() : null;
}
- while (x.left != null) {
- x = x.left;
+
+ public Entry<K, V> ceilingEntry(K key) {
+ return bound(find(key, CEILING.forOrder(ascending)));
}
- return x;
- }
- static <K,V> Node<K, V> maximum(Node<K, V> x) {
- if (x == null) {
- return null;
+ public K ceilingKey(K key) {
+ Entry<K, V> entry = bound(find(key, CEILING.forOrder(ascending)));
+ return entry != null ? entry.getKey() : null;
}
- while (x.right != null) {
- x = x.right;
+
+ public Entry<K, V> higherEntry(K key) {
+ return bound(find(key, HIGHER.forOrder(ascending)));
}
- return x;
- }
- /**
- * Maps the specified key to the specified value.
- *
- * @param key
- * the key.
- * @param value
- * the value.
- * @return the value of any previous mapping with the specified key or
- * {@code null} if there was no mapping.
- * @throws ClassCastException
- * if the specified key cannot be compared with the keys in this
- * map.
- * @throws NullPointerException
- * if the specified key is {@code null} and the comparator
- * cannot handle {@code null} keys.
- */
- @Override
- public V put(K key, V value) {
- if (root == null) {
- root = createNode(key, value);
- size = 1;
- modCount++;
- return null;
+ public K higherKey(K key) {
+ Entry<K, V> entry = bound(find(key, HIGHER.forOrder(ascending)));
+ return entry != null ? entry.getKey() : null;
}
- Comparable<K> object = comparator == null ? toComparable((K) key) : null;
- K keyK = (K) key;
- Node<K, V> node = root;
- Node<K, V> prevNode = null;
- int result = 0;
- while (node != null) {
- prevNode = node;
- K[] keys = node.keys;
- int left_idx = node.left_idx;
- result = cmp(object, keyK, keys[left_idx]);
- if (result < 0) {
- node = node.left;
- } else if (result == 0) {
- V res = node.values[left_idx];
- node.values[left_idx] = value;
- return res;
- } else {
- int right_idx = node.right_idx;
- if (left_idx != right_idx) {
- result = cmp(object, keyK, keys[right_idx]);
- }
- if (result > 0) {
- node = node.right;
- } else if (result == 0) {
- V res = node.values[right_idx];
- node.values[right_idx] = value;
- return res;
- } else { /*search in node*/
- int low = left_idx + 1, mid = 0, high = right_idx - 1;
- while (low <= high) {
- mid = (low + high) >>> 1;
- result = cmp(object, keyK, keys[mid]);
- if (result > 0) {
- low = mid + 1;
- } else if (result == 0) {
- V res = node.values[mid];
- node.values[mid] = value;
- return res;
- } else {
- high = mid - 1;
- }
- }
- result = low;
- break;
- }
- }
- } /* while */
-/*
- if(node == null) {
- if(prevNode==null) {
- - case of empty Tree
- } else {
- result < 0 - prevNode.left==null - attach here
- result > 0 - prevNode.right==null - attach here
- }
+
+ public Comparator<? super K> comparator() {
+ if (ascending) {
+ return TreeMap.this.comparator();
} else {
- insert into node.
- result - index where it should be inserted.
+ return Collections.reverseOrder(comparator);
}
- */
- size++;
- modCount++;
- if (node == null) {
- if (prevNode == null) {
- // case of empty Tree
- root = createNode(key, value);
- } else if (prevNode.size < Node.NODE_SIZE) {
- // there is a place for insert
- if (result < 0) {
- appendFromLeft(prevNode, key, value);
- } else {
- appendFromRight(prevNode, key, value);
- }
- } else {
- // create and link
- Node<K, V> newNode = createNode(key, value);
- if (result < 0) {
- attachToLeft(prevNode, newNode);
- } else {
- attachToRight(prevNode, newNode);
- }
- balance(newNode);
- }
- } else {
- // insert into node.
- // result - index where it should be inserted.
- if (node.size < Node.NODE_SIZE) { // insert and ok
- int left_idx = node.left_idx;
- int right_idx = node.right_idx;
- if (left_idx == 0 || ((right_idx != Node.NODE_SIZE - 1) && (right_idx - result <= result - left_idx))) {
- int right_idxPlus1 = right_idx + 1;
- System.arraycopy(node.keys, result, node.keys, result + 1, right_idxPlus1 - result);
- System.arraycopy(node.values, result, node.values, result + 1, right_idxPlus1 - result);
- node.right_idx = right_idxPlus1;
- node.keys[result] = key;
- node.values[result] = value;
- } else {
- int left_idxMinus1 = left_idx - 1;
- System.arraycopy(node.keys, left_idx, node.keys, left_idxMinus1, result - left_idx);
- System.arraycopy(node.values, left_idx, node.values, left_idxMinus1, result - left_idx);
- node.left_idx = left_idxMinus1;
- node.keys[result - 1] = key;
- node.values[result - 1] = value;
- }
- node.size++;
- } else {
- // there are no place here
- // insert and push old pair
- Node<K, V> previous = node.prev;
- Node<K, V> nextNode = node.next;
- boolean removeFromStart;
- boolean attachFromLeft = false;
- Node<K, V> attachHere = null;
- if (previous == null) {
- if (nextNode != null && nextNode.size < Node.NODE_SIZE) {
- // move last pair to next
- removeFromStart = false;
- } else {
- // next node doesn't exist or full
- // left==null
- // drop first pair to new node from left
- removeFromStart = true;
- attachFromLeft = true;
- attachHere = node;
- }
- } else if (nextNode == null) {
- if (previous.size < Node.NODE_SIZE) {
- // move first pair to prev
- removeFromStart = true;
- } else {
- // right == null;
- // drop last pair to new node from right
- removeFromStart = false;
- attachFromLeft = false;
- attachHere = node;
- }
- } else {
- if (previous.size < Node.NODE_SIZE) {
- if (nextNode.size < Node.NODE_SIZE) {
- // choose prev or next for moving
- removeFromStart = previous.size < nextNode.size;
- } else {
- // move first pair to prev
- removeFromStart = true;
- }
- } else {
- if (nextNode.size < Node.NODE_SIZE) {
- // move last pair to next
- removeFromStart = false;
- } else {
- // prev & next are full
- // if node.right!=null then node.next.left==null
- // if node.left!=null then node.prev.right==null
- if (node.right == null) {
- attachHere = node;
- attachFromLeft = false;
- removeFromStart = false;
- } else {
- attachHere = nextNode;
- attachFromLeft = true;
- removeFromStart = false;
- }
- }
- }
- }
- K movedKey;
- V movedValue;
- if (removeFromStart) {
- // node.left_idx == 0
- movedKey = node.keys[0];
- movedValue = node.values[0];
- int resMunus1 = result - 1;
- System.arraycopy(node.keys, 1, node.keys, 0, resMunus1);
- System.arraycopy(node.values, 1, node.values, 0, resMunus1);
- node.keys [resMunus1] = key;
- node.values[resMunus1] = value;
- } else {
- // node.right_idx == Node.NODE_SIZE - 1
- movedKey = node.keys[Node.NODE_SIZE - 1];
- movedValue = node.values[Node.NODE_SIZE - 1];
- System.arraycopy(node.keys, result, node.keys, result + 1, Node.NODE_SIZE - 1 - result);
- System.arraycopy(node.values, result, node.values, result + 1, Node.NODE_SIZE - 1 - result);
- node.keys[result] = key;
- node.values[result] = value;
- }
- if (attachHere == null) {
- if (removeFromStart) {
- appendFromRight(previous, movedKey, movedValue);
- } else {
- appendFromLeft(nextNode, movedKey, movedValue);
- }
- } else {
- Node<K, V> newNode = createNode(movedKey, movedValue);
- if (attachFromLeft) {
- attachToLeft(attachHere, newNode);
- } else {
- attachToRight(attachHere, newNode);
- }
- balance(newNode);
- }
- }
}
- return null;
- }
- private void appendFromLeft(Node<K, V> node, K keyObj, V value) {
- if (node.left_idx == 0) {
- int new_right = node.right_idx + 1;
- System.arraycopy(node.keys, 0, node.keys, 1, new_right);
- System.arraycopy(node.values, 0, node.values, 1, new_right);
- node.right_idx = new_right;
- } else {
- node.left_idx--;
+ /*
+ * View factory methods.
+ */
+
+ private BoundedEntrySet entrySet;
+ private BoundedKeySet keySet;
+
+ @Override public Set<Entry<K, V>> entrySet() {
+ BoundedEntrySet result = entrySet;
+ return result != null ? result : (entrySet = new BoundedEntrySet());
}
- node.size++;
- node.keys[node.left_idx] = keyObj;
- node.values[node.left_idx] = value;
- }
- private void attachToLeft(Node<K, V> node, Node<K, V> newNode) {
- newNode.parent = node;
- // node.left==null - attach here
- node.left = newNode;
- Node<K, V> predecessor = node.prev;
- newNode.prev = predecessor;
- newNode.next = node;
- if (predecessor != null) {
- predecessor.next = newNode;
- }
- node.prev = newNode;
- }
+ @Override public Set<K> keySet() {
+ return navigableKeySet();
+ }
- /* add pair into node; existence free room in the node should be checked
- * before call
- */
- private void appendFromRight(Node<K, V> node, K keyObj, V value) {
- if (node.right_idx == Node.NODE_SIZE - 1) {
- int left_idx = node.left_idx;
- int left_idxMinus1 = left_idx - 1;
- System.arraycopy(node.keys, left_idx, node.keys, left_idxMinus1, Node.NODE_SIZE - left_idx);
- System.arraycopy(node.values, left_idx, node.values, left_idxMinus1, Node.NODE_SIZE - left_idx);
- node.left_idx = left_idxMinus1;
- } else {
- node.right_idx++;
+ public NavigableSet<K> navigableKeySet() {
+ BoundedKeySet result = keySet;
+ return result != null ? result : (keySet = new BoundedKeySet());
}
- node.size++;
- node.keys[node.right_idx] = keyObj;
- node.values[node.right_idx] = value;
- }
- private void attachToRight(Node<K, V> node, Node<K, V> newNode) {
- newNode.parent = node;
- // - node.right==null - attach here
- node.right = newNode;
- newNode.prev = node;
- Node<K, V> successor = node.next;
- newNode.next = successor;
- if (successor != null) {
- successor.prev = newNode;
- }
- node.next = newNode;
- }
+ public NavigableMap<K, V> descendingMap() {
+ return new BoundedMap(!ascending, from, fromBound, to, toBound);
+ }
- private Node<K, V> createNode(K keyObj, V value) {
- Node<K, V> node = new Node<K, V>();
- node.keys[0] = keyObj;
- node.values[0] = value;
- node.left_idx = 0;
- node.right_idx = 0;
- node.size = 1;
- return node;
- }
+ public NavigableSet<K> descendingKeySet() {
+ return new BoundedMap(!ascending, from, fromBound, to, toBound).navigableKeySet();
+ }
- void balance(Node<K, V> x) {
- Node<K, V> y;
- x.color = true;
- while (x != root && x.parent.color) {
- if (x.parent == x.parent.parent.left) {
- y = x.parent.parent.right;
- if (y != null && y.color) {
- x.parent.color = false;
- y.color = false;
- x.parent.parent.color = true;
- x = x.parent.parent;
- } else {
- if (x == x.parent.right) {
- x = x.parent;
- leftRotate(x);
- }
- x.parent.color = false;
- x.parent.parent.color = true;
- rightRotate(x.parent.parent);
- }
- } else {
- y = x.parent.parent.left;
- if (y != null && y.color) {
- x.parent.color = false;
- y.color = false;
- x.parent.parent.color = true;
- x = x.parent.parent;
- } else {
- if (x == x.parent.left) {
- x = x.parent;
- rightRotate(x);
- }
- x.parent.color = false;
- x.parent.parent.color = true;
- leftRotate(x.parent.parent);
- }
- }
+ public NavigableMap<K, V> subMap(K from, boolean fromInclusive, K to, boolean toInclusive) {
+ Bound fromBound = fromInclusive ? INCLUSIVE : EXCLUSIVE;
+ Bound toBound = toInclusive ? INCLUSIVE : EXCLUSIVE;
+ return subMap(from, fromBound, to, toBound);
}
- root.color = false;
- }
- private void rightRotate(Node<K, V> x) {
- Node<K, V> y = x.left;
- x.left = y.right;
- if (y.right != null) {
- y.right.parent = x;
+ public NavigableMap<K, V> subMap(K fromInclusive, K toExclusive) {
+ return subMap(fromInclusive, INCLUSIVE, toExclusive, EXCLUSIVE);
}
- y.parent = x.parent;
- if (x.parent == null) {
- root = y;
- } else {
- if (x == x.parent.right) {
- x.parent.right = y;
- } else {
- x.parent.left = y;
- }
+
+ public NavigableMap<K, V> headMap(K to, boolean inclusive) {
+ Bound toBound = inclusive ? INCLUSIVE : EXCLUSIVE;
+ return subMap(null, NO_BOUND, to, toBound);
}
- y.right = x;
- x.parent = y;
- }
+ public NavigableMap<K, V> headMap(K toExclusive) {
+ return subMap(null, NO_BOUND, toExclusive, EXCLUSIVE);
+ }
- private void leftRotate(Node<K, V> x) {
- Node<K, V> y = x.right;
- x.right = y.left;
- if (y.left != null) {
- y.left.parent = x;
+ public NavigableMap<K, V> tailMap(K from, boolean inclusive) {
+ Bound fromBound = inclusive ? INCLUSIVE : EXCLUSIVE;
+ return subMap(from, fromBound, null, NO_BOUND);
}
- y.parent = x.parent;
- if (x.parent == null) {
- root = y;
- } else {
- if (x == x.parent.left) {
- x.parent.left = y;
- } else {
- x.parent.right = y;
- }
+
+ public NavigableMap<K, V> tailMap(K fromInclusive) {
+ return subMap(fromInclusive, INCLUSIVE, null, NO_BOUND);
}
- y.left = x;
- x.parent = y;
- }
+ private NavigableMap<K, V> subMap(K from, Bound fromBound, K to, Bound toBound) {
+ if (!ascending) {
+ K fromTmp = from;
+ Bound fromBoundTmp = fromBound;
+ from = to;
+ fromBound = toBound;
+ to = fromTmp;
+ toBound = fromBoundTmp;
+ }
- /**
- * Copies all the mappings in the given map to this map. These mappings will
- * replace all mappings that this map had for any of the keys currently in
- * the given map.
- *
- * @param map
- * the map to copy mappings from.
- * @throws ClassCastException
- * if a key in the specified map cannot be compared with the
- * keys in this map.
- * @throws NullPointerException
- * if a key in the specified map is {@code null} and the
- * comparator cannot handle {@code null} keys.
- */
- @Override
- public void putAll(Map<? extends K, ? extends V> map) {
- super.putAll(map);
- }
+ if (fromBound == NO_BOUND) {
+ from = this.from;
+ fromBound = this.fromBound;
+ } else if (this.fromBound != NO_BOUND) {
+ int comparison = comparator.compare(from, this.from);
+ if (comparison < 0 || (comparison == 0
+ && fromBound == INCLUSIVE && this.fromBound == EXCLUSIVE)) {
+ throw new IllegalArgumentException();
+ }
+ }
- /**
- * Removes the mapping with the specified key from this map.
- *
- * @param key
- * the key of the mapping to remove.
- * @return the value of the removed mapping or {@code null} if no mapping
- * for the specified key was found.
- * @throws ClassCastException
- * if the specified key cannot be compared with the keys in this
- * map.
- * @throws NullPointerException
- * if the specified key is {@code null} and the comparator
- * cannot handle {@code null} keys.
- */
- @Override
- public V remove(Object key) {
- if (size == 0) {
- return null;
+ if (toBound == NO_BOUND) {
+ to = this.to;
+ toBound = this.toBound;
+ } else if (this.toBound != NO_BOUND) {
+ int comparison = comparator.compare(to, this.to);
+ if (comparison > 0 || (comparison == 0
+ && toBound == EXCLUSIVE && this.toBound == INCLUSIVE)) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ return new BoundedMap(ascending, from, fromBound, to, toBound);
}
- Comparable<K> object = comparator == null ? toComparable((K) key) : null;
- K keyK = (K) key;
- Node<K, V> node = root;
- while (node != null) {
- K[] keys = node.keys;
- int left_idx = node.left_idx;
- int result = cmp(object, keyK, keys[left_idx]);
- if (result < 0) {
- node = node.left;
- } else if (result == 0) {
- V value = node.values[left_idx];
- removeLeftmost(node);
- return value;
- } else {
- int right_idx = node.right_idx;
- if (left_idx != right_idx) {
- result = cmp(object, keyK, keys[right_idx]);
+
+ /*
+ * Bounded view implementations.
+ */
+
+ abstract class BoundedIterator<T> extends MapIterator<T> {
+ protected BoundedIterator(Node<K, V> next) {
+ super(next);
+ }
+
+ @Override protected Node<K, V> stepForward() {
+ Node<K, V> result = super.stepForward();
+ if (next != null && !isInBounds(next.key, NO_BOUND, toBound)) {
+ next = null;
}
- if (result > 0) {
- node = node.right;
- } else if (result == 0) {
- V value = node.values[right_idx];
- removeRightmost(node);
- return value;
- } else { /*search in node*/
- int low = left_idx + 1, mid = 0, high = right_idx - 1;
- while (low <= high) {
- mid = (low + high) >>> 1;
- result = cmp(object, keyK, keys[mid]);
- if (result > 0) {
- low = mid + 1;
- } else if (result == 0) {
- V value = node.values[mid];
- removeMiddleElement(node, mid);
- return value;
- } else {
- high = mid - 1;
- }
- }
- return null;
+ return result;
+ }
+
+ @Override protected Node<K, V> stepBackward() {
+ Node<K, V> result = super.stepBackward();
+ if (next != null && !isInBounds(next.key, fromBound, NO_BOUND)) {
+ next = null;
}
+ return result;
}
}
- return null;
- }
- void removeLeftmost(Node<K, V> node) {
- int index = node.left_idx;
- if (node.size == 1) {
- deleteNode(node);
- } else if (node.prev != null && (Node.NODE_SIZE - 1 - node.prev.right_idx) > node.size) {
- // move all to prev node and kill it
- Node<K, V> prev = node.prev;
- int size = node.right_idx - index;
- System.arraycopy(node.keys, index + 1, prev.keys, prev.right_idx + 1, size);
- System.arraycopy(node.values, index + 1, prev.values, prev.right_idx + 1, size);
- prev.right_idx += size;
- prev.size += size;
- deleteNode(node);
- } else if (node.next != null && (node.next.left_idx) > node.size) {
- // move all to next node and kill it
- Node<K, V> next = node.next;
- int size = node.right_idx - index;
- int next_new_left = next.left_idx - size;
- next.left_idx = next_new_left;
- System.arraycopy(node.keys, index + 1, next.keys, next_new_left, size);
- System.arraycopy(node.values, index + 1, next.values, next_new_left, size);
- next.size += size;
- deleteNode(node);
- } else {
- node.keys[index] = null;
- node.values[index] = null;
- node.left_idx++;
- node.size--;
- Node<K, V> prev = node.prev;
- if (prev != null && prev.size == 1) {
- node.size++;
- node.left_idx--;
- node.keys [node.left_idx] = prev.keys [prev.left_idx];
- node.values[node.left_idx] = prev.values[prev.left_idx];
- deleteNode(prev);
+ final class BoundedEntrySet extends AbstractSet<Entry<K, V>> {
+ @Override public int size() {
+ return BoundedMap.this.size();
}
- }
- modCount++;
- size--;
- }
- void removeRightmost(Node<K, V> node) {
- int index = node.right_idx;
- if (node.size == 1) {
- deleteNode(node);
- } else if (node.prev != null && (Node.NODE_SIZE - 1 - node.prev.right_idx) > node.size) {
- // move all to prev node and kill it
- Node<K, V> prev = node.prev;
- int left_idx = node.left_idx;
- int size = index - left_idx;
- System.arraycopy(node.keys, left_idx, prev.keys, prev.right_idx + 1, size);
- System.arraycopy(node.values, left_idx, prev.values, prev.right_idx + 1, size);
- prev.right_idx += size;
- prev.size += size;
- deleteNode(node);
- } else if (node.next != null && (node.next.left_idx) > node.size) {
- // move all to next node and kill it
- Node<K, V> next = node.next;
- int left_idx = node.left_idx;
- int size = index - left_idx;
- int next_new_left = next.left_idx - size;
- next.left_idx = next_new_left;
- System.arraycopy(node.keys, left_idx, next.keys, next_new_left, size);
- System.arraycopy(node.values, left_idx, next.values, next_new_left, size);
- next.size += size;
- deleteNode(node);
- } else {
- node.keys[index] = null;
- node.values[index] = null;
- node.right_idx--;
- node.size--;
- Node<K, V> next = node.next;
- if (next != null && next.size == 1) {
- node.size++;
- node.right_idx++;
- node.keys[node.right_idx] = next.keys[next.left_idx];
- node.values[node.right_idx] = next.values[next.left_idx];
- deleteNode(next);
+ @Override public boolean isEmpty() {
+ return BoundedMap.this.isEmpty();
}
- }
- modCount++;
- size--;
- }
- void removeMiddleElement(Node<K, V> node, int index) {
- // this function is called iff index if some middle element;
- // so node.left_idx < index < node.right_idx
- // condition above assume that node.size > 1
- if (node.prev != null && (Node.NODE_SIZE - 1 - node.prev.right_idx) > node.size) {
- // move all to prev node and kill it
- Node<K, V> prev = node.prev;
- int left_idx = node.left_idx;
- int size = index - left_idx;
- System.arraycopy(node.keys, left_idx, prev.keys, prev.right_idx + 1, size);
- System.arraycopy(node.values, left_idx, prev.values, prev.right_idx + 1, size);
- prev.right_idx += size;
- size = node.right_idx - index;
- System.arraycopy(node.keys, index + 1, prev.keys, prev.right_idx + 1, size);
- System.arraycopy(node.values, index + 1, prev.values, prev.right_idx + 1, size);
- prev.right_idx += size;
- prev.size += (node.size - 1);
- deleteNode(node);
- } else if (node.next != null && (node.next.left_idx) > node.size) {
- // move all to next node and kill it
- Node<K, V> next = node.next;
- int left_idx = node.left_idx;
- int next_new_left = next.left_idx - node.size + 1;
- next.left_idx = next_new_left;
- int size = index - left_idx;
- System.arraycopy(node.keys, left_idx, next.keys, next_new_left, size);
- System.arraycopy(node.values, left_idx, next.values, next_new_left, size);
- next_new_left += size;
- size = node.right_idx - index;
- System.arraycopy(node.keys, index + 1, next.keys, next_new_left, size);
- System.arraycopy(node.values, index + 1, next.values, next_new_left, size);
- next.size += (node.size - 1);
- deleteNode(node);
- } else {
- int moveFromRight = node.right_idx - index;
- int left_idx = node.left_idx;
- int moveFromLeft = index - left_idx ;
- if (moveFromRight <= moveFromLeft) {
- System.arraycopy(node.keys, index + 1, node.keys, index, moveFromRight);
- System.arraycopy(node.values, index + 1, node.values, index, moveFromRight);
- Node<K, V> next = node.next;
- if (next != null && next.size == 1) {
- node.keys [node.right_idx] = next.keys [next.left_idx];
- node.values[node.right_idx] = next.values[next.left_idx];
- deleteNode(next);
- } else {
- node.keys [node.right_idx] = null;
- node.values[node.right_idx] = null;
- node.right_idx--;
- node.size--;
- }
- } else {
- System.arraycopy(node.keys, left_idx , node.keys, left_idx + 1, moveFromLeft);
- System.arraycopy(node.values, left_idx , node.values, left_idx + 1, moveFromLeft);
- Node<K, V> prev = node.prev;
- if (prev != null && prev.size == 1) {
- node.keys [left_idx ] = prev.keys [prev.left_idx];
- node.values[left_idx ] = prev.values[prev.left_idx];
- deleteNode(prev);
- } else {
- node.keys [left_idx ] = null;
- node.values[left_idx ] = null;
- node.left_idx++;
- node.size--;
+ @Override public Iterator<Entry<K, V>> iterator() {
+ return new BoundedIterator<Entry<K, V>>((Node<K, V>) firstEntry()) {
+ public Entry<K, V> next() {
+ return ascending ? stepForward() : stepBackward();
+ }
+ };
+ }
+
+ @Override public boolean contains(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
}
+ Entry<?, ?> entry = (Entry<?, ?>) o;
+ return isInBounds(entry.getKey()) && findByEntry(entry) != null;
}
- }
- modCount++;
- size--;
- }
- void removeFromIterator(Node<K, V> node, int index) {
- if (node.size == 1) {
- // it is safe to delete the whole node here.
- // iterator already moved to the next node;
- deleteNode(node);
- } else {
- int left_idx = node.left_idx;
- if (index == left_idx) {
- Node<K, V> prev = node.prev;
- if (prev != null && prev.size == 1) {
- node.keys [left_idx] = prev.keys [prev.left_idx];
- node.values[left_idx] = prev.values[prev.left_idx];
- deleteNode(prev);
- } else {
- node.keys [left_idx] = null;
- node.values[left_idx] = null;
- node.left_idx++;
- node.size--;
+ @Override public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
}
- } else if (index == node.right_idx) {
- node.keys [index] = null;
- node.values[index] = null;
- node.right_idx--;
- node.size--;
- } else {
- int moveFromRight = node.right_idx - index;
- int moveFromLeft = index - left_idx;
- if (moveFromRight <= moveFromLeft) {
- System.arraycopy(node.keys, index + 1, node.keys, index, moveFromRight );
- System.arraycopy(node.values, index + 1, node.values, index, moveFromRight );
- node.keys [node.right_idx] = null;
- node.values[node.right_idx] = null;
- node.right_idx--;
- node.size--;
- } else {
- System.arraycopy(node.keys, left_idx, node.keys, left_idx+ 1, moveFromLeft);
- System.arraycopy(node.values, left_idx, node.values, left_idx+ 1, moveFromLeft);
- node.keys [left_idx] = null;
- node.values[left_idx] = null;
- node.left_idx++;
- node.size--;
- }
+ Entry<?, ?> entry = (Entry<?, ?>) o;
+ return isInBounds(entry.getKey()) && TreeMap.this.entrySet().remove(entry);
}
}
- modCount++;
- size--;
- }
- private void deleteNode(Node<K, V> node) {
- if (node.right == null) {
- if (node.left != null) {
- attachToParent(node, node.left);
- } else {
- attachNullToParent(node);
- }
- fixNextChain(node);
- } else if(node.left == null) { // node.right != null
- attachToParent(node, node.right);
- fixNextChain(node);
- } else {
- // Here node.left!=nul && node.right!=null
- // node.next should replace node in tree
- // node.next!=null by tree logic.
- // node.next.left==null by tree logic.
- // node.next.right may be null or non-null
- Node<K, V> toMoveUp = node.next;
- fixNextChain(node);
- if(toMoveUp.right==null){
- attachNullToParent(toMoveUp);
- } else {
- attachToParent(toMoveUp, toMoveUp.right);
+ final class BoundedKeySet extends AbstractSet<K> implements NavigableSet<K> {
+ @Override public int size() {
+ return BoundedMap.this.size();
}
- // Here toMoveUp is ready to replace node
- toMoveUp.left = node.left;
- if (node.left != null) {
- node.left.parent = toMoveUp;
+
+ @Override public boolean isEmpty() {
+ return BoundedMap.this.isEmpty();
}
- toMoveUp.right = node.right;
- if (node.right != null) {
- node.right.parent = toMoveUp;
+
+ @Override public Iterator<K> iterator() {
+ return new BoundedIterator<K>((Node<K, V>) firstEntry()) {
+ public K next() {
+ return (ascending ? stepForward() : stepBackward()).key;
+ }
+ };
}
- attachToParentNoFixup(node,toMoveUp);
- toMoveUp.color = node.color;
- }
- }
- private void attachToParentNoFixup(Node<K, V> toDelete, Node<K, V> toConnect) {
- // assert toConnect!=null
- Node<K,V> parent = toDelete.parent;
- toConnect.parent = parent;
- if (parent == null) {
- root = toConnect;
- } else if (toDelete == parent.left) {
- parent.left = toConnect;
- } else {
- parent.right = toConnect;
- }
- }
+ public Iterator<K> descendingIterator() {
+ return new BoundedIterator<K>((Node<K, V>) lastEntry()) {
+ public K next() {
+ return (ascending ? stepBackward() : stepForward()).key;
+ }
+ };
+ }
- private void attachToParent(Node<K, V> toDelete, Node<K, V> toConnect) {
- // assert toConnect!=null
- attachToParentNoFixup(toDelete,toConnect);
- if (!toDelete.color) {
- fixup(toConnect);
- }
- }
+ @Override public boolean contains(Object key) {
+ return isInBounds(key) && findByObject(key) != null;
+ }
- private void attachNullToParent(Node<K, V> toDelete) {
- Node<K, V> parent = toDelete.parent;
- if (parent == null) {
- root = null;
- } else {
- if (toDelete == parent.left) {
- parent.left = null;
- } else {
- parent.right = null;
+ @Override public boolean remove(Object key) {
+ return isInBounds(key) && removeInternalByKey(key) != null;
}
- if (!toDelete.color) {
- fixup(parent);
+
+ /*
+ * Navigable methods.
+ */
+
+ public K first() {
+ return firstKey();
}
- }
- }
- private void fixNextChain(Node<K, V> node) {
- if (node.prev != null) {
- node.prev.next = node.next;
- }
- if (node.next != null) {
- node.next.prev = node.prev;
- }
- }
+ public K pollFirst() {
+ Entry<K, ?> entry = pollFirstEntry();
+ return entry != null ? entry.getKey() : null;
+ }
- private void fixup(Node<K, V> x) {
- Node<K, V> w;
- while (x != root && !x.color) {
- if (x == x.parent.left) {
- w = x.parent.right;
- if (w == null) {
- x = x.parent;
- continue;
- }
- if (w.color) {
- w.color = false;
- x.parent.color = true;
- leftRotate(x.parent);
- w = x.parent.right;
- if (w == null) {
- x = x.parent;
- continue;
- }
- }
- if ((w.left == null || !w.left.color)
- && (w.right == null || !w.right.color)) {
- w.color = true;
- x = x.parent;
- } else {
- if (w.right == null || !w.right.color) {
- w.left.color = false;
- w.color = true;
- rightRotate(w);
- w = x.parent.right;
- }
- w.color = x.parent.color;
- x.parent.color = false;
- w.right.color = false;
- leftRotate(x.parent);
- x = root;
- }
- } else {
- w = x.parent.left;
- if (w == null) {
- x = x.parent;
- continue;
- }
- if (w.color) {
- w.color = false;
- x.parent.color = true;
- rightRotate(x.parent);
- w = x.parent.left;
- if (w == null) {
- x = x.parent;
- continue;
- }
- }
- if ((w.left == null || !w.left.color)
- && (w.right == null || !w.right.color)) {
- w.color = true;
- x = x.parent;
- } else {
- if (w.left == null || !w.left.color) {
- w.right.color = false;
- w.color = true;
- leftRotate(w);
- w = x.parent.left;
- }
- w.color = x.parent.color;
- x.parent.color = false;
- w.left.color = false;
- rightRotate(x.parent);
- x = root;
- }
+ public K last() {
+ return lastKey();
+ }
+
+ public K pollLast() {
+ Entry<K, ?> entry = pollLastEntry();
+ return entry != null ? entry.getKey() : null;
+ }
+
+ public K lower(K key) {
+ return lowerKey(key);
+ }
+
+ public K floor(K key) {
+ return floorKey(key);
+ }
+
+ public K ceiling(K key) {
+ return ceilingKey(key);
+ }
+
+ public K higher(K key) {
+ return higherKey(key);
+ }
+
+ public Comparator<? super K> comparator() {
+ return BoundedMap.this.comparator();
+ }
+
+ /*
+ * View factory methods.
+ */
+
+ public NavigableSet<K> subSet(K from, boolean fromInclusive, K to, boolean toInclusive) {
+ return subMap(from, fromInclusive, to, toInclusive).navigableKeySet();
+ }
+
+ public SortedSet<K> subSet(K fromInclusive, K toExclusive) {
+ return subMap(fromInclusive, toExclusive).navigableKeySet();
+ }
+
+ public NavigableSet<K> headSet(K to, boolean inclusive) {
+ return headMap(to, inclusive).navigableKeySet();
+ }
+
+ public SortedSet<K> headSet(K toExclusive) {
+ return headMap(toExclusive).navigableKeySet();
+ }
+
+ public NavigableSet<K> tailSet(K from, boolean inclusive) {
+ return tailMap(from, inclusive).navigableKeySet();
+ }
+
+ public SortedSet<K> tailSet(K fromInclusive) {
+ return tailMap(fromInclusive).navigableKeySet();
+ }
+
+ public NavigableSet<K> descendingSet() {
+ return new BoundedMap(!ascending, from, fromBound, to, toBound).navigableKeySet();
}
}
- x.color = false;
- }
+ Object writeReplace() throws ObjectStreamException {
+ return ascending
+ ? new AscendingSubMap<K, V>(TreeMap.this, from, fromBound, to, toBound)
+ : new DescendingSubMap<K, V>(TreeMap.this, from, fromBound, to, toBound);
+ }
+ }
/**
- * Returns the number of mappings in this map.
- *
- * @return the number of mappings in this map.
+ * Returns the number of elements in the iteration.
*/
- @Override
- public int size() {
- return size;
+ static int count(Iterator<?> iterator) {
+ int count = 0;
+ while (iterator.hasNext()) {
+ iterator.next();
+ count++;
+ }
+ return count;
}
- /**
- * Returns a sorted map over a range of this sorted map with all keys
- * greater than or equal to the specified {@code startKey} and less than the
- * specified {@code endKey}. Changes to the returned sorted map are
- * reflected in this sorted map and vice versa.
- * <p>
- * Note: The returned map will not allow an insertion of a key outside the
- * specified range.
- *
- * @param startKey
- * the low boundary of the range (inclusive).
- * @param endKey
- * the high boundary of the range (exclusive),
- * @return a sorted map with the key from the specified range.
- * @throws ClassCastException
- * if the start or end key cannot be compared with the keys in
- * this map.
- * @throws NullPointerException
- * if the start or end key is {@code null} and the comparator
- * cannot handle {@code null} keys.
- * @throws IllegalArgumentException
- * if the start key is greater than the end key, or if this map
- * is itself a sorted map over a range of another sorted map and
- * the specified range is outside of its range.
+ /*
+ * Serialization
*/
- public SortedMap<K, V> subMap(K startKey, K endKey) {
- if (comparator == null) {
- if (toComparable(startKey).compareTo(endKey) <= 0) {
- return new SubMap<K, V>(startKey, this, endKey);
- }
- } else {
- if (comparator.compare(startKey, endKey) <= 0) {
- return new SubMap<K, V>(startKey, this, endKey);
- }
+
+ private static final long serialVersionUID = 919286545866124006L;
+
+ private void writeObject(ObjectOutputStream stream) throws IOException {
+ stream.putFields().put("comparator", comparator != NATURAL_ORDER ? comparator : null);
+ stream.writeFields();
+ stream.writeInt(size);
+ for (Map.Entry<K, V> entry : entrySet()) {
+ stream.writeObject(entry.getKey());
+ stream.writeObject(entry.getValue());
}
- throw new IllegalArgumentException();
}
- /**
- * Returns a sorted map over a range of this sorted map with all keys that
- * are greater than or equal to the specified {@code startKey}. Changes to
- * the returned sorted map are reflected in this sorted map and vice versa.
- * <p>
- * Note: The returned map will not allow an insertion of a key outside the
- * specified range.
- *
- * @param startKey
- * the low boundary of the range specified.
- * @return a sorted map where the keys are greater or equal to
- * {@code startKey}.
- * @throws ClassCastException
- * if the specified key cannot be compared with the keys in this
- * map.
- * @throws NullPointerException
- * if the specified key is {@code null} and the comparator
- * cannot handle {@code null} keys.
- * @throws IllegalArgumentException
- * if this map itself a sorted map over a range of another map
- * and the specified key is outside of its range.
- */
- public SortedMap<K, V> tailMap(K startKey) {
- // Check for errors
+ @SuppressWarnings("unchecked") // we have to trust that keys are Ks and values are Vs
+ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ GetField fields = stream.readFields();
+ comparator = (Comparator<? super K>) fields.get("comparator", null);
if (comparator == null) {
- toComparable(startKey).compareTo(startKey);
- } else {
- comparator.compare(startKey, startKey);
+ comparator = (Comparator<? super K>) NATURAL_ORDER;
+ }
+ int size = stream.readInt();
+ for (int i = 0; i < size; i++) {
+ putInternal((K) stream.readObject(), (V) stream.readObject());
}
- return new SubMap<K, V>(startKey, this);
}
- /**
- * Returns a collection of the values contained in this map. The collection
- * is backed by this map so changes to one are reflected by the other. The
- * collection supports remove, removeAll, retainAll and clear operations,
- * and it does not support add or addAll operations.
- * <p>
- * This method returns a collection which is the subclass of
- * AbstractCollection. The iterator method of this subclass returns a
- * "wrapper object" over the iterator of map's entrySet(). The {@code size}
- * method wraps the map's size method and the {@code contains} method wraps
- * the map's containsValue method.
- * <p>
- * The collection is created when this method is called for the first time
- * and returned in response to all subsequent calls. This method may return
- * different collections when multiple concurrent calls occur, since no
- * synchronization is performed.
- *
- * @return a collection of the values contained in this map.
- */
- @Override
- public Collection<V> values() {
- if (valuesCollection == null) {
- valuesCollection = new AbstractCollection<V>() {
- @Override
- public boolean contains(Object object) {
- return containsValue(object);
- }
+ static abstract class NavigableSubMap<K, V> extends AbstractMap<K, V> implements Serializable {
+ TreeMap<K, V> m;
+ Object lo;
+ Object hi;
+ boolean fromStart;
+ boolean toEnd;
+ boolean loInclusive;
+ boolean hiInclusive;
- @Override
- public int size() {
- return size;
- }
+ NavigableSubMap(TreeMap<K, V> delegate, K from, Bound fromBound, K to, Bound toBound) {
+ this.m = delegate;
+ this.lo = from;
+ this.hi = to;
+ this.fromStart = fromBound != NO_BOUND;
+ this.toEnd = toBound != NO_BOUND;
+ this.loInclusive = fromBound == INCLUSIVE;
+ this.hiInclusive = toBound == INCLUSIVE;
+ }
- @Override
- public void clear() {
- TreeMap.this.clear();
- }
+ @Override public Set<Entry<K, V>> entrySet() {
+ throw new UnsupportedOperationException();
+ }
- @Override
- public Iterator<V> iterator() {
- return new UnboundedValueIterator<K, V>(TreeMap.this);
- }
- };
+ @SuppressWarnings("unchecked") // we have to trust that the bounds are Ks
+ private Object readResolve() throws ObjectStreamException {
+ Bound fromBound = fromStart ? (loInclusive ? INCLUSIVE : EXCLUSIVE) : NO_BOUND;
+ Bound toBound = toEnd ? (hiInclusive ? INCLUSIVE : EXCLUSIVE) : NO_BOUND;
+ boolean ascending = !(this instanceof DescendingSubMap);
+ return m.new BoundedMap(ascending, (K) lo, fromBound, (K) hi, toBound);
}
- return valuesCollection;
}
- private void writeObject(ObjectOutputStream stream) throws IOException {
- stream.defaultWriteObject();
- stream.writeInt(size);
- if (size > 0) {
- Node<K, V> node = minimum(root);
- while (node != null) {
- int to = node.right_idx;
- for (int i = node.left_idx; i <= to; i++) {
- stream.writeObject(node.keys[i]);
- stream.writeObject(node.values[i]);
- }
- node = node.next;
- }
+ static class DescendingSubMap<K, V> extends NavigableSubMap<K, V> {
+ private static final long serialVersionUID = 912986545866120460L;
+ Comparator<K> reverseComparator;
+ DescendingSubMap(TreeMap<K, V> delegate, K from, Bound fromBound, K to, Bound toBound) {
+ super(delegate, from, fromBound, to, toBound);
}
}
- @SuppressWarnings("unchecked")
- private void readObject(ObjectInputStream stream) throws IOException,
- ClassNotFoundException {
- stream.defaultReadObject();
- int size = stream.readInt();
- Node<K, V> lastNode = null;
- for (int i = 0; i < size; i++) {
- lastNode = addToLast(lastNode, (K) stream.readObject(), (V) stream.readObject());
+ static class AscendingSubMap<K, V> extends NavigableSubMap<K, V> {
+ private static final long serialVersionUID = 912986545866124060L;
+ AscendingSubMap(TreeMap<K, V> delegate, K from, Bound fromBound, K to, Bound toBound) {
+ super(delegate, from, fromBound, to, toBound);
+ }
+ }
+
+ static class SubMap<K, V> extends AbstractMap<K, V> implements Serializable {
+ private static final long serialVersionUID = -6520786458950516097L;
+ TreeMap<K, V> m;
+ Object lo;
+ Object hi;
+ boolean fromStart;
+ boolean toEnd;
+
+ @Override public Set<Entry<K, V>> entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked") // we have to trust that the bounds are Ks
+ private Object readResolve() throws ObjectStreamException {
+ Bound fromBound = fromStart ? INCLUSIVE : NO_BOUND;
+ Bound toBound = toEnd ? EXCLUSIVE : NO_BOUND;
+ return m.new BoundedMap(true, (K) lo, fromBound, (K) hi, toBound);
}
}
}
diff --git a/luni/src/main/java/java/util/TreeSet.java b/luni/src/main/java/java/util/TreeSet.java
index b6ed5ff..98f6303 100644
--- a/luni/src/main/java/java/util/TreeSet.java
+++ b/luni/src/main/java/java/util/TreeSet.java
@@ -377,12 +377,9 @@ public class TreeSet<E> extends AbstractSet<E> implements SortedSet<E>,
TreeMap<E, Object> map = new TreeMap<E, Object>(
(Comparator<? super E>) stream.readObject());
int size = stream.readInt();
- if (size > 0) {
- TreeMap.Node<E, Object> lastNode = null;
- for(int i=0; i<size; i++) {
- E elem = (E)stream.readObject();
- lastNode = map.addToLast(lastNode,elem, Boolean.TRUE);
- }
+ for (int i = 0; i < size; i++) {
+ E elem = (E)stream.readObject();
+ map.put(elem, Boolean.TRUE);
}
backingMap = map;
}
diff --git a/luni/src/test/java/java/util/AllTests.java b/luni/src/test/java/java/util/AllTests.java
index 487a169..6539162 100644
--- a/luni/src/test/java/java/util/AllTests.java
+++ b/luni/src/test/java/java/util/AllTests.java
@@ -29,6 +29,7 @@ public class AllTests {
suite.addTestSuite(java.util.RandomTest.class);
suite.addTestSuite(java.util.ServiceLoaderTest.class);
suite.addTestSuite(java.util.TimeZoneTest.class);
+ suite.addTestSuite(java.util.TreeMapTest.class);
return suite;
}
}
diff --git a/luni/src/test/java/java/util/SerializableTester.java b/luni/src/test/java/java/util/SerializableTester.java
new file mode 100644
index 0000000..de98d96
--- /dev/null
+++ b/luni/src/test/java/java/util/SerializableTester.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+import junit.framework.AssertionFailedError;
+
+public class SerializableTester<T> {
+
+ private final String golden;
+ private final T value;
+
+ public SerializableTester(T value, String golden) {
+ this.golden = golden;
+ this.value = value;
+ }
+
+ protected void verify(T deserialized) {}
+
+ public void test() {
+ try {
+ if (golden == null || golden.length() == 0) {
+ fail("No golden value supplied! Consider using this: "
+ + hexEncode(serialize(value)));
+ }
+
+ // just a sanity check! if this fails, verify() is probably broken
+ verify(value);
+
+ @SuppressWarnings("unchecked") // deserialize should return the proper type
+ T deserialized = (T) deserialize(hexDecode(golden));
+ assertEquals("User-constructed value doesn't equal deserialized golden value",
+ value, deserialized);
+ verify(deserialized);
+
+ @SuppressWarnings("unchecked") // deserialize should return the proper type
+ T reserialized = (T) deserialize(serialize(value));
+ assertEquals("User-constructed value doesn't equal itself, reserialized",
+ value, reserialized);
+ verify(reserialized);
+
+ } catch (Exception e) {
+ Error failure = new AssertionFailedError();
+ failure.initCause(e);
+ throw failure;
+ }
+ }
+
+ private byte[] serialize(Object object) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ObjectOutputStream(out).writeObject(object);
+ return out.toByteArray();
+ }
+
+ private Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
+ ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ Object result = in.readObject();
+ assertEquals(-1, in.read());
+ return result;
+ }
+
+ private String hexEncode(byte[] bytes) {
+ StringBuilder result = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ result.append(String.format("%02x", b));
+ }
+ return result.toString();
+ }
+
+ private byte[] hexDecode(String s) {
+ byte[] result = new byte[s.length() / 2];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = (byte) Integer.parseInt(s.substring(i*2, i*2 + 2), 16);
+ }
+ return result;
+ }
+}
+
diff --git a/luni/src/test/java/java/util/TreeMapTest.java b/luni/src/test/java/java/util/TreeMapTest.java
new file mode 100644
index 0000000..2ce0bb5
--- /dev/null
+++ b/luni/src/test/java/java/util/TreeMapTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Map.Entry;
+import junit.framework.TestCase;
+
+public class TreeMapTest extends TestCase {
+
+ public void testConcurrentModificationDetection() {
+ Map<String, String> map = new TreeMap<String, String>();
+ map.put("A", "a");
+ map.put("B", "b");
+ map.put("C", "c");
+ Iterator<Entry<String,String>> iterator = map.entrySet().iterator();
+ iterator.next();
+ iterator.next();
+ iterator.remove();
+ map.put("D", "d");
+ try {
+ iterator.next();
+ fail();
+ } catch (ConcurrentModificationException e) {
+ }
+ }
+
+ public void testIteratorRemoves() {
+ Map<String, String> map = new TreeMap<String, String>();
+ map.put("A", "a");
+ map.put("B", "b");
+ map.put("C", "c");
+ Iterator<Entry<String,String>> iterator = map.entrySet().iterator();
+ assertEquals("A", iterator.next().getKey());
+ assertEquals("B", iterator.next().getKey());
+ iterator.remove();
+ assertEquals("C", iterator.next().getKey());
+ iterator.remove();
+ assertFalse(iterator.hasNext());
+ }
+
+ /**
+ * Test that entry set contains and removal use the comparator rather than equals.
+ */
+ public void testEntrySetUsesComparatorOnly() {
+ Map<String,String> map = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+ map.put("ABC", "a");
+ assertTrue(map.entrySet().contains(new SimpleEntry<String, String>("abc", "a")));
+ assertTrue(map.entrySet().remove(new SimpleEntry<String, String>("abc", "a")));
+ assertEquals(0, map.size());
+ }
+
+ public void testMapConstructorPassingSortedMap() {
+ Map<String,String> source = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+ NavigableMap<String,String> copy = new TreeMap<String, String>(source);
+ assertEquals(null, copy.comparator());
+ }
+
+ public void testNullsWithNaturalOrder() {
+ HashMap<String, String> copyFrom = new HashMap<String, String>();
+ copyFrom.put(null, "b");
+ try {
+ new TreeMap<String, String>(copyFrom);
+ fail(); // the RI doesn't throw if null is the only key
+ } catch (NullPointerException expected) {
+ }
+
+ TreeMap<String,String> map = new TreeMap<String, String>();
+ try {
+ map.put(null, "b");
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ map.descendingMap().put(null, "b");
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ map.subMap("a", "z").put(null, "b");
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ public void testClassCastExceptions() {
+ Map<Object, Object> map = new TreeMap<Object, Object>();
+ map.put("A", "a");
+ try {
+ map.get(5);
+ fail();
+ } catch (ClassCastException e) {
+ }
+ try {
+ map.containsKey(5);
+ fail();
+ } catch (ClassCastException e) {
+ }
+ try {
+ map.remove(5);
+ fail();
+ } catch (ClassCastException e) {
+ }
+ }
+
+ public void testClassCastExceptionsOnBounds() {
+ Map<Object, Object> map = new TreeMap<Object, Object>().subMap("a", "z");
+ try {
+ map.get(5);
+ fail();
+ } catch (ClassCastException e) {
+ }
+ try {
+ map.containsKey(5);
+ fail();
+ } catch (ClassCastException e) {
+ }
+ try {
+ map.remove(5);
+ fail();
+ } catch (ClassCastException e) {
+ }
+ }
+
+ public void testClone() {
+ TreeMap<String, String> map = new TreeMap<String, String>() {
+ @Override public String toString() {
+ return "x:" + super.toString();
+ }
+ };
+
+ map.put("A", "a");
+ map.put("B", "b");
+
+ @SuppressWarnings("unchecked")
+ TreeMap<String, String> clone = (TreeMap<String, String>) map.clone();
+ assertEquals(map.getClass(), clone.getClass());
+ assertEquals("x:{A=a, B=b}", map.toString());
+ }
+
+ public void testEmptyMapSerialization() {
+ String s = "aced0005737200166578616d706c65732e6a657373652e547265654d61700cc1f"
+ + "63e2d256ae60300014c000a636f6d70617261746f727400164c6a6176612f75746"
+ + "96c2f436f6d70617261746f723b78707077040000000078";
+ TreeMap<String, String> map = new TreeMap<String, String>();
+ new SerializableTester<TreeMap<String, String>>(map, s).test();
+ }
+
+ public void testSerializationWithComparator() {
+ String s = "aced0005737200116a6176612e7574696c2e547265654d61700cc1f63e2d256a"
+ + "e60300014c000a636f6d70617261746f727400164c6a6176612f7574696c2f436"
+ + "f6d70617261746f723b78707372002a6a6176612e6c616e672e537472696e6724"
+ + "43617365496e73656e736974697665436f6d70617261746f7277035c7d5c50e5c"
+ + "e02000078707704000000027400016171007e00057400016271007e000678";
+ TreeMap<String,String> map = new TreeMap<String, String>(
+ String.CASE_INSENSITIVE_ORDER);
+ map.put("a", "a");
+ map.put("b", "b");
+ new SerializableTester<NavigableMap<String, String>>(map, s) {
+ @Override protected void verify(NavigableMap<String, String> deserialized) {
+ assertEquals(0, deserialized.comparator().compare("X", "x"));
+ }
+ }.test();
+ }
+
+ public void testSubmapSerialization() {
+ String s = "aced0005737200216a6176612e7574696c2e547265654d617024417363656e646"
+ + "96e675375624d61700cab946d1f0fab1c020000787200216a6176612e7574696c2"
+ + "e547265654d6170244e6176696761626c655375624d6170e2d0a70e64210e08020"
+ + "0075a000966726f6d53746172745a000b6869496e636c75736976655a000b6c6f4"
+ + "96e636c75736976655a0005746f456e644c000268697400124c6a6176612f6c616"
+ + "e672f4f626a6563743b4c00026c6f71007e00024c00016d7400134c6a6176612f7"
+ + "574696c2f547265654d61703b7870000001007400016374000161737200116a617"
+ + "6612e7574696c2e547265654d61700cc1f63e2d256ae60300014c000a636f6d706"
+ + "17261746f727400164c6a6176612f7574696c2f436f6d70617261746f723b78707"
+ + "372002a6a6176612e6c616e672e537472696e672443617365496e73656e7369746"
+ + "97665436f6d70617261746f7277035c7d5c50e5ce0200007870770400000004710"
+ + "07e000671007e00067400016271007e000c71007e000571007e000574000164710"
+ + "07e000d78";
+ TreeMap<String,String> map = new TreeMap<String, String>(
+ String.CASE_INSENSITIVE_ORDER);
+ map.put("a", "a");
+ map.put("b", "b");
+ map.put("c", "c");
+ map.put("d", "d");
+ SortedMap<String, String> submap = map.subMap("a", "c");
+ new SerializableTester<SortedMap<String, String>>(submap, s) {
+ @Override protected void verify(SortedMap<String, String> deserialized) {
+ try {
+ deserialized.put("e", "e");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ }.test();
+ }
+
+ public void testNavigableSubmapSerialization() {
+ String s = "aced0005737200216a6176612e7574696c2e547265654d617024417363656e646"
+ + "96e675375624d61700cab946d1f0fab1c020000787200216a6176612e7574696c2"
+ + "e547265654d6170244e6176696761626c655375624d6170e2d0a70e64210e08020"
+ + "0075a000966726f6d53746172745a000b6869496e636c75736976655a000b6c6f4"
+ + "96e636c75736976655a0005746f456e644c000268697400124c6a6176612f6c616"
+ + "e672f4f626a6563743b4c00026c6f71007e00024c00016d7400134c6a6176612f7"
+ + "574696c2f547265654d61703b7870000100007400016374000161737200116a617"
+ + "6612e7574696c2e547265654d61700cc1f63e2d256ae60300014c000a636f6d706"
+ + "17261746f727400164c6a6176612f7574696c2f436f6d70617261746f723b78707"
+ + "372002a6a6176612e6c616e672e537472696e672443617365496e73656e7369746"
+ + "97665436f6d70617261746f7277035c7d5c50e5ce0200007870770400000004710"
+ + "07e000671007e00067400016271007e000c71007e000571007e000574000164710"
+ + "07e000d78";
+ TreeMap<String,String> map = new TreeMap<String, String>(
+ String.CASE_INSENSITIVE_ORDER);
+ map.put("a", "a");
+ map.put("b", "b");
+ map.put("c", "c");
+ map.put("d", "d");
+ SortedMap<String, String> submap = map.subMap("a", false, "c", true);
+ new SerializableTester<SortedMap<String, String>>(submap, s) {
+ @Override protected void verify(SortedMap<String, String> deserialized) {
+ try {
+ deserialized.put("e", "e");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ }.test();
+ }
+
+ public void testDescendingMapSerialization() {
+ String s = "aced0005737200226a6176612e7574696c2e547265654d61702444657363656e6"
+ + "4696e675375624d61700cab946d1f0f9d0c0200014c001172657665727365436f6"
+ + "d70617261746f727400164c6a6176612f7574696c2f436f6d70617261746f723b7"
+ + "87200216a6176612e7574696c2e547265654d6170244e6176696761626c6553756"
+ + "24d6170e2d0a70e64210e080200075a000966726f6d53746172745a000b6869496"
+ + "e636c75736976655a000b6c6f496e636c75736976655a0005746f456e644c00026"
+ + "8697400124c6a6176612f6c616e672f4f626a6563743b4c00026c6f71007e00034"
+ + "c00016d7400134c6a6176612f7574696c2f547265654d61703b787001010101707"
+ + "0737200116a6176612e7574696c2e547265654d61700cc1f63e2d256ae60300014"
+ + "c000a636f6d70617261746f7271007e000178707372002a6a6176612e6c616e672"
+ + "e537472696e672443617365496e73656e736974697665436f6d70617261746f727"
+ + "7035c7d5c50e5ce02000078707704000000027400016171007e000a74000162710"
+ + "07e000b78737200286a6176612e7574696c2e436f6c6c656374696f6e732452657"
+ + "665727365436f6d70617261746f7232000003fa6c354d510200014c0003636d707"
+ + "1007e0001787071007e0009";
+ TreeMap<String,String> map = new TreeMap<String, String>(
+ String.CASE_INSENSITIVE_ORDER);
+ map.put("a", "a");
+ map.put("b", "b");
+ NavigableMap<String, String> descendingMap = map.descendingMap();
+ new SerializableTester<NavigableMap<String, String>>(descendingMap, s) {
+ @Override protected void verify(NavigableMap<String, String> deserialized) {
+ assertEquals("b", deserialized.navigableKeySet().first());
+ }
+ }.test();
+ }
+}
+