diff --git a/src/java/org/apache/commons/collections/DoubleOrderedMap.java b/src/java/org/apache/commons/collections/DoubleOrderedMap.java
new file mode 100644
index 000000000..94009a4bb
--- /dev/null
+++ b/src/java/org/apache/commons/collections/DoubleOrderedMap.java
@@ -0,0 +1,2046 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DoubleOrderedMap.java,v 1.1 2002/01/20 04:36:08 craigmcc Exp $
+ * $Revision: 1.1 $
+ * $Date: 2002/01/20 04:36:08 $
+ *
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.commons.collections;
+
+
+
+import java.lang.reflect.Array;
+
+import java.util.*;
+
+
+/**
+* Red-Black tree-based implementation of Map. This class guarantees
+* that the map will be in both ascending key order and ascending
+* value order, sorted according to the natural order for the key's
+* and value's classes.
+*
+* This Map is intended for applications that need to be able to look
+* up a key-value pairing by either key or value, and need to do so
+* with equal efficiency.
+*
+* While that goal could be accomplished by taking a pair of TreeMaps
+* and redirecting requests to the appropriate TreeMap (e.g.,
+* containsKey would be directed to the TreeMap that maps values to
+* keys, containsValue would be directed to the TreeMap that maps keys
+* to values), there are problems with that implementation,
+* particularly when trying to keep the two TreeMaps synchronized with
+* each other. And if the data contained in the TreeMaps is large, the
+* cost of redundant storage becomes significant.
+*
+* This solution keeps the data properly synchronized and minimizes
+* the data storage. The red-black algorithm is based on TreeMap's,
+* but has been modified to simultaneously map a tree node by key and
+* by value. This doubles the cost of put operations (but so does
+* using two TreeMaps), and nearly doubles the cost of remove
+* operations (there is a savings in that the lookup of the node to be
+* removed only has to be performed once). And since only one node
+* contains the key and value, storage is significantly less than that
+* required by two TreeMaps.
+*
+* There are some limitations placed on data kept in this Map. The
+* biggest one is this:
+*
+* When performing a put operation, neither the key nor the value may
+* already exist in the Map. In the java.util Map implementations
+* (HashMap, TreeMap), you can perform a put with an already mapped
+* key, and neither cares about duplicate values at all ... but this
+* implementation's put method with throw an IllegalArgumentException
+* if either the key or the value is already in the Map.
+*
+* Obviously, that same restriction (and consequence of failing to
+* heed that restriction) applies to the putAll method.
+*
+* The Map.Entry instances returned by the appropriate methods will
+* not allow setValue() and will throw an
+* UnsupportedOperationException on attempts to call that method.
+*
+* New methods are added to take advantage of the fact that values are
+* kept sorted independently of their keys:
+*
+* Object getKeyForValue(Object value) is the opposite of get; it
+* takes a value and returns its key, if any.
+*
+* Object removeValue(Object value) finds and removes the specified
+* value and returns the now un-used key.
+*
+* Set entrySetByValue() returns the Map.Entry's in a Set whose
+* iterator will iterate over the Map.Entry's in ascending order by
+* their corresponding values.
+*
+* Set keySetByValue() returns the keys in a Set whose iterator will
+* iterate over the keys in ascending order by their corresponding
+* values.
+*
+* Collection valuesByValue() returns the values in a Collection whose
+* iterator will iterate over the values in ascending order.
+*
+* @author Marc Johnson (marcj at users dot sourceforge dot net)
+*/
+
+// final for performance
+public final class DoubleOrderedMap extends AbstractMap {
+
+ private Node[] rootNode = new Node[]{ null,
+ null };
+ private int nodeCount = 0;
+ private int modifications = 0;
+ private Set[] setOfKeys = new Set[]{ null,
+ null };
+ private Set[] setOfEntries = new Set[]{ null,
+ null };
+ private Collection[] collectionOfValues = new Collection[]{
+null,
+
+null };
+ private static final int KEY = 0;
+ private static final int VALUE = 1;
+ private static final int SUM_OF_INDICES = KEY + VALUE;
+ private static final int FIRST_INDEX = 0;
+ private static final int NUMBER_OF_INDICES = 2;
+ private static final String[] dataName = new String[]{ "key",
+ "value"
+};
+
+ /**
+ * Construct a new DoubleOrderedMap
+ */
+ public DoubleOrderedMap() {}
+
+ /**
+ * Constructs a new DoubleOrderedMap from an existing Map, with keys and
+ * values sorted
+ *
+ * @param map the map whose mappings are to be placed in this map.
+ *
+ * @exception ClassCastException if the keys in the map are not
+ * Comparable, or are not mutually
+ * comparable; also if the values in
+ * the map are not Comparable, or
+ * are not mutually Comparable
+ * @exception NullPointerException if any key or value in the map
+ * is null
+ * @exception IllegalArgumentException if there are duplicate keys
+ * or duplicate values in the
+ * map
+ */
+ public DoubleOrderedMap(final Map map)
+ throws ClassCastException, NullPointerException,
+ IllegalArgumentException {
+ putAll(map);
+ }
+
+ /**
+ * Returns the key to which this map maps the specified value.
+ * Returns null if the map contains no mapping for this value.
+ *
+ * @param value value whose associated key is to be returned.
+ *
+ * @return the key to which this map maps the specified value, or
+ * null if the map contains no mapping for this value.
+ *
+ * @exception ClassCastException if the value is of an
+ * inappropriate type for this map.
+ * @exception NullPointerException if the value is null
+ */
+ public Object getKeyForValue(final Object value)
+ throws ClassCastException, NullPointerException {
+ return doGet((Comparable) value, VALUE);
+ }
+
+ /**
+ * Removes the mapping for this value from this map if present
+ *
+ * @param value value whose mapping is to be removed from the map.
+ *
+ * @return previous key associated with specified value, or null
+ * if there was no mapping for value.
+ */
+ public Object removeValue(final Object value) {
+ return doRemove((Comparable) value, VALUE);
+ }
+
+ /**
+ * Returns a set view of the mappings contained in this map. Each
+ * element in the returned set is a Map.Entry. The set is backed
+ * by the map, so changes to the map are reflected in the set, and
+ * vice-versa. If the map is modified while an iteration over the
+ * set is in progress, the results of the iteration are
+ * undefined. The set supports element removal, which removes the
+ * corresponding mapping from the map, via the Iterator.remove,
+ * Set.remove, removeAll, retainAll and clear operations. It does
+ * not support the add or addAll operations.
+ *
+ * The difference between this method and entrySet is that
+ * entrySet's iterator() method returns an iterator that iterates
+ * over the mappings in ascending order by key. This method's
+ * iterator method iterates over the mappings in ascending order
+ * by value.
+ *
+ * @return a set view of the mappings contained in this map.
+ */
+ public Set entrySetByValue() {
+
+ if (setOfEntries[VALUE] == null) {
+ setOfEntries[VALUE] = new AbstractSet() {
+
+ public Iterator iterator() {
+
+ return new DoubleOrderedMapIterator(VALUE) {
+
+ protected Object doGetNext() {
+ return lastReturnedNode;
+ }
+ };
+ }
+
+ public boolean contains(Object o) {
+
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry entry = (Map.Entry) o;
+ Object key = entry.getKey();
+ Node node = lookup((Comparable) entry.getValue(),
+ VALUE);
+
+ return (node != null) && node.getData(KEY).equals(key);
+ }
+
+ public boolean remove(Object o) {
+
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry entry = (Map.Entry) o;
+ Object key = entry.getKey();
+ Node node = lookup((Comparable) entry.getValue(),
+ VALUE);
+
+ if ((node != null) && node.getData(KEY).equals(key)) {
+ doRedBlackDelete(node);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public int size() {
+ return DoubleOrderedMap.this.size();
+ }
+
+ public void clear() {
+ DoubleOrderedMap.this.clear();
+ }
+ };
+ }
+
+ return setOfEntries[VALUE];
+ }
+
+ /**
+ * Returns a set view of the keys contained in this map. The set
+ * is backed by the map, so changes to the map are reflected in
+ * the set, and vice-versa. If the map is modified while an
+ * iteration over the set is in progress, the results of the
+ * iteration are undefined. The set supports element removal,
+ * which removes the corresponding mapping from the map, via the
+ * Iterator.remove, Set.remove, removeAll, retainAll, and clear
+ * operations. It does not support the add or addAll
+ * operations.
+ *
+ * The difference between this method and keySet is that keySet's
+ * iterator() method returns an iterator that iterates over the
+ * keys in ascending order by key. This method's iterator method
+ * iterates over the keys in ascending order by value.
+ *
+ * @return a set view of the keys contained in this map.
+ */
+ public Set keySetByValue() {
+
+ if (setOfKeys[VALUE] == null) {
+ setOfKeys[VALUE] = new AbstractSet() {
+
+ public Iterator iterator() {
+
+ return new DoubleOrderedMapIterator(VALUE) {
+
+ protected Object doGetNext() {
+ return lastReturnedNode.getData(KEY);
+ }
+ };
+ }
+
+ public int size() {
+ return DoubleOrderedMap.this.size();
+ }
+
+ public boolean contains(Object o) {
+ return containsKey(o);
+ }
+
+ public boolean remove(Object o) {
+
+ int oldnodeCount = nodeCount;
+
+ DoubleOrderedMap.this.remove(o);
+
+ return nodeCount != oldnodeCount;
+ }
+
+ public void clear() {
+ DoubleOrderedMap.this.clear();
+ }
+ };
+ }
+
+ return setOfKeys[VALUE];
+ }
+
+ /**
+ * Returns a collection view of the values contained in this
+ * map. The collection is backed by the map, so changes to the map
+ * are reflected in the collection, and vice-versa. If the map is
+ * modified while an iteration over the collection is in progress,
+ * the results of the iteration are undefined. The collection
+ * supports element removal, which removes the corresponding
+ * mapping from the map, via the Iterator.remove,
+ * Collection.remove, removeAll, retainAll and clear operations.
+ * It does not support the add or addAll operations.
+ *
+ * The difference between this method and values is that values's
+ * iterator() method returns an iterator that iterates over the
+ * values in ascending order by key. This method's iterator method
+ * iterates over the values in ascending order by key.
+ *
+ * @return a collection view of the values contained in this map.
+ */
+ public Collection valuesByValue() {
+
+ if (collectionOfValues[VALUE] == null) {
+ collectionOfValues[VALUE] = new AbstractCollection() {
+
+ public Iterator iterator() {
+
+ return new DoubleOrderedMapIterator(VALUE) {
+
+ protected Object doGetNext() {
+ return lastReturnedNode.getData(VALUE);
+ }
+ };
+ }
+
+ public int size() {
+ return DoubleOrderedMap.this.size();
+ }
+
+ public boolean contains(Object o) {
+ return containsValue(o);
+ }
+
+ public boolean remove(Object o) {
+
+ int oldnodeCount = nodeCount;
+
+ removeValue(o);
+
+ return nodeCount != oldnodeCount;
+ }
+
+ public boolean removeAll(Collection c) {
+
+ boolean modified = false;
+ Iterator iter = c.iterator();
+
+ while (iter.hasNext()) {
+ if (removeValue(iter.next()) != null) {
+ modified = true;
+ }
+ }
+
+ return modified;
+ }
+
+ public void clear() {
+ DoubleOrderedMap.this.clear();
+ }
+ };
+ }
+
+ return collectionOfValues[VALUE];
+ }
+
+ /**
+ * common remove logic (remove by key or remove by value)
+ *
+ * @param o the key, or value, that we're looking for
+ * @param index KEY or VALUE
+ *
+ * @return the key, if remove by value, or the value, if remove by
+ * key. null if the specified key or value could not be
+ * found
+ */
+ private Object doRemove(final Comparable o, final int index) {
+
+ Node node = lookup(o, index);
+ Object rval = null;
+
+ if (node != null) {
+ rval = node.getData(oppositeIndex(index));
+
+ doRedBlackDelete(node);
+ }
+
+ return rval;
+ }
+
+ /**
+ * common get logic, used to get by key or get by value
+ *
+ * @param o the key or value that we're looking for
+ * @param index KEY or VALUE
+ *
+ * @return the key (if the value was mapped) or the value (if the
+ * key was mapped); null if we couldn't find the specified
+ * object
+ */
+ private Object doGet(final Comparable o, final int index) {
+
+ checkNonNullComparable(o, index);
+
+ Node node = lookup(o, index);
+
+ return ((node == null)
+ ? null
+ : node.getData(oppositeIndex(index)));
+ }
+
+ /**
+ * Get the opposite index of the specified index
+ *
+ * @param index KEY or VALUE
+ *
+ * @return VALUE (if KEY was specified), else KEY
+ */
+ private int oppositeIndex(final int index) {
+
+ // old trick ... to find the opposite of a value, m or n,
+ // subtract the value from the sum of the two possible
+ // values. (m + n) - m = n; (m + n) - n = m
+ return SUM_OF_INDICES - index;
+ }
+
+ /**
+ * do the actual lookup of a piece of data
+ *
+ * @param data the key or value to be looked up
+ * @param index KEY or VALUE
+ *
+ * @return the desired Node, or null if there is no mapping of the
+ * specified data
+ */
+ private Node lookup(final Comparable data, final int index) {
+
+ Node rval = null;
+ Node node = rootNode[index];
+
+ while (node != null) {
+ int cmp = compare(data, node.getData(index));
+
+ if (cmp == 0) {
+ rval = node;
+
+ break;
+ } else {
+ node = (cmp < 0)
+ ? node.getLeft(index)
+ : node.getRight(index);
+ }
+ }
+
+ return rval;
+ }
+
+ /**
+ * Compare two objects
+ *
+ * @param o1 the first object
+ * @param o2 the second object
+ *
+ * @return negative value if o1 < o2; 0 if o1 == o2; positive
+ * value if o1 > o2
+ */
+ private static int compare(final Comparable o1, final Comparable o2) {
+ return ((Comparable) o1).compareTo(o2);
+ }
+
+ /**
+ * find the least node from a given node. very useful for starting
+ * a sorting iterator ...
+ *
+ * @param node the node from which we will start searching
+ * @param index KEY or VALUE
+ *
+ * @return the smallest node, from the specified node, in the
+ * specified mapping
+ */
+ private static Node leastNode(final Node node, final int index) {
+
+ Node rval = node;
+
+ if (rval != null) {
+ while (rval.getLeft(index) != null) {
+ rval = rval.getLeft(index);
+ }
+ }
+
+ return rval;
+ }
+
+ /**
+ * get the next larger node from the specified node
+ *
+ * @param node the node to be searched from
+ * @param index KEY or VALUE
+ *
+ * @return the specified node
+ */
+ private Node nextGreater(final Node node, final int index) {
+
+ Node rval = null;
+
+ if (node == null) {
+ rval = null;
+ } else if (node.getRight(index) != null) {
+
+ // everything to the node's right is larger. The least of
+ // the right node's descendents is the next larger node
+ rval = leastNode(node.getRight(index), index);
+ } else {
+
+ // traverse up our ancestry until we find an ancestor that
+ // is null or one whose left child is our ancestor. If we
+ // find a null, then this node IS the largest node in the
+ // tree, and there is no greater node. Otherwise, we are
+ // the largest node in the subtree on that ancestor's left
+ // ... and that ancestor is the next greatest node
+ Node parent = node.getParent(index);
+ Node child = node;
+
+ while ((parent != null) && (child == parent.getRight(index))) {
+ child = parent;
+ parent = parent.getParent(index);
+ }
+
+ rval = parent;
+ }
+
+ return rval;
+ }
+
+ /**
+ * copy the color from one node to another, dealing with the fact
+ * that one or both nodes may, in fact, be null
+ *
+ * @param from the node whose color we're copying; may be null
+ * @param to the node whose color we're changing; may be null
+ * @param index KEY or VALUE
+ */
+ private static void copyColor(final Node from, final Node to,
+ final int index) {
+
+ if (to != null) {
+ if (from == null) {
+
+ // by default, make it black
+ to.setBlack(index);
+ } else {
+ to.copyColor(from, index);
+ }
+ }
+ }
+
+ /**
+ * is the specified node red? if the node does not exist, no, it's
+ * black, thank you
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static boolean isRed(final Node node, final int index) {
+
+ return ((node == null)
+ ? false
+ : node.isRed(index));
+ }
+
+ /**
+ * is the specified black red? if the node does not exist, sure,
+ * it's black, thank you
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static boolean isBlack(final Node node, final int index) {
+
+ return ((node == null)
+ ? true
+ : node.isBlack(index));
+ }
+
+ /**
+ * force a node (if it exists) red
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static void makeRed(final Node node, final int index) {
+
+ if (node != null) {
+ node.setRed(index);
+ }
+ }
+
+ /**
+ * force a node (if it exists) black
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static void makeBlack(final Node node, final int index) {
+
+ if (node != null) {
+ node.setBlack(index);
+ }
+ }
+
+ /**
+ * get a node's grandparent. mind you, the node, its parent, or
+ * its grandparent may not exist. no problem
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static Node getGrandParent(final Node node, final int index) {
+ return getParent(getParent(node, index), index);
+ }
+
+ /**
+ * get a node's parent. mind you, the node, or its parent, may not
+ * exist. no problem
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static Node getParent(final Node node, final int index) {
+
+ return ((node == null)
+ ? null
+ : node.getParent(index));
+ }
+
+ /**
+ * get a node's right child. mind you, the node may not exist. no
+ * problem
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static Node getRightChild(final Node node, final int index) {
+
+ return (node == null)
+ ? null
+ : node.getRight(index);
+ }
+
+ /**
+ * get a node's left child. mind you, the node may not exist. no
+ * problem
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static Node getLeftChild(final Node node, final int index) {
+
+ return (node == null)
+ ? null
+ : node.getLeft(index);
+ }
+
+ /**
+ * is this node its parent's left child? mind you, the node, or
+ * its parent, may not exist. no problem. if the node doesn't
+ * exist ... it's its non-existent parent's left child. If the
+ * node does exist but has no parent ... no, we're not the
+ * non-existent parent's left child. Otherwise (both the specified
+ * node AND its parent exist), check.
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static boolean isLeftChild(final Node node, final int index) {
+
+ return (node == null)
+ ? true
+ : ((node.getParent(index) == null)
+ ? false
+ : (node == node.getParent(index).getLeft(index)));
+ }
+
+ /**
+ * is this node its parent's right child? mind you, the node, or
+ * its parent, may not exist. no problem. if the node doesn't
+ * exist ... it's its non-existent parent's right child. If the
+ * node does exist but has no parent ... no, we're not the
+ * non-existent parent's right child. Otherwise (both the
+ * specified node AND its parent exist), check.
+ *
+ * @param node the node (may be null) in question
+ * @param index KEY or VALUE
+ */
+ private static boolean isRightChild(final Node node, final int index) {
+
+ return (node == null)
+ ? true
+ : ((node.getParent(index) == null)
+ ? false
+ : (node == node.getParent(index).getRight(index)));
+ }
+
+ /**
+ * do a rotate left. standard fare in the world of balanced trees
+ *
+ * @param node the node to be rotated
+ * @param index KEY or VALUE
+ */
+ private void rotateLeft(final Node node, final int index) {
+
+ Node rightChild = node.getRight(index);
+
+ node.setRight(rightChild.getLeft(index), index);
+
+ if (rightChild.getLeft(index) != null) {
+ rightChild.getLeft(index).setParent(node, index);
+ }
+
+ rightChild.setParent(node.getParent(index), index);
+
+ if (node.getParent(index) == null) {
+
+ // node was the root ... now its right child is the root
+ rootNode[index] = rightChild;
+ } else if (node.getParent(index).getLeft(index) == node) {
+ node.getParent(index).setLeft(rightChild, index);
+ } else {
+ node.getParent(index).setRight(rightChild, index);
+ }
+
+ rightChild.setLeft(node, index);
+ node.setParent(rightChild, index);
+ }
+
+ /**
+ * do a rotate right. standard fare in the world of balanced trees
+ *
+ * @param node the node to be rotated
+ * @param index KEY or VALUE
+ */
+ private void rotateRight(final Node node, final int index) {
+
+ Node leftChild = node.getLeft(index);
+
+ node.setLeft(leftChild.getRight(index), index);
+
+ if (leftChild.getRight(index) != null) {
+ leftChild.getRight(index).setParent(node, index);
+ }
+
+ leftChild.setParent(node.getParent(index), index);
+
+ if (node.getParent(index) == null) {
+
+ // node was the root ... now its left child is the root
+ rootNode[index] = leftChild;
+ } else if (node.getParent(index).getRight(index) == node) {
+ node.getParent(index).setRight(leftChild, index);
+ } else {
+ node.getParent(index).setLeft(leftChild, index);
+ }
+
+ leftChild.setRight(node, index);
+ node.setParent(leftChild, index);
+ }
+
+ /**
+ * complicated red-black insert stuff. Based on Sun's TreeMap
+ * implementation, though it's barely recognizeable any more
+ *
+ * @param insertedNode the node to be inserted
+ * @param index KEY or VALUE
+ */
+ private void doRedBlackInsert(final Node insertedNode, final int index)
+{
+
+ Node currentNode = insertedNode;
+
+ makeRed(currentNode, index);
+
+ while ((currentNode != null) && (currentNode != rootNode[index])
+ && (isRed(currentNode.getParent(index), index))) {
+ if (isLeftChild(getParent(currentNode, index), index)) {
+ Node y = getRightChild(getGrandParent(currentNode, index),
+ index);
+
+ if (isRed(y, index)) {
+ makeBlack(getParent(currentNode, index), index);
+ makeBlack(y, index);
+ makeRed(getGrandParent(currentNode, index), index);
+
+ currentNode = getGrandParent(currentNode, index);
+ } else {
+ if (isRightChild(currentNode, index)) {
+ currentNode = getParent(currentNode, index);
+
+ rotateLeft(currentNode, index);
+ }
+
+ makeBlack(getParent(currentNode, index), index);
+ makeRed(getGrandParent(currentNode, index), index);
+
+ if (getGrandParent(currentNode, index) != null) {
+ rotateRight(getGrandParent(currentNode, index),
+ index);
+ }
+ }
+ } else {
+
+ // just like clause above, except swap left for right
+ Node y = getLeftChild(getGrandParent(currentNode, index),
+ index);
+
+ if (isRed(y, index)) {
+ makeBlack(getParent(currentNode, index), index);
+ makeBlack(y, index);
+ makeRed(getGrandParent(currentNode, index), index);
+
+ currentNode = getGrandParent(currentNode, index);
+ } else {
+ if (isLeftChild(currentNode, index)) {
+ currentNode = getParent(currentNode, index);
+
+ rotateRight(currentNode, index);
+ }
+
+ makeBlack(getParent(currentNode, index), index);
+ makeRed(getGrandParent(currentNode, index), index);
+
+ if (getGrandParent(currentNode, index) != null) {
+ rotateLeft(getGrandParent(currentNode, index),
+index);
+ }
+ }
+ }
+ }
+
+ makeBlack(rootNode[index], index);
+ }
+
+ /**
+ * complicated red-black delete stuff. Based on Sun's TreeMap
+ * implementation, though it's barely recognizeable any more
+ *
+ * @param deletedNode the node to be deleted
+ */
+ private void doRedBlackDelete(final Node deletedNode) {
+
+ for (int index = FIRST_INDEX; index < NUMBER_OF_INDICES; index++) {
+
+ // if deleted node has both left and children, swap with
+ // the next greater node
+ if ((deletedNode.getLeft(index) != null)
+ && (deletedNode.getRight(index) != null)) {
+ swapPosition(nextGreater(deletedNode, index), deletedNode,
+ index);
+ }
+
+ Node replacement = ((deletedNode.getLeft(index) != null)
+ ? deletedNode.getLeft(index)
+ : deletedNode.getRight(index));
+
+ if (replacement != null) {
+ replacement.setParent(deletedNode.getParent(index), index);
+
+ if (deletedNode.getParent(index) == null) {
+ rootNode[index] = replacement;
+ } else if (deletedNode
+ == deletedNode.getParent(index).getLeft(index)) {
+ deletedNode.getParent(index).setLeft(replacement,
+index);
+ } else {
+ deletedNode.getParent(index).setRight(replacement,
+index);
+ }
+
+ deletedNode.setLeft(null, index);
+ deletedNode.setRight(null, index);
+ deletedNode.setParent(null, index);
+
+ if (isBlack(deletedNode, index)) {
+ doRedBlackDeleteFixup(replacement, index);
+ }
+ } else {
+
+ // replacement is null
+ if (deletedNode.getParent(index) == null) {
+
+ // empty tree
+ rootNode[index] = null;
+ } else {
+
+ // deleted node had no children
+ if (isBlack(deletedNode, index)) {
+ doRedBlackDeleteFixup(deletedNode, index);
+ }
+
+ if (deletedNode.getParent(index) != null) {
+ if (deletedNode
+ == deletedNode.getParent(index)
+ .getLeft(index)) {
+ deletedNode.getParent(index).setLeft(null,
+index);
+ } else {
+ deletedNode.getParent(index).setRight(null,
+ index);
+ }
+
+ deletedNode.setParent(null, index);
+ }
+ }
+ }
+ }
+
+ shrink();
+ }
+
+ /**
+ * complicated red-black delete stuff. Based on Sun's TreeMap
+ * implementation, though it's barely recognizeable any more. This
+ * rebalances the tree (somewhat, as red-black trees are not
+ * perfectly balanced -- perfect balancing takes longer)
+ *
+ * @param replacementNode the node being replaced
+ * @param index KEY or VALUE
+ */
+ private void doRedBlackDeleteFixup(final Node replacementNode,
+ final int index) {
+
+ Node currentNode = replacementNode;
+
+ while ((currentNode != rootNode[index])
+ && (isBlack(currentNode, index))) {
+ if (isLeftChild(currentNode, index)) {
+ Node siblingNode =
+ getRightChild(getParent(currentNode, index), index);
+
+ if (isRed(siblingNode, index)) {
+ makeBlack(siblingNode, index);
+ makeRed(getParent(currentNode, index), index);
+ rotateLeft(getParent(currentNode, index), index);
+
+ siblingNode = getRightChild(getParent(currentNode,
+index),
+ index);
+ }
+
+ if (isBlack(getLeftChild(siblingNode, index), index)
+ && isBlack(getRightChild(siblingNode, index),
+ index)) {
+ makeRed(siblingNode, index);
+
+ currentNode = getParent(currentNode, index);
+ } else {
+ if (isBlack(getRightChild(siblingNode, index), index)) {
+ makeBlack(getLeftChild(siblingNode, index), index);
+ makeRed(siblingNode, index);
+ rotateRight(siblingNode, index);
+
+ siblingNode =
+ getRightChild(getParent(currentNode, index),
+ index);
+ }
+
+ copyColor(getParent(currentNode, index), siblingNode,
+ index);
+ makeBlack(getParent(currentNode, index), index);
+ makeBlack(getRightChild(siblingNode, index), index);
+ rotateLeft(getParent(currentNode, index), index);
+
+ currentNode = rootNode[index];
+ }
+ } else {
+ Node siblingNode = getLeftChild(getParent(currentNode,
+index),
+ index);
+
+ if (isRed(siblingNode, index)) {
+ makeBlack(siblingNode, index);
+ makeRed(getParent(currentNode, index), index);
+ rotateRight(getParent(currentNode, index), index);
+
+ siblingNode = getLeftChild(getParent(currentNode,
+index),
+ index);
+ }
+
+ if (isBlack(getRightChild(siblingNode, index), index)
+ && isBlack(getLeftChild(siblingNode, index), index))
+{
+ makeRed(siblingNode, index);
+
+ currentNode = getParent(currentNode, index);
+ } else {
+ if (isBlack(getLeftChild(siblingNode, index), index)) {
+ makeBlack(getRightChild(siblingNode, index), index);
+ makeRed(siblingNode, index);
+ rotateLeft(siblingNode, index);
+
+ siblingNode =
+ getLeftChild(getParent(currentNode, index),
+ index);
+ }
+
+ copyColor(getParent(currentNode, index), siblingNode,
+ index);
+ makeBlack(getParent(currentNode, index), index);
+ makeBlack(getLeftChild(siblingNode, index), index);
+ rotateRight(getParent(currentNode, index), index);
+
+ currentNode = rootNode[index];
+ }
+ }
+ }
+
+ makeBlack(currentNode, index);
+ }
+
+ /**
+ * swap two nodes (except for their content), taking care of
+ * special cases where one is the other's parent ... hey, it
+ * happens.
+ *
+ * @param x one node
+ * @param y another node
+ * @param index KEY or VALUE
+ */
+ private void swapPosition(final Node x, final Node y, final int index) {
+
+ // Save initial values.
+ Node xFormerParent = x.getParent(index);
+ Node xFormerLeftChild = x.getLeft(index);
+ Node xFormerRightChild = x.getRight(index);
+ Node yFormerParent = y.getParent(index);
+ Node yFormerLeftChild = y.getLeft(index);
+ Node yFormerRightChild = y.getRight(index);
+ boolean xWasLeftChild =
+ (x.getParent(index) != null)
+ && (x == x.getParent(index).getLeft(index));
+ boolean yWasLeftChild =
+ (y.getParent(index) != null)
+ && (y == y.getParent(index).getLeft(index));
+
+ // Swap, handling special cases of one being the other's parent.
+ if (x == yFormerParent) { // x was y's parent
+ x.setParent(y, index);
+
+ if (yWasLeftChild) {
+ y.setLeft(x, index);
+ y.setRight(xFormerRightChild, index);
+ } else {
+ y.setRight(x, index);
+ y.setLeft(xFormerLeftChild, index);
+ }
+ } else {
+ x.setParent(yFormerParent, index);
+
+ if (yFormerParent != null) {
+ if (yWasLeftChild) {
+ yFormerParent.setLeft(x, index);
+ } else {
+ yFormerParent.setRight(x, index);
+ }
+ }
+
+ y.setLeft(xFormerLeftChild, index);
+ y.setRight(xFormerRightChild, index);
+ }
+
+ if (y == xFormerParent) { // y was x's parent
+ y.setParent(x, index);
+
+ if (xWasLeftChild) {
+ x.setLeft(y, index);
+ x.setRight(yFormerRightChild, index);
+ } else {
+ x.setRight(y, index);
+ x.setLeft(yFormerLeftChild, index);
+ }
+ } else {
+ y.setParent(xFormerParent, index);
+
+ if (xFormerParent != null) {
+ if (xWasLeftChild) {
+ xFormerParent.setLeft(y, index);
+ } else {
+ xFormerParent.setRight(y, index);
+ }
+ }
+
+ x.setLeft(yFormerLeftChild, index);
+ x.setRight(yFormerRightChild, index);
+ }
+
+ // Fix children's parent pointers
+ if (x.getLeft(index) != null) {
+ x.getLeft(index).setParent(x, index);
+ }
+
+ if (x.getRight(index) != null) {
+ x.getRight(index).setParent(x, index);
+ }
+
+ if (y.getLeft(index) != null) {
+ y.getLeft(index).setParent(y, index);
+ }
+
+ if (y.getRight(index) != null) {
+ y.getRight(index).setParent(y, index);
+ }
+
+ x.swapColors(y, index);
+
+ // Check if root changed
+ if (rootNode[index] == x) {
+ rootNode[index] = y;
+ } else if (rootNode[index] == y) {
+ rootNode[index] = x;
+ }
+ }
+
+ /**
+ * check if an object is fit to be proper input ... has to be
+ * Comparable and non-null
+ *
+ * @param o the object being checked
+ * @param index KEY or VALUE (used to put the right word in the
+ * exception message)
+ *
+ * @exception NullPointerException if o is null
+ * @exception ClassCastException if o is not Comparable
+ */
+ private static void checkNonNullComparable(final Object o,
+ final int index) {
+
+ if (o == null) {
+ throw new NullPointerException(dataName[index]
+ + " cannot be null");
+ }
+
+ if (!(o instanceof Comparable)) {
+ throw new ClassCastException(dataName[index]
+ + " must be Comparable");
+ }
+ }
+
+ /**
+ * check a key for validity (non-null and implements Comparable)
+ *
+ * @param key the key to be checked
+ *
+ * @exception NullPointerException if key is null
+ * @exception ClassCastException if key is not Comparable
+ */
+ private static void checkKey(final Object key) {
+ checkNonNullComparable(key, KEY);
+ }
+
+ /**
+ * check a value for validity (non-null and implements Comparable)
+ *
+ * @param value the value to be checked
+ *
+ * @exception NullPointerException if value is null
+ * @exception ClassCastException if value is not Comparable
+ */
+ private static void checkValue(final Object value) {
+ checkNonNullComparable(value, VALUE);
+ }
+
+ /**
+ * check a key and a value for validity (non-null and implements
+ * Comparable)
+ *
+ * @param key the key to be checked
+ * @param value the value to be checked
+ *
+ * @exception NullPointerException if key or value is null
+ * @exception ClassCastException if key or value is not Comparable
+ */
+ private static void checkKeyAndValue(final Object key,
+ final Object value) {
+ checkKey(key);
+ checkValue(value);
+ }
+
+ /**
+ * increment the modification count -- used to check for
+ * concurrent modification of the map through the map and through
+ * an Iterator from one of its Set or Collection views
+ */
+ private void modify() {
+ modifications++;
+ }
+
+ /**
+ * bump up the size and note that the map has changed
+ */
+ private void grow() {
+
+ modify();
+
+ nodeCount++;
+ }
+
+ /**
+ * decrement the size and note that the map has changed
+ */
+ private void shrink() {
+
+ modify();
+
+ nodeCount--;
+ }
+
+ /**
+ * insert a node by its value
+ *
+ * @param newNode the node to be inserted
+ *
+ * @exception IllegalArgumentException if the node already exists
+ * in the value mapping
+ */
+ private void insertValue(final Node newNode)
+ throws IllegalArgumentException {
+
+ Node node = rootNode[VALUE];
+
+ while (true) {
+ int cmp = compare(newNode.getData(VALUE), node.getData(VALUE));
+
+ if (cmp == 0) {
+ throw new IllegalArgumentException(
+ "Cannot store a duplicate value (\""
+ + newNode.getData(VALUE) + "\") in this Map");
+ } else if (cmp < 0) {
+ if (node.getLeft(VALUE) != null) {
+ node = node.getLeft(VALUE);
+ } else {
+ node.setLeft(newNode, VALUE);
+ newNode.setParent(node, VALUE);
+ doRedBlackInsert(newNode, VALUE);
+
+ break;
+ }
+ } else { // cmp > 0
+ if (node.getRight(VALUE) != null) {
+ node = node.getRight(VALUE);
+ } else {
+ node.setRight(newNode, VALUE);
+ newNode.setParent(node, VALUE);
+ doRedBlackInsert(newNode, VALUE);
+
+ break;
+ }
+ }
+ }
+ }
+
+ /* ********** START implementation of Map ********** */
+
+ /**
+ * Returns the number of key-value mappings in this map. If the
+ * map contains more than Integer.MAXVALUE elements, returns
+ * Integer.MAXVALUE.
+ *
+ * @return the number of key-value mappings in this map.
+ */
+ public int size() {
+ return nodeCount;
+ }
+
+ /**
+ * Returns true if this map contains a mapping for the specified
+ * key.
+ *
+ * @param key key whose presence in this map is to be tested.
+ *
+ * @return true if this map contains a mapping for the specified
+ * key.
+ *
+ * @exception ClassCastException if the key is of an inappropriate
+ * type for this map.
+ * @exception NullPointerException if the key is null
+ */
+ public boolean containsKey(final Object key)
+ throws ClassCastException, NullPointerException {
+
+ checkKey(key);
+
+ return lookup((Comparable) key, KEY) != null;
+ }
+
+ /**
+ * Returns true if this map maps one or more keys to the
+ * specified value.
+ *
+ * @param value value whose presence in this map is to be tested.
+ *
+ * @return true if this map maps one or more keys to the specified
+ * value.
+ */
+ public boolean containsValue(final Object value) {
+
+ checkValue(value);
+
+ return lookup((Comparable) value, VALUE) != null;
+ }
+
+ /**
+ * Returns the value to which this map maps the specified
+ * key. Returns null if the map contains no mapping for this key.
+ *
+ * @param key key whose associated value is to be returned.
+ *
+ * @return the value to which this map maps the specified key, or
+ * null if the map contains no mapping for this key.
+ *
+ * @exception ClassCastException if the key is of an inappropriate
+ * type for this map.
+ * @exception NullPointerException if the key is null
+ */
+ public Object get(final Object key)
+ throws ClassCastException, NullPointerException {
+ return doGet((Comparable) key, KEY);
+ }
+
+ /**
+ * Associates the specified value with the specified key in this
+ * map.
+ *
+ * @param key key with which the specified value is to be
+ * associated.
+ * @param value value to be associated with the specified key.
+ *
+ * @return null
+ *
+ * @exception ClassCastException if the class of the specified key
+ * or value prevents it from being
+ * stored in this map.
+ * @exception NullPointerException if the specified key or value
+ * is null
+ * @exception IllegalArgumentException if the key duplicates an
+ * existing key, or if the
+ * value duplicates an
+ * existing value
+ */
+ public Object put(final Object key, final Object value)
+ throws ClassCastException, NullPointerException,
+ IllegalArgumentException {
+
+ checkKeyAndValue(key, value);
+
+ Node node = rootNode[KEY];
+
+ if (node == null) {
+ Node root = new Node((Comparable) key, (Comparable) value);
+
+ rootNode[KEY] = root;
+ rootNode[VALUE] = root;
+
+ grow();
+ } else {
+ while (true) {
+ int cmp = compare((Comparable) key, node.getData(KEY));
+
+ if (cmp == 0) {
+ throw new IllegalArgumentException(
+ "Cannot store a duplicate key (\"" + key
+ + "\") in this Map");
+ } else if (cmp < 0) {
+ if (node.getLeft(KEY) != null) {
+ node = node.getLeft(KEY);
+ } else {
+ Node newNode = new Node((Comparable) key,
+ (Comparable) value);
+
+ insertValue(newNode);
+ node.setLeft(newNode, KEY);
+ newNode.setParent(node, KEY);
+ doRedBlackInsert(newNode, KEY);
+ grow();
+
+ break;
+ }
+ } else { // cmp > 0
+ if (node.getRight(KEY) != null) {
+ node = node.getRight(KEY);
+ } else {
+ Node newNode = new Node((Comparable) key,
+ (Comparable) value);
+
+ insertValue(newNode);
+ node.setRight(newNode, KEY);
+ newNode.setParent(node, KEY);
+ doRedBlackInsert(newNode, KEY);
+ grow();
+
+ break;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Removes the mapping for this key from this map if present
+ *
+ * @param key key whose mapping is to be removed from the map.
+ *
+ * @return previous value associated with specified key, or null
+ * if there was no mapping for key.
+ */
+ public Object remove(final Object key) {
+ return doRemove((Comparable) key, KEY);
+ }
+
+ /**
+ * Removes all mappings from this map
+ */
+ public void clear() {
+
+ modify();
+
+ nodeCount = 0;
+ rootNode[KEY] = null;
+ rootNode[VALUE] = null;
+ }
+
+ /**
+ * Returns a set view of the keys contained in this map. The set
+ * is backed by the map, so changes to the map are reflected in
+ * the set, and vice-versa. If the map is modified while an
+ * iteration over the set is in progress, the results of the
+ * iteration are undefined. The set supports element removal,
+ * which removes the corresponding mapping from the map, via the
+ * Iterator.remove, Set.remove, removeAll, retainAll, and clear
+ * operations. It does not support the add or addAll operations.
+ *
+ * @return a set view of the keys contained in this map.
+ */
+ public Set keySet() {
+
+ if (setOfKeys[KEY] == null) {
+ setOfKeys[KEY] = new AbstractSet() {
+
+ public Iterator iterator() {
+
+ return new DoubleOrderedMapIterator(KEY) {
+
+ protected Object doGetNext() {
+ return lastReturnedNode.getData(KEY);
+ }
+ };
+ }
+
+ public int size() {
+ return DoubleOrderedMap.this.size();
+ }
+
+ public boolean contains(Object o) {
+ return containsKey(o);
+ }
+
+ public boolean remove(Object o) {
+
+ int oldNodeCount = nodeCount;
+
+ DoubleOrderedMap.this.remove(o);
+
+ return nodeCount != oldNodeCount;
+ }
+
+ public void clear() {
+ DoubleOrderedMap.this.clear();
+ }
+ };
+ }
+
+ return setOfKeys[KEY];
+ }
+
+ /**
+ * Returns a collection view of the values contained in this
+ * map. The collection is backed by the map, so changes to the map
+ * are reflected in the collection, and vice-versa. If the map is
+ * modified while an iteration over the collection is in progress,
+ * the results of the iteration are undefined. The collection
+ * supports element removal, which removes the corresponding
+ * mapping from the map, via the Iterator.remove,
+ * Collection.remove, removeAll, retainAll and clear operations.
+ * It does not support the add or addAll operations.
+ *
+ * @return a collection view of the values contained in this map.
+ */
+ public Collection values() {
+
+ if (collectionOfValues[KEY] == null) {
+ collectionOfValues[KEY] = new AbstractCollection() {
+
+ public Iterator iterator() {
+
+ return new DoubleOrderedMapIterator(KEY) {
+
+ protected Object doGetNext() {
+ return lastReturnedNode.getData(VALUE);
+ }
+ };
+ }
+
+ public int size() {
+ return DoubleOrderedMap.this.size();
+ }
+
+ public boolean contains(Object o) {
+ return containsValue(o);
+ }
+
+ public boolean remove(Object o) {
+
+ int oldNodeCount = nodeCount;
+
+ removeValue(o);
+
+ return nodeCount != oldNodeCount;
+ }
+
+ public boolean removeAll(Collection c) {
+
+ boolean modified = false;
+ Iterator iter = c.iterator();
+
+ while (iter.hasNext()) {
+ if (removeValue(iter.next()) != null) {
+ modified = true;
+ }
+ }
+
+ return modified;
+ }
+
+ public void clear() {
+ DoubleOrderedMap.this.clear();
+ }
+ };
+ }
+
+ return collectionOfValues[KEY];
+ }
+
+ /**
+ * Returns a set view of the mappings contained in this map. Each
+ * element in the returned set is a Map.Entry. The set is backed
+ * by the map, so changes to the map are reflected in the set, and
+ * vice-versa. If the map is modified while an iteration over the
+ * set is in progress, the results of the iteration are
+ * undefined. The set supports element removal, which removes the
+ * corresponding mapping from the map, via the Iterator.remove,
+ * Set.remove, removeAll, retainAll and clear operations. It does
+ * not support the add or addAll operations.
+ *
+ * @return a set view of the mappings contained in this map.
+ */
+ public Set entrySet() {
+
+ if (setOfEntries[KEY] == null) {
+ setOfEntries[KEY] = new AbstractSet() {
+
+ public Iterator iterator() {
+
+ return new DoubleOrderedMapIterator(KEY) {
+
+ protected Object doGetNext() {
+ return lastReturnedNode;
+ }
+ };
+ }
+
+ public boolean contains(Object o) {
+
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry entry = (Map.Entry) o;
+ Object value = entry.getValue();
+ Node node = lookup((Comparable) entry.getKey(),
+ KEY);
+
+ return (node != null)
+ && node.getData(VALUE).equals(value);
+ }
+
+ public boolean remove(Object o) {
+
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry entry = (Map.Entry) o;
+ Object value = entry.getValue();
+ Node node = lookup((Comparable) entry.getKey(),
+ KEY);
+
+ if ((node != null) && node.getData(VALUE).equals(value))
+{
+ doRedBlackDelete(node);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public int size() {
+ return DoubleOrderedMap.this.size();
+ }
+
+ public void clear() {
+ DoubleOrderedMap.this.clear();
+ }
+ };
+ }
+
+ return setOfEntries[KEY];
+ }
+
+ /* ********** END implementation of Map ********** */
+ private abstract class DoubleOrderedMapIterator implements Iterator {
+
+ private int expectedModifications;
+ protected Node lastReturnedNode;
+ private Node nextNode;
+ private int iteratorType;
+
+ /**
+ * Constructor
+ *
+ * @param type
+ */
+ DoubleOrderedMapIterator(final int type) {
+
+ iteratorType = type;
+ expectedModifications = DoubleOrderedMap.this.modifications;
+ lastReturnedNode = null;
+ nextNode = leastNode(rootNode[iteratorType],
+ iteratorType);
+ }
+
+ /**
+ * @return 'next', whatever that means for a given kind of
+ * DoubleOrderedMapIterator
+ */
+ protected abstract Object doGetNext();
+
+ /* ********** START implementation of Iterator ********** */
+
+ /**
+ * @return true if the iterator has more elements.
+ */
+ public final boolean hasNext() {
+ return nextNode != null;
+ }
+
+ /**
+ * @return the next element in the iteration.
+ *
+ * @exception NoSuchElementException if iteration has no more
+ * elements.
+ * @exception ConcurrentModificationException if the
+ * DoubleOrderedMap is
+ * modified behind
+ * the iterator's
+ * back
+ */
+ public final Object next()
+ throws NoSuchElementException,
+ ConcurrentModificationException {
+
+ if (nextNode == null) {
+ throw new NoSuchElementException();
+ }
+
+ if (modifications != expectedModifications) {
+ throw new ConcurrentModificationException();
+ }
+
+ lastReturnedNode = nextNode;
+ nextNode = nextGreater(nextNode, iteratorType);
+
+ return doGetNext();
+ }
+
+ /**
+ * Removes from the underlying collection the last element
+ * returned by the iterator. This method can be called only
+ * once per call to next. The behavior of an iterator is
+ * unspecified if the underlying collection is modified while
+ * the iteration is in progress in any way other than by
+ * calling this method.
+ *
+ * @exception IllegalStateException if the next method has not
+ * yet been called, or the
+ * remove method has already
+ * been called after the last
+ * call to the next method.
+ * @exception ConcurrentModificationException if the
+ * DoubleOrderedMap is
+ * modified behind
+ * the iterator's
+ * back
+ */
+ public final void remove()
+ throws IllegalStateException,
+ ConcurrentModificationException {
+
+ if (lastReturnedNode == null) {
+ throw new IllegalStateException();
+ }
+
+ if (modifications != expectedModifications) {
+ throw new ConcurrentModificationException();
+ }
+
+ doRedBlackDelete(lastReturnedNode);
+
+ expectedModifications++;
+
+ lastReturnedNode = null;
+ }
+
+ /* ********** END implementation of Iterator ********** */
+ } // end private abstract class DoubleOrderedMapIterator
+
+ // final for performance
+ private static final class Node implements Map.Entry {
+
+ private Comparable[] data;
+ private Node[] leftNode;
+ private Node[] rightNode;
+ private Node[] parentNode;
+ private boolean[] blackColor;
+ private int hashcodeValue;
+ private boolean calculatedHashCode;
+
+ /**
+ * Make a new cell with given key and value, and with null
+ * links, and black (true) colors.
+ *
+ * @param key
+ * @param value
+ */
+ Node(final Comparable key, final Comparable value) {
+
+ data = new Comparable[]{ key, value };
+ leftNode = new Node[]{ null, null };
+ rightNode = new Node[]{ null, null };
+ parentNode = new Node[]{ null, null };
+ blackColor = new boolean[]{ true, true };
+ calculatedHashCode = false;
+ }
+
+ /**
+ * get the specified data
+ *
+ * @param index KEY or VALUE
+ *
+ * @return the key or value
+ */
+ private Comparable getData(final int index) {
+ return data[index];
+ }
+
+ /**
+ * Set this node's left node
+ *
+ * @param node the new left node
+ * @param index KEY or VALUE
+ */
+ private void setLeft(final Node node, final int index) {
+ leftNode[index] = node;
+ }
+
+ /**
+ * get the left node
+ *
+ * @param index KEY or VALUE
+ *
+ * @return the left node -- may be null
+ */
+ private Node getLeft(final int index) {
+ return leftNode[index];
+ }
+
+ /**
+ * Set this node's right node
+ *
+ * @param node the new right node
+ * @param index KEY or VALUE
+ */
+ private void setRight(final Node node, final int index) {
+ rightNode[index] = node;
+ }
+
+ /**
+ * get the right node
+ *
+ * @param index KEY or VALUE
+ *
+ * @return the right node -- may be null
+ */
+ private Node getRight(final int index) {
+ return rightNode[index];
+ }
+
+ /**
+ * Set this node's parent node
+ *
+ * @param node the new parent node
+ * @param index KEY or VALUE
+ */
+ private void setParent(final Node node, final int index) {
+ parentNode[index] = node;
+ }
+
+ /**
+ * get the parent node
+ *
+ * @param index KEY or VALUE
+ *
+ * @return the parent node -- may be null
+ */
+ private Node getParent(final int index) {
+ return parentNode[index];
+ }
+
+ /**
+ * exchange colors with another node
+ *
+ * @param node the node to swap with
+ * @param index KEY or VALUE
+ */
+ private void swapColors(final Node node, final int index) {
+
+ // Swap colors -- old hacker's trick
+ blackColor[index] ^= node.blackColor[index];
+ node.blackColor[index] ^= blackColor[index];
+ blackColor[index] ^= node.blackColor[index];
+ }
+
+ /**
+ * is this node black?
+ *
+ * @param index KEY or VALUE
+ *
+ * @return true if black (which is represented as a true boolean)
+ */
+ private boolean isBlack(final int index) {
+ return blackColor[index];
+ }
+
+ /**
+ * is this node red?
+ *
+ * @param index KEY or VALUE
+ *
+ * @return true if non-black
+ */
+ private boolean isRed(final int index) {
+ return !blackColor[index];
+ }
+
+ /**
+ * make this node black
+ *
+ * @param index KEY or VALUE
+ */
+ private void setBlack(final int index) {
+ blackColor[index] = true;
+ }
+
+ /**
+ * make this node red
+ *
+ * @param index KEY or VALUE
+ */
+ private void setRed(final int index) {
+ blackColor[index] = false;
+ }
+
+ /**
+ * make this node the same color as another
+ *
+ * @param node the node whose color we're adopting
+ * @param index KEY or VALUE
+ */
+ private void copyColor(final Node node, final int index) {
+ blackColor[index] = node.blackColor[index];
+ }
+
+ /* ********** START implementation of Map.Entry ********** */
+
+ /**
+ * @return the key corresponding to this entry.
+ */
+ public Object getKey() {
+ return data[KEY];
+ }
+
+ /**
+ * @return the value corresponding to this entry.
+ */
+ public Object getValue() {
+ return data[VALUE];
+ }
+
+ /**
+ * Optional operation that is not permitted in this
+ * implementation
+ *
+ * @param ignored
+ *
+ * @return does not return
+ *
+ * @exception UnsupportedOperationException
+ */
+ public Object setValue(Object ignored)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Map.Entry.setValue is not supported");
+ }
+
+ /**
+ * Compares the specified object with this entry for equality.
+ * Returns true if the given object is also a map entry and
+ * the two entries represent the same mapping.
+ *
+ * @param o object to be compared for equality with this map
+ * entry.
+ * @return true if the specified object is equal to this map
+ * entry.
+ */
+ public boolean equals(Object o) {
+
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry e = (Map.Entry) o;
+
+ return data[KEY].equals(e.getKey())
+ && data[VALUE].equals(e.getValue());
+ }
+
+ /**
+ * @return the hash code value for this map entry.
+ */
+ public int hashCode() {
+
+ if (!calculatedHashCode) {
+ hashcodeValue = data[KEY].hashCode()
+ ^ data[VALUE].hashCode();
+ calculatedHashCode = true;
+ }
+
+ return hashcodeValue;
+ }
+
+ /* ********** END implementation of Map.Entry ********** */
+ }
+} // end public class DoubleOrderedMap
diff --git a/src/java/org/apache/commons/collections/package.html b/src/java/org/apache/commons/collections/package.html
index d96290ce4..d78d86621 100644
--- a/src/java/org/apache/commons/collections/package.html
+++ b/src/java/org/apache/commons/collections/package.html
@@ -1,4 +1,4 @@
-
+
Package Documentation for org.apache.commons.collections
@@ -34,7 +34,8 @@
{@link org.apache.commons.collections.FastHashMap}
{@link org.apache.commons.collections.FastTreeMap}
{@link org.apache.commons.collections.LRUMap}
- {@link org.apache.commons.collections.SoftRefHashMap}
+ {@link org.apache.commons.collections.SoftRefHashMap}
+ {@link org.apache.commons.collections.DoubleOrderedMap}
Special-purpose implementations of the {@link
diff --git a/src/test/org/apache/commons/collections/LocalTestNode.java b/src/test/org/apache/commons/collections/LocalTestNode.java
new file mode 100644
index 000000000..e9bb7a18e
--- /dev/null
+++ b/src/test/org/apache/commons/collections/LocalTestNode.java
@@ -0,0 +1,164 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/LocalTestNode.java,v 1.1 2002/01/20 04:36:08 craigmcc Exp $
+ * $Revision: 1.1 $
+ * $Date: 2002/01/20 04:36:08 $
+ *
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.commons.collections;
+
+
+
+/**
+* Class LocalTestNode
+*
+* a helper class for TestDoubleOrderedMap
+*
+* @author Marc Johnson (marcj at users dot sourceforge dot net)
+*/
+class LocalTestNode implements Comparable {
+
+ private Comparable key;
+ private Comparable value;
+
+ /**
+ * construct a LocalTestNode
+ *
+ * @param key value used to create the key and value
+ */
+ LocalTestNode(final int key) {
+ this.key = new Integer(key);
+ this.value = String.valueOf(key);
+ }
+
+ /**
+ * @param key the unique key associated with the current node.
+ */
+ void setKey(Comparable key) {
+ this.key = key;
+ }
+
+ /**
+ * @return the unique key associated with the current node
+ */
+ Comparable getKey() {
+ return key;
+ }
+
+ /**
+ * @param value the unique value associated with the current node.
+ */
+ void setValue(Comparable value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the unique value associated with the current node
+ */
+ Comparable getValue() {
+ return value;
+ }
+
+ /**
+ * Method compareTo
+ *
+ * @param o
+ *
+ * @return
+ */
+ public int compareTo(Object o) {
+
+ LocalTestNode other = (LocalTestNode) o;
+ int rval = getKey().compareTo(other.getKey());
+
+ if (rval == 0) {
+ rval = getValue().compareTo(other.getValue());
+ }
+
+ return rval;
+ }
+
+ /**
+ * Method equals
+ *
+ * @param o
+ *
+ * @return true if equal
+ */
+ public boolean equals(Object o) {
+
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o.getClass().equals(this.getClass()))) {
+ return false;
+ }
+
+ LocalTestNode node = (LocalTestNode) o;
+
+ return (getKey().equals(node.getKey())
+ && getValue().equals(node.getValue()));
+ }
+
+ /**
+ * @return hash code
+ */
+ public int hashCode() {
+ return getKey().hashCode() ^ getValue().hashCode();
+ }
+}
diff --git a/src/test/org/apache/commons/collections/TestAll.java b/src/test/org/apache/commons/collections/TestAll.java
index f8aa1dfef..2e2f23a49 100644
--- a/src/test/org/apache/commons/collections/TestAll.java
+++ b/src/test/org/apache/commons/collections/TestAll.java
@@ -1,7 +1,7 @@
/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestAll.java,v 1.13 2001/09/18 10:41:39 jstrachan Exp $
- * $Revision: 1.13 $
- * $Date: 2001/09/18 10:41:39 $
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestAll.java,v 1.14 2002/01/20 04:36:08 craigmcc Exp $
+ * $Revision: 1.14 $
+ * $Date: 2002/01/20 04:36:08 $
*
* ====================================================================
*
@@ -66,7 +66,7 @@ import junit.framework.*;
/**
* Entry point for all Collections tests.
* @author Rodney Waldhoff
- * @version $Id: TestAll.java,v 1.13 2001/09/18 10:41:39 jstrachan Exp $
+ * @version $Id: TestAll.java,v 1.14 2002/01/20 04:36:08 craigmcc Exp $
*/
public class TestAll extends TestCase {
public TestAll(String testName) {
@@ -95,6 +95,7 @@ public class TestAll extends TestCase {
suite.addTest(TestSingletonIterator.suite());
suite.addTest(TestTreeBag.suite());
suite.addTest(TestTreeMap.suite());
+ suite.addTest(TestDoubleOrderedMap.suite());
return suite;
}
diff --git a/src/test/org/apache/commons/collections/TestDoubleOrderedMap.java b/src/test/org/apache/commons/collections/TestDoubleOrderedMap.java
new file mode 100644
index 000000000..dc6116604
--- /dev/null
+++ b/src/test/org/apache/commons/collections/TestDoubleOrderedMap.java
@@ -0,0 +1,2798 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestDoubleOrderedMap.java,v 1.1 2002/01/20 04:36:08 craigmcc Exp $
+ * $Revision: 1.1 $
+ * $Date: 2002/01/20 04:36:08 $
+ *
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.commons.collections;
+
+
+
+import junit.framework.*;
+
+import java.util.*;
+
+
+/**
+* Class TestDoubleOrderedMap
+*
+* Test cases for DoubleOrderedMap
+*
+* @author Marc Johnson (marcj at users dot sourceforge dot net)
+*/
+public class TestDoubleOrderedMap extends TestCase {
+
+ /**
+ * constructor
+ *
+ * @param name
+ */
+ public TestDoubleOrderedMap(final String name) {
+ super(name);
+ }
+
+ /**
+ * create a suite of the tests in this class
+ *
+ * @return the test suite
+ */
+ public static Test suite() {
+ return new TestSuite(TestDoubleOrderedMap.class);
+ }
+
+ /**
+ * test size() method
+ */
+ public void testSize() {
+
+ Map m = new DoubleOrderedMap();
+
+ assertEquals(0, m.size());
+
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k].getValue());
+ assertEquals(k + 1, m.size());
+ }
+
+ int count = m.size();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.remove(nodes[k].getKey());
+
+ --count;
+
+ assertEquals(count, m.size());
+
+ // failed remove should not affect size
+ m.remove(nodes[k].getKey());
+ assertEquals(count, m.size());
+ }
+ }
+
+ /**
+ * test IsEmpty() method
+ */
+ public void testIsEmpty() {
+
+ Map m = new DoubleOrderedMap();
+
+ assertTrue(m.isEmpty());
+
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k].getValue());
+ assertTrue(!m.isEmpty());
+ }
+
+ int count = m.size();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.remove(nodes[k].getKey());
+
+ --count;
+
+ if (count == 0) {
+ assertTrue(m.isEmpty());
+ } else {
+ assertTrue(!m.isEmpty());
+ }
+
+ // failed remove should not affect emptiness
+ m.remove(nodes[k].getKey());
+
+ if (count == 0) {
+ assertTrue(m.isEmpty());
+ } else {
+ assertTrue(!m.isEmpty());
+ }
+ }
+ }
+
+ /**
+ * test containsKey() method
+ */
+ public void testContainsKey() {
+
+ Map m = new DoubleOrderedMap();
+
+ try {
+ m.containsKey(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ try {
+ m.containsKey(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ assertTrue(!m.containsKey("foo"));
+
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ assertTrue(m.containsKey(nodes[k].getKey()));
+ }
+
+ assertTrue(!m.containsKey(new Integer(-1)));
+
+ try {
+ m.containsKey("foo");
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.remove(nodes[k].getKey());
+ assertTrue(!m.containsKey(nodes[k].getKey()));
+ }
+ }
+
+ /**
+ * test containsValue() method
+ */
+ public void testContainsValue() {
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ assertTrue(m.containsValue(nodes[k]));
+ }
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.remove(nodes[k].getKey());
+ assertTrue(!m.containsValue(nodes[k]));
+ }
+ }
+
+ /**
+ * test get() method
+ */
+ public void testGet() {
+
+ Map m = new DoubleOrderedMap();
+
+ try {
+ m.get(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ try {
+ m.get(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ assertNull(m.get("foo"));
+
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ assertSame(m.get(nodes[k].getKey()), nodes[k]);
+ }
+
+ assertNull(m.get(new Integer(-1)));
+
+ try {
+ m.get("foo");
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ assertNotNull(m.get(nodes[k].getKey()));
+ m.remove(nodes[k].getKey());
+ assertNull(m.get(nodes[k].getKey()));
+ }
+ }
+
+ /**
+ * test put() method
+ */
+ public void testPut() {
+
+ Map m = new DoubleOrderedMap();
+
+ try {
+ m.put(new Object(), "foo");
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ try {
+ m.put(null, "foo");
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ m.put("foo", null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ m.put("foo", new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ LocalTestNode[] nodes = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ assertNull(m.put(nodes[k].getKey(), nodes[k].getValue()));
+
+ try {
+ m.put(nodes[k].getKey(), "foo");
+ } catch (IllegalArgumentException ignored) {}
+ }
+ }
+
+ /**
+ * test remove() method
+ */
+ public void testRemove() {
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ try {
+ m.remove(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ m.remove(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ assertNull(m.remove(new Integer(-1)));
+
+ try {
+ m.remove("foo");
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k += 2) {
+ Comparable key = nodes[k].getKey();
+
+ assertNotNull(m.get(key));
+ assertSame(nodes[k], m.remove(key));
+ assertNull(m.remove(key));
+ assertNull(m.get(key));
+ }
+
+ for (int k = 1; k < nodes.length; k += 2) {
+ Comparable key = nodes[k].getKey();
+
+ assertNotNull(m.get(key));
+ assertSame(nodes[k], m.remove(key));
+ assertNull(m.remove(key));
+ assertNull(m.get(key));
+ }
+
+ assertTrue(m.isEmpty());
+ }
+
+ /**
+ * Method testPutAll
+ */
+ public void testPutAll() {
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ Map m1 = new HashMap();
+
+ m1.put(null, "foo");
+
+ try {
+ m.putAll(m1);
+ fail("Should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ m1 = new HashMap();
+
+ m1.put(new Object(), "bar");
+
+ try {
+ m.putAll(m1);
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ m1 = new HashMap();
+
+ m1.put("fubar", null);
+
+ try {
+ m.putAll(m1);
+ fail("Should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ m1 = new HashMap();
+
+ m1.put("fubar", new Object());
+
+ try {
+ m.putAll(m1);
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ m1 = new HashMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m1.put(nodes[k].getKey(), nodes[k].getValue());
+ }
+
+ m.putAll(m1);
+ assertEquals(nodes.length, m.size());
+
+ for (int k = 0; k < nodes.length; k++) {
+ assertSame(nodes[k].getValue(), m.get(nodes[k].getKey()));
+ }
+ }
+
+ /**
+ * test clear() method
+ */
+ public void testClear() {
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k].getValue());
+ assertTrue(!m.isEmpty());
+ }
+
+ assertTrue(!m.isEmpty());
+
+ for (int k = 0; k < nodes.length; k++) {
+ assertTrue(m.containsKey(nodes[k].getKey()));
+ assertTrue(m.containsValue(nodes[k].getValue()));
+ }
+
+ m.clear();
+ assertTrue(m.isEmpty());
+
+ for (int k = 0; k < nodes.length; k++) {
+ assertTrue(!m.containsKey(nodes[k].getKey()));
+ assertTrue(!m.containsValue(nodes[k].getValue()));
+ }
+ }
+
+ /**
+ * test keySet() method
+ */
+ public void testKeySet() {
+
+ testKeySet(new DoubleOrderedMap());
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ testKeySet(m);
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ int count = m.size();
+
+ for (Iterator iter = m.keySet().iterator(); iter.hasNext(); ) {
+ iter.next();
+ iter.remove();
+
+ --count;
+
+ assertEquals(count, m.size());
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ Set s = m.keySet();
+
+ try {
+ s.remove(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ s.remove(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ Comparable key = nodes[k].getKey();
+
+ assertTrue(s.remove(key));
+ assertTrue(!s.contains(key));
+ assertTrue(!m.containsKey(key));
+ assertTrue(!m.containsValue(nodes[k]));
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ Collection c1 = new LinkedList();
+ Collection c2 = new LinkedList();
+
+ c2.add(new Integer(-99));
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ c2.add(nodes[k].getKey());
+ }
+
+ assertTrue(m.keySet().containsAll(c1));
+ assertTrue(!m.keySet().containsAll(c2));
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ c1.add(new Integer(-55));
+
+ try {
+ m.keySet().addAll(c1);
+ fail("should have caught exception of addAll()");
+ } catch (UnsupportedOperationException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ }
+
+ assertTrue(!m.keySet().retainAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 1) {
+ c1.add(nodes[k].getKey());
+ }
+ }
+
+ assertTrue(m.keySet().retainAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(m.keySet().retainAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(!m.keySet().removeAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 0) {
+ c1.add(nodes[k].getKey());
+ }
+ }
+
+ assertTrue(m.keySet().removeAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ }
+
+ assertTrue(m.keySet().removeAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m.keySet().clear();
+ assertEquals(0, m.size());
+ }
+
+ /**
+ * test values() method
+ */
+ public void testValues() {
+
+ testValues(new DoubleOrderedMap());
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ testValues(m);
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ int count = m.size();
+
+ for (Iterator iter = m.values().iterator(); iter.hasNext(); ) {
+ iter.next();
+ iter.remove();
+
+ --count;
+
+ assertEquals(count, m.size());
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ count = m.size();
+
+ Collection s = m.values();
+
+ for (int k = 0; k < count; k++) {
+ assertTrue(s.remove(nodes[k]));
+ assertTrue(!s.contains(nodes[k]));
+ assertTrue(!m.containsKey(nodes[k].getKey()));
+ assertTrue(!m.containsValue(nodes[k]));
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ Collection c1 = new LinkedList();
+ Collection c2 = new LinkedList();
+
+ c2.add(new LocalTestNode(-123));
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ c2.add(nodes[k]);
+ }
+
+ assertTrue(m.values().containsAll(c1));
+ assertTrue(!m.values().containsAll(c2));
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ }
+
+ try {
+ m.values().addAll(c1);
+ fail("should have caught exception of addAll()");
+ } catch (UnsupportedOperationException ignored) {}
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ }
+
+ assertTrue(!m.values().retainAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 1) {
+ c1.add(nodes[k]);
+ }
+ }
+
+ assertTrue(m.values().retainAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(m.values().retainAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(!m.values().removeAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 0) {
+ c1.add(nodes[k]);
+ }
+ }
+
+ assertTrue(m.values().removeAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ }
+
+ assertTrue(m.values().removeAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m.values().clear();
+ assertEquals(0, m.size());
+ }
+
+ /**
+ * test entrySet() method
+ */
+ public void testEntrySet() {
+
+ testEntrySet(new DoubleOrderedMap());
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ testEntrySet(m);
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ try {
+ ((Map.Entry) m.entrySet().iterator().next())
+ .setValue(new LocalTestNode(-1));
+ fail("Should have caught UnsupportedOperationException");
+ } catch (UnsupportedOperationException ignored) {}
+
+ int count = m.size();
+
+ for (Iterator iter = m.entrySet().iterator(); iter.hasNext(); ) {
+ iter.next();
+ iter.remove();
+
+ --count;
+
+ assertEquals(count, m.size());
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ Collection c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ }
+
+ try {
+ m.entrySet().addAll(c1);
+ fail("should have caught exception of addAll()");
+ } catch (UnsupportedOperationException ignored) {}
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m.entrySet().clear();
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ int x = 0;
+
+ for (Iterator iter = m.entrySet().iterator(); iter.hasNext(); ) {
+ Map.Entry entry = (Map.Entry) iter.next();
+
+ assertSame(entry.getKey(), nodes[x].getKey());
+ assertSame(entry.getValue(), nodes[x]);
+
+ x++;
+ }
+ }
+
+ /**
+ * Method testEquals
+ */
+ public void testEquals() {
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(!m.equals(null));
+ assertEquals(m, m);
+
+ Map m1 = new HashMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m1.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertEquals(m, m1);
+
+ m1 = new DoubleOrderedMap();
+
+ for (int k = 0; k < (nodes.length - 1); k++) {
+ m1.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(!m.equals(m1));
+
+ m1 = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m1.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ LocalTestNode node1 = new LocalTestNode(-1000);
+
+ m1.put(node1.getKey(), node1);
+ assertTrue(!m.equals(m1));
+
+ m1 = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m1.put(nodes[k].getKey(), nodes[nodes.length - (k + 1)]);
+ }
+
+ assertTrue(!m.equals(m1));
+
+ m1 = new DoubleOrderedMap();
+
+ for (int k = nodes.length - 1; k >= 0; k--) {
+ m1.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertEquals(m, m1);
+ }
+
+ /**
+ * test hashCode() method
+ */
+ public void testHashCode() {
+
+ Map m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ Map m1 = new DoubleOrderedMap();
+
+ for (int k = nodes.length - 1; k >= 0; k--) {
+ m1.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertEquals(m.hashCode(), m1.hashCode());
+ }
+
+ /**
+ * test constructors
+ */
+ public void testConstructors() {
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+
+ assertTrue(m.isEmpty());
+
+ DoubleOrderedMap m1 = new DoubleOrderedMap(m);
+
+ assertTrue(m1.isEmpty());
+
+ m1 = new DoubleOrderedMap();
+
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m1.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m = new DoubleOrderedMap(m1);
+
+ assertEquals(m, m1);
+
+ Map m2 = new HashMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m2.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m = new DoubleOrderedMap(m2);
+
+ assertEquals(m, m2);
+
+ // reject duplicated values
+ m2 = new HashMap();
+
+ m2.put("1", "foo");
+ m2.put("2", "foo");
+
+ try {
+ m = new DoubleOrderedMap(m2);
+
+ fail("Should have caught IllegalArgumentException");
+ } catch (IllegalArgumentException ignored) {}
+
+ // reject null values
+ m2.put("2", null);
+
+ try {
+ m = new DoubleOrderedMap(m2);
+
+ fail("Should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ // reject non-Comparable values
+ m2.put("2", new Object());
+
+ try {
+ m = new DoubleOrderedMap(m2);
+
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ // reject incompatible values
+ m2.put("2", new Integer(2));
+
+ try {
+ m = new DoubleOrderedMap(m2);
+
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ // reject incompatible keys
+ m2.remove("2");
+ m2.put(new Integer(2), "bad key");
+
+ try {
+ m = new DoubleOrderedMap(m2);
+
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ // reject non-Comparable keys
+ m2.clear();
+ m2.put("1", "foo");
+ m2.put(new Object(), "bad key");
+
+ try {
+ m = new DoubleOrderedMap(m2);
+
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+ }
+
+ /**
+ * test getKeyForValue() method
+ */
+ public void testGetKeyForValue() {
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+
+ try {
+ m.getKeyForValue(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ try {
+ m.getKeyForValue(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ assertNull(m.getKeyForValue("foo"));
+
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ assertSame(m.getKeyForValue(nodes[k]), nodes[k].getKey());
+ }
+
+ assertNull(m.getKeyForValue(new LocalTestNode(-1)));
+
+ try {
+ m.getKeyForValue("foo");
+ fail("Should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ assertNotNull(m.getKeyForValue(nodes[k]));
+ m.remove(nodes[k].getKey());
+ assertNull(m.getKeyForValue(nodes[k]));
+ }
+ }
+
+ /**
+ * test removeValue() method
+ */
+ public void testRemoveValue() {
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ try {
+ m.removeValue(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ m.removeValue(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ assertNull(m.remove(new Integer(-1)));
+
+ try {
+ m.removeValue("foo");
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k += 2) {
+ assertNotNull(m.getKeyForValue(nodes[k]));
+ assertSame(nodes[k].getKey(), m.removeValue(nodes[k]));
+ assertNull(m.removeValue(nodes[k]));
+ assertNull(m.getKeyForValue(nodes[k]));
+ }
+
+ for (int k = 1; k < nodes.length; k += 2) {
+ assertNotNull(m.getKeyForValue(nodes[k]));
+ assertSame(nodes[k].getKey(), m.removeValue(nodes[k]));
+ assertNull(m.removeValue(nodes[k]));
+ assertNull(m.getKeyForValue(nodes[k]));
+ }
+
+ assertTrue(m.isEmpty());
+ }
+
+ /**
+ * test entrySetByValue() method
+ */
+ public void testEntrySetByValue() {
+
+ testEntrySetByValue(new DoubleOrderedMap());
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ testEntrySetByValue(m);
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ try {
+ ((Map.Entry) m.entrySetByValue().iterator().next())
+ .setValue(new LocalTestNode(-1));
+ fail("Should have caught UnsupportedOperationException");
+ } catch (UnsupportedOperationException ignored) {}
+
+ int count = m.size();
+
+ for (Iterator iter = m.entrySetByValue().iterator();
+ iter.hasNext(); ) {
+ iter.next();
+ iter.remove();
+
+ --count;
+
+ assertEquals(count, m.size());
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ Collection c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ }
+
+ try {
+ m.entrySetByValue().addAll(c1);
+ fail("should have caught exception of addAll()");
+ } catch (UnsupportedOperationException ignored) {}
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m.entrySetByValue().clear();
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ int x = 0;
+
+ for (Iterator iter = m.entrySetByValue().iterator();
+ iter.hasNext(); ) {
+ Map.Entry entry = (Map.Entry) iter.next();
+
+ assertSame(entry.getKey(), nodes[x].getKey());
+ assertSame(entry.getValue(), nodes[x]);
+
+ x++;
+ }
+ }
+
+ /**
+ * test keySetByValue() method
+ */
+ public void testKeySetByValue() {
+
+ testKeySetByValue(new DoubleOrderedMap());
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ testKeySetByValue(m);
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ int count = m.size();
+
+ for (Iterator iter = m.keySetByValue().iterator(); iter.hasNext(); )
+{
+ iter.next();
+ iter.remove();
+
+ --count;
+
+ assertEquals(count, m.size());
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ Set s = m.keySetByValue();
+
+ try {
+ s.remove(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ s.remove(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ Comparable key = nodes[k].getKey();
+
+ assertTrue(s.remove(key));
+ assertTrue(!s.contains(key));
+ assertTrue(!m.containsKey(key));
+ assertTrue(!m.containsValue(nodes[k]));
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ Collection c1 = new LinkedList();
+ Collection c2 = new LinkedList();
+
+ c2.add(new Integer(-99));
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ c2.add(nodes[k].getKey());
+ }
+
+ assertTrue(m.keySetByValue().containsAll(c1));
+ assertTrue(!m.keySetByValue().containsAll(c2));
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ c1.add(new Integer(-55));
+
+ try {
+ m.keySetByValue().addAll(c1);
+ fail("should have caught exception of addAll()");
+ } catch (UnsupportedOperationException ignored) {}
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ }
+
+ assertTrue(!m.keySetByValue().retainAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 1) {
+ c1.add(nodes[k].getKey());
+ }
+ }
+
+ assertTrue(m.keySetByValue().retainAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(m.keySetByValue().retainAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(!m.keySetByValue().removeAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 0) {
+ c1.add(nodes[k].getKey());
+ }
+ }
+
+ assertTrue(m.keySetByValue().removeAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k].getKey());
+ }
+
+ assertTrue(m.keySetByValue().removeAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m.keySetByValue().clear();
+ assertEquals(0, m.size());
+ }
+
+ /**
+ * test valuesByValue() method
+ */
+ public void testValuesByValue() {
+
+ testValuesByValue(new DoubleOrderedMap());
+
+ DoubleOrderedMap m = new DoubleOrderedMap();
+ LocalTestNode nodes[] = makeLocalNodes();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ testValuesByValue(m);
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ int count = m.size();
+
+ for (Iterator iter = m.valuesByValue().iterator(); iter.hasNext(); )
+{
+ iter.next();
+ iter.remove();
+
+ --count;
+
+ assertEquals(count, m.size());
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ count = m.size();
+
+ Collection s = m.valuesByValue();
+
+ for (int k = 0; k < count; k++) {
+ assertTrue(s.remove(nodes[k]));
+ assertTrue(!s.contains(nodes[k]));
+ assertTrue(!m.containsKey(nodes[k].getKey()));
+ assertTrue(!m.containsValue(nodes[k]));
+ }
+
+ assertTrue(m.isEmpty());
+
+ m = new DoubleOrderedMap();
+
+ Collection c1 = new LinkedList();
+ Collection c2 = new LinkedList();
+
+ c2.add(new LocalTestNode(-123));
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ c2.add(nodes[k]);
+ }
+
+ assertTrue(m.valuesByValue().containsAll(c1));
+ assertTrue(!m.valuesByValue().containsAll(c2));
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ }
+
+ try {
+ m.valuesByValue().addAll(c1);
+ fail("should have caught exception of addAll()");
+ } catch (UnsupportedOperationException ignored) {}
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ }
+
+ assertTrue(!m.valuesByValue().retainAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 1) {
+ c1.add(nodes[k]);
+ }
+ }
+
+ assertTrue(m.valuesByValue().retainAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(m.valuesByValue().retainAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ assertTrue(!m.valuesByValue().removeAll(c1));
+ assertEquals(nodes.length, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+
+ if (k % 2 == 0) {
+ c1.add(nodes[k]);
+ }
+ }
+
+ assertTrue(m.valuesByValue().removeAll(c1));
+ assertEquals(nodes.length / 2, m.size());
+
+ m = new DoubleOrderedMap();
+ c1 = new LinkedList();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ c1.add(nodes[k]);
+ }
+
+ assertTrue(m.valuesByValue().removeAll(c1));
+ assertEquals(0, m.size());
+
+ m = new DoubleOrderedMap();
+
+ for (int k = 0; k < nodes.length; k++) {
+ m.put(nodes[k].getKey(), nodes[k]);
+ }
+
+ m.valuesByValue().clear();
+ assertEquals(0, m.size());
+ }
+
+ /* ********** START helper methods ********** */
+ private void testKeySet(final Map m) {
+
+ Set s = m.keySet();
+
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ LocalTestNode node = new LocalTestNode(-1);
+
+ m.put(node.getKey(), node);
+ assertTrue(s.contains(node.getKey()));
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ m.remove(node.getKey());
+ assertTrue(!s.contains(node.getKey()));
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ try {
+ s.contains(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ s.contains(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < m.size(); k++) {
+ assertTrue(s.contains(new Integer(k)));
+ }
+
+ int count = 0;
+
+ for (Iterator iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ ++count;
+ }
+
+ assertEquals(count, s.size());
+
+ // force the map to have some content
+ m.put(node.getKey(), node);
+
+ Iterator iter = m.keySet().iterator();
+ LocalTestNode node2 = new LocalTestNode(-2);
+
+ m.put(node2.getKey(), node2);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a put");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.remove(node2.getKey());
+
+ iter = s.iterator();
+
+ m.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Map remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ s.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Set remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ iter = s.iterator();
+ count = 0;
+
+ boolean terminated = false;
+
+ try {
+ while (true) {
+ iter.next();
+
+ ++count;
+ }
+ } catch (NoSuchElementException ignored) {
+ terminated = true;
+ }
+
+ assertTrue(terminated);
+ assertEquals(m.size(), count);
+
+ iter = s.iterator();
+
+ try {
+ iter.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ iter.next();
+ m.put(node2.getKey(), node2);
+
+ try {
+ iter.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ Iterator iter2 = s.iterator();
+
+ iter2.next();
+
+ LocalTestNode node3 = new LocalTestNode(-3);
+
+ m.put(node3.getKey(), node3);
+
+ try {
+ iter2.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ int removalCount = 0;
+
+ for (iter = s.iterator(); iter.hasNext(); ) {
+ if (iter.next().equals(node.getKey())) {
+ try {
+ iter.remove();
+
+ ++removalCount;
+
+ iter.remove();
+ fail("2nd remove should have failed");
+ } catch (IllegalStateException ignored) {
+ assertEquals(1, removalCount);
+ }
+ }
+ }
+
+ assertEquals(1, removalCount);
+ assertTrue(!s.contains(node.getKey()));
+
+ removalCount = 0;
+
+ m.put(node.getKey(), node);
+
+ Object[] a1 = s.toArray();
+
+ assertEquals(s.size(), a1.length);
+
+ if (a1.length > 1) {
+ Comparable first = (Comparable) a1[0];
+
+ for (int k = 1; k < a1.length; k++) {
+ Comparable second = (Comparable) a1[k];
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+
+ iter = s.iterator();
+ first = (Comparable) iter.next();
+
+ for (; iter.hasNext(); ) {
+ Comparable second = (Comparable) iter.next();
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ String array2[] = (String[]) s.toArray(new String[0]);
+
+ if (s.size() != 0) {
+ fail("should have caught exception creating an invalid array");
+ }
+ } catch (ArrayStoreException ignored) {}
+
+ Comparable array2[] = (Comparable[]) s.toArray(new Comparable[0]);
+ Integer array3[] = (Integer[]) s.toArray(new Integer[s.size()]);
+
+ if (array3.length > 1) {
+ Integer first = array3[0];
+
+ for (int k = 1; k < array3.length; k++) {
+ Integer second = array3[k];
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ s.add("foo");
+ fail("should have thrown an exception");
+ } catch (UnsupportedOperationException ignored) {}
+
+ assertTrue(!s.equals(null));
+ assertEquals(s, s);
+
+ Set hs = new HashSet(s);
+
+ assertEquals(s, hs);
+ assertEquals(hs, s);
+ assertEquals(s.hashCode(), hs.hashCode());
+ }
+
+ private void testKeySetByValue(final DoubleOrderedMap m) {
+
+ Set s = m.keySetByValue();
+
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ LocalTestNode node = new LocalTestNode(-1);
+
+ m.put(node.getKey(), node);
+ assertTrue(s.contains(node.getKey()));
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ m.remove(node.getKey());
+ assertTrue(!s.contains(node.getKey()));
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ try {
+ s.contains(null);
+ fail("should have caught NullPointerException");
+ } catch (NullPointerException ignored) {}
+
+ try {
+ s.contains(new Object());
+ fail("should have caught ClassCastException");
+ } catch (ClassCastException ignored) {}
+
+ for (int k = 0; k < m.size(); k++) {
+ assertTrue(s.contains(new Integer(k)));
+ }
+
+ int count = 0;
+
+ for (Iterator iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ ++count;
+ }
+
+ assertEquals(count, s.size());
+
+ // force the map to have some content
+ m.put(node.getKey(), node);
+
+ Iterator iter = m.keySetByValue().iterator();
+ LocalTestNode node2 = new LocalTestNode(-2);
+
+ m.put(node2.getKey(), node2);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a put");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.remove(node2.getKey());
+
+ iter = s.iterator();
+
+ m.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Map remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ s.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Set remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ iter = s.iterator();
+ count = 0;
+
+ boolean terminated = false;
+
+ try {
+ while (true) {
+ iter.next();
+
+ ++count;
+ }
+ } catch (NoSuchElementException ignored) {
+ terminated = true;
+ }
+
+ assertTrue(terminated);
+ assertEquals(m.size(), count);
+
+ iter = s.iterator();
+
+ try {
+ iter.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ iter.next();
+ m.put(node2.getKey(), node2);
+
+ try {
+ iter.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ Iterator iter2 = s.iterator();
+
+ iter2.next();
+
+ LocalTestNode node3 = new LocalTestNode(-3);
+
+ m.put(node3.getKey(), node3);
+
+ try {
+ iter2.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ int removalCount = 0;
+
+ for (iter = s.iterator(); iter.hasNext(); ) {
+ if (iter.next().equals(node.getKey())) {
+ try {
+ iter.remove();
+
+ ++removalCount;
+
+ iter.remove();
+ fail("2nd remove should have failed");
+ } catch (IllegalStateException ignored) {
+ assertEquals(1, removalCount);
+ }
+ }
+ }
+
+ assertEquals(1, removalCount);
+ assertTrue(!s.contains(node.getKey()));
+
+ removalCount = 0;
+
+ m.put(node.getKey(), node);
+
+ Object[] a1 = s.toArray();
+
+ assertEquals(s.size(), a1.length);
+
+ // if (a1.length > 1)
+ // {
+ // Comparable first = ( Comparable ) a1[ 0 ];
+ // for (int k = 1; k < a1.length; k++)
+ // {
+ // Comparable second = ( Comparable ) a1[ k ];
+ // assertTrue(first.compareTo(second) < 0);
+ // first = second;
+ // }
+ // iter = s.iterator();
+ // first = ( Comparable ) iter.next();
+ // for (; iter.hasNext(); )
+ // {
+ // Comparable second = ( Comparable ) iter.next();
+ // assertTrue(first.compareTo(second) < 0);
+ // first = second;
+ // }
+ // }
+ try {
+ String array2[] = (String[]) s.toArray(new String[0]);
+
+ if (s.size() != 0) {
+ fail("should have caught exception creating an invalid array");
+ }
+ } catch (ArrayStoreException ignored) {}
+
+ Comparable array2[] = (Comparable[]) s.toArray(new Comparable[0]);
+ Integer array3[] = (Integer[]) s.toArray(new Integer[s.size()]);
+
+ // if (array3.length > 1)
+ // {
+ // Integer first = array3[ 0 ];
+ // for (int k = 1; k < array3.length; k++)
+ // {
+ // Integer second = array3[ k ];
+ // assertTrue(first.compareTo(second) < 0);
+ // first = second;
+ // }
+ // }
+ try {
+ s.add("foo");
+ fail("should have thrown an exception");
+ } catch (UnsupportedOperationException ignored) {}
+
+ assertTrue(!s.equals(null));
+ assertEquals(s, s);
+
+ Set hs = new HashSet(s);
+
+ assertEquals(s, hs);
+ assertEquals(hs, s);
+ assertEquals(s.hashCode(), hs.hashCode());
+ }
+
+ private void testValues(Map m) {
+
+ Collection s = m.values();
+
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ LocalTestNode node = new LocalTestNode(-1);
+
+ m.put(node.getKey(), node);
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ m.remove(node.getKey());
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ assertTrue(!s.contains(node));
+
+ for (int k = 0; k < m.size(); k++) {
+ assertTrue(s.contains(new LocalTestNode(k)));
+ }
+
+ m.put(node.getKey(), node);
+ assertTrue(s.contains(node));
+ m.remove(node.getKey());
+ assertTrue(!s.contains(node));
+
+ int count = 0;
+
+ for (Iterator iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ ++count;
+ }
+
+ assertEquals(s.size(), count);
+
+ LocalTestNode node4 = new LocalTestNode(-4);
+
+ m.put(node4.getKey(), node4);
+
+ Iterator iter = s.iterator();
+
+ m.put(node.getKey(), node);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a put");
+ } catch (ConcurrentModificationException ignored) {}
+
+ iter = s.iterator();
+
+ m.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Map remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ s.remove(node);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Set remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ iter = s.iterator();
+ count = 0;
+
+ boolean terminated = false;
+
+ try {
+ while (true) {
+ iter.next();
+
+ ++count;
+ }
+ } catch (NoSuchElementException ignored) {
+ terminated = true;
+ }
+
+ assertTrue(terminated);
+ assertEquals(m.size(), count);
+
+ iter = s.iterator();
+
+ try {
+ iter.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ Iterator iter2 = s.iterator();
+
+ try {
+ iter2.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ iter.next();
+
+ LocalTestNode node2 = new LocalTestNode(-2);
+
+ m.put(node2.getKey(), node2);
+
+ try {
+ iter.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ LocalTestNode node3 = new LocalTestNode(-3);
+
+ m.put(node3.getKey(), node3);
+
+ iter2 = s.iterator();
+
+ while (iter2.hasNext()) {
+ iter2.next();
+ }
+
+ int removalCount = 0;
+
+ for (iter = s.iterator(); iter.hasNext(); ) {
+ if (iter.next().equals(node3)) {
+ try {
+ iter.remove();
+
+ ++removalCount;
+
+ iter.remove();
+ fail("2nd remove should have failed");
+ } catch (IllegalStateException ignored) {
+ assertEquals(1, removalCount);
+ }
+ }
+ }
+
+ assertEquals(1, removalCount);
+ assertTrue(!s.contains(node3));
+
+ Object[] a1 = s.toArray();
+
+ assertEquals(s.size(), a1.length);
+
+ if (a1.length > 1) {
+ Comparable first = (Comparable) a1[0];
+
+ for (int k = 1; k < a1.length; k++) {
+ Comparable second = (Comparable) a1[k];
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+
+ iter = s.iterator();
+ first = (Comparable) iter.next();
+
+ for (; iter.hasNext(); ) {
+ Comparable second = (Comparable) iter.next();
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ String array2[] = (String[]) s.toArray(new String[0]);
+
+ if (s.size() != 0) {
+ fail("should have caught exception creating an invalid array");
+ }
+ } catch (ArrayStoreException ignored) {}
+
+ m.remove(node.getKey());
+ m.remove(node2.getKey());
+ m.remove(node3.getKey());
+
+ LocalTestNode array2[] =
+ (LocalTestNode[]) s.toArray(new LocalTestNode[0]);
+ LocalTestNode array3[] =
+ (LocalTestNode[]) s.toArray(new LocalTestNode[s.size()]);
+
+ if (array3.length > 1) {
+ LocalTestNode first = array3[0];
+
+ for (int k = 1; k < array3.length; k++) {
+ LocalTestNode second = array3[k];
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ s.add(node.getKey());
+ fail("should have thrown an exception");
+ } catch (UnsupportedOperationException ignored) {}
+
+ assertTrue(!s.equals(null));
+ assertEquals(s, s);
+
+ Set hs = new HashSet(s);
+
+ assertTrue(!s.equals(hs));
+ assertTrue(!hs.equals(s));
+ }
+
+ private void testValuesByValue(DoubleOrderedMap m) {
+
+ Collection s = m.valuesByValue();
+
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ LocalTestNode node = new LocalTestNode(-1);
+
+ m.put(node.getKey(), node);
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ m.remove(node.getKey());
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ assertTrue(!s.contains(node));
+
+ for (int k = 0; k < m.size(); k++) {
+ assertTrue(s.contains(new LocalTestNode(k)));
+ }
+
+ m.put(node.getKey(), node);
+ assertTrue(s.contains(node));
+ m.remove(node.getKey());
+ assertTrue(!s.contains(node));
+
+ int count = 0;
+
+ for (Iterator iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ ++count;
+ }
+
+ assertEquals(s.size(), count);
+
+ LocalTestNode node4 = new LocalTestNode(-4);
+
+ m.put(node4.getKey(), node4);
+
+ Iterator iter = s.iterator();
+
+ m.put(node.getKey(), node);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a put");
+ } catch (ConcurrentModificationException ignored) {}
+
+ iter = s.iterator();
+
+ m.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Map remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ s.remove(node);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Set remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ iter = s.iterator();
+ count = 0;
+
+ boolean terminated = false;
+
+ try {
+ while (true) {
+ iter.next();
+
+ ++count;
+ }
+ } catch (NoSuchElementException ignored) {
+ terminated = true;
+ }
+
+ assertTrue(terminated);
+ assertEquals(m.size(), count);
+
+ iter = s.iterator();
+
+ try {
+ iter.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ Iterator iter2 = s.iterator();
+
+ try {
+ iter2.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+
+ iter.next();
+
+ LocalTestNode node2 = new LocalTestNode(-2);
+
+ m.put(node2.getKey(), node2);
+
+ try {
+ iter.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ LocalTestNode node3 = new LocalTestNode(-3);
+
+ m.put(node3.getKey(), node3);
+
+ iter2 = s.iterator();
+
+ while (iter2.hasNext()) {
+ iter2.next();
+ }
+
+ int removalCount = 0;
+
+ for (iter = s.iterator(); iter.hasNext(); ) {
+ if (iter.next().equals(node3)) {
+ try {
+ iter.remove();
+
+ ++removalCount;
+
+ iter.remove();
+ fail("2nd remove should have failed");
+ } catch (IllegalStateException ignored) {
+ assertEquals(1, removalCount);
+ }
+ }
+ }
+
+ assertEquals(1, removalCount);
+ assertTrue(!s.contains(node3));
+
+ Object[] a1 = s.toArray();
+
+ assertEquals(s.size(), a1.length);
+
+ try {
+ String array2[] = (String[]) s.toArray(new String[0]);
+
+ if (s.size() != 0) {
+ fail("should have caught exception creating an invalid array");
+ }
+ } catch (ArrayStoreException ignored) {}
+
+ m.remove(node.getKey());
+ m.remove(node2.getKey());
+ m.remove(node3.getKey());
+
+ LocalTestNode array2[] =
+ (LocalTestNode[]) s.toArray(new LocalTestNode[0]);
+ LocalTestNode array3[] =
+ (LocalTestNode[]) s.toArray(new LocalTestNode[s.size()]);
+
+ try {
+ s.add(node.getKey());
+ fail("should have thrown an exception");
+ } catch (UnsupportedOperationException ignored) {}
+
+ assertTrue(!s.equals(null));
+ assertEquals(s, s);
+
+ Set hs = new HashSet(s);
+
+ assertTrue(!s.equals(hs));
+ assertTrue(!hs.equals(s));
+ }
+
+ private void testEntrySet(Map m) {
+
+ Set s = m.entrySet();
+
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ LocalTestNode node = new LocalTestNode(-1);
+
+ m.put(node.getKey(), node);
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ m.remove(node.getKey());
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ int count = 0;
+
+ for (Iterator iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ ++count;
+ }
+
+ assertEquals(s.size(), count);
+
+ LocalTestNode node2 = new LocalTestNode(-2);
+
+ if (m.size() == 0) {
+ m.put(node2.getKey(), node2);
+ }
+
+ Iterator iter = s.iterator();
+
+ m.put(node.getKey(), node);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a put");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.remove(node2.getKey());
+
+ iter = s.iterator();
+
+ m.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Map remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+ count = 0;
+
+ boolean terminated = false;
+
+ try {
+ while (true) {
+ iter.next();
+
+ ++count;
+ }
+ } catch (NoSuchElementException ignored) {
+ terminated = true;
+ }
+
+ assertTrue(terminated);
+ assertEquals(m.size(), count);
+
+ iter = s.iterator();
+
+ try {
+ iter.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ iter = s.iterator();
+
+ iter.next();
+
+ LocalTestNode node3 = new LocalTestNode(-3);
+
+ m.put(node3.getKey(), node3);
+
+ try {
+ iter.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ int removalCount = 0;
+ int when = m.size() / 2;
+ int timer = 0;
+
+ for (iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ if (timer == when) {
+ try {
+ iter.remove();
+
+ ++removalCount;
+
+ iter.remove();
+ fail("2nd remove should have failed");
+ } catch (IllegalStateException ignored) {
+ assertEquals(1, removalCount);
+ }
+ }
+
+ timer++;
+ }
+
+ assertEquals(1, removalCount);
+
+ Iterator iter2 = s.iterator();
+
+ try {
+ iter2.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ iter2 = s.iterator();
+
+ while (iter2.hasNext()) {
+ iter2.next();
+ }
+
+ LocalTestNode node4 = new LocalTestNode(-4);
+
+ m.put(node4.getKey(), node4);
+
+ try {
+ iter2.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ Object[] a1 = s.toArray();
+
+ assertEquals(s.size(), a1.length);
+
+ if (a1.length > 1) {
+ Map.Entry first = (Map.Entry) a1[0];
+
+ for (int k = 1; k < a1.length; k++) {
+ Map.Entry second = (Map.Entry) a1[k];
+
+ assertTrue(((Comparable) first.getKey())
+ .compareTo((Comparable) second.getKey()) < 0);
+
+ first = second;
+ }
+
+ iter = s.iterator();
+ first = (Map.Entry) iter.next();
+
+ for (; iter.hasNext(); ) {
+ Map.Entry second = (Map.Entry) iter.next();
+
+ assertTrue(((Comparable) first.getKey())
+ .compareTo((Comparable) second.getKey()) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ Integer array2[] = (Integer[]) s.toArray(new Integer[0]);
+
+ if (s.size() != 0) {
+ fail("should have caught exception creating an invalid array");
+ }
+ } catch (ArrayStoreException ignored) {}
+
+ Map.Entry array2[] = (Map.Entry[]) s.toArray(new Map.Entry[0]);
+ Map.Entry array3[] = (Map.Entry[]) s.toArray(new Map.Entry[s.size()]);
+
+ if (array3.length > 1) {
+ Comparable first = (Comparable) ((Map.Entry) array3[0]).getKey();
+
+ for (int k = 1; k < array3.length; k++) {
+ Comparable second =
+ (Comparable) ((Map.Entry) array3[k]).getKey();
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ s.add(node.getKey());
+ fail("should have thrown an exception");
+ } catch (UnsupportedOperationException ignored) {}
+
+ assertTrue(!s.equals(null));
+ assertEquals("SetEquality 1", s, s);
+
+ Set hs = new HashSet(s);
+
+ assertEquals("SetEquality 2", s, hs);
+ assertEquals("SetEquality 3", hs, s);
+ assertEquals(s.hashCode(), hs.hashCode());
+ }
+
+ private void testEntrySetByValue(DoubleOrderedMap m) {
+
+ Set s = m.entrySetByValue();
+
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ LocalTestNode node = new LocalTestNode(-1);
+
+ m.put(node.getKey(), node);
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+ m.remove(node.getKey());
+ assertEquals(m.size(), s.size());
+ assertEquals(m.isEmpty(), s.isEmpty());
+
+ int count = 0;
+
+ for (Iterator iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ ++count;
+ }
+
+ assertEquals(s.size(), count);
+
+ LocalTestNode node2 = new LocalTestNode(-2);
+
+ if (m.size() == 0) {
+ m.put(node2.getKey(), node2);
+ }
+
+ Iterator iter = s.iterator();
+
+ m.put(node.getKey(), node);
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a put");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.remove(node2.getKey());
+
+ iter = s.iterator();
+
+ m.remove(node.getKey());
+
+ try {
+ iter.next();
+ fail("next() should have thrown an exception after a Map remove");
+ } catch (ConcurrentModificationException ignored) {}
+
+ m.put(node.getKey(), node);
+
+ iter = s.iterator();
+ count = 0;
+
+ boolean terminated = false;
+
+ try {
+ while (true) {
+ iter.next();
+
+ ++count;
+ }
+ } catch (NoSuchElementException ignored) {
+ terminated = true;
+ }
+
+ assertTrue(terminated);
+ assertEquals(m.size(), count);
+
+ iter = s.iterator();
+
+ try {
+ iter.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ iter = s.iterator();
+
+ iter.next();
+
+ LocalTestNode node3 = new LocalTestNode(-3);
+
+ m.put(node3.getKey(), node3);
+
+ try {
+ iter.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ int removalCount = 0;
+ int when = m.size() / 2;
+ int timer = 0;
+
+ for (iter = s.iterator(); iter.hasNext(); ) {
+ iter.next();
+
+ if (timer == when) {
+ try {
+ iter.remove();
+
+ ++removalCount;
+
+ iter.remove();
+ fail("2nd remove should have failed");
+ } catch (IllegalStateException ignored) {
+ assertEquals(1, removalCount);
+ }
+ }
+
+ timer++;
+ }
+
+ assertEquals(1, removalCount);
+
+ Iterator iter2 = s.iterator();
+
+ try {
+ iter2.remove();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException ignored) {}
+
+ iter2 = s.iterator();
+
+ while (iter2.hasNext()) {
+ iter2.next();
+ }
+
+ LocalTestNode node4 = new LocalTestNode(-4);
+
+ m.put(node4.getKey(), node4);
+
+ try {
+ iter2.remove();
+ fail("should have thrown exception");
+ } catch (ConcurrentModificationException ignored) {}
+
+ Object[] a1 = s.toArray();
+
+ assertEquals(s.size(), a1.length);
+
+ if (a1.length > 1) {
+ Map.Entry first = (Map.Entry) a1[0];
+
+ for (int k = 1; k < a1.length; k++) {
+ Map.Entry second = (Map.Entry) a1[k];
+
+ assertTrue(((Comparable) first.getKey())
+ .compareTo((Comparable) second.getKey()) < 0);
+
+ first = second;
+ }
+
+ iter = s.iterator();
+ first = (Map.Entry) iter.next();
+
+ for (; iter.hasNext(); ) {
+ Map.Entry second = (Map.Entry) iter.next();
+
+ assertTrue(((Comparable) first.getKey())
+ .compareTo((Comparable) second.getKey()) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ Integer array2[] = (Integer[]) s.toArray(new Integer[0]);
+
+ if (s.size() != 0) {
+ fail("should have caught exception creating an invalid array");
+ }
+ } catch (ArrayStoreException ignored) {}
+
+ Map.Entry array2[] = (Map.Entry[]) s.toArray(new Map.Entry[0]);
+ Map.Entry array3[] = (Map.Entry[]) s.toArray(new Map.Entry[s.size()]);
+
+ if (array3.length > 1) {
+ Comparable first =
+ (Comparable) ((Map.Entry) array3[0]).getValue();
+
+ for (int k = 1; k < array3.length; k++) {
+ Comparable second =
+ (Comparable) ((Map.Entry) array3[k]).getValue();
+
+ assertTrue(first.compareTo(second) < 0);
+
+ first = second;
+ }
+ }
+
+ try {
+ s.add(node.getKey());
+ fail("should have thrown an exception");
+ } catch (UnsupportedOperationException ignored) {}
+
+ assertTrue(!s.equals(null));
+ assertEquals("SetEquality 1", s, s);
+
+ Set hs = new HashSet(s);
+
+ assertEquals("SetEquality 2", s, hs);
+ assertEquals("SetEquality 3", hs, s);
+ assertEquals(s.hashCode(), hs.hashCode());
+ }
+
+ private LocalTestNode[] makeLocalNodes() {
+
+ LocalTestNode nodes[] = new LocalTestNode[1023];
+
+ for (int k = 0; k < nodes.length; k++) {
+ nodes[k] = new LocalTestNode(k);
+ }
+
+ return nodes;
+ }
+
+ /* ********** END helper methods ********** */
+
+ /**
+ * Method main
+ *
+ * @param unusedArgs
+ */
+ public static void main(final String unusedArgs[]) {
+ junit.textui.TestRunner.run(TestDoubleOrderedMap.class);
+ }
+}
|