From 6a5c4a72c6a74802025906c1d9a256d50cefc8cb Mon Sep 17 00:00:00 2001 From: "Craig R. McClanahan" Date: Sun, 20 Jan 2002 04:36:08 +0000 Subject: [PATCH] Committed the DoubleOrderedMap class and associated unit tests (gotta love it when the unit test source is 50% longer than the class being tested :-). The only change I made was to use the long-form version of the Apache license -- according to the Apache board, that is the only acceptable approach at the moment. Any remaining classes in commons-collections (or elsewhere) using the short form license should be updated. Submitted by: Marc Johnson git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@130509 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/collections/DoubleOrderedMap.java | 2046 ++++++++++++ .../apache/commons/collections/package.html | 5 +- .../commons/collections/LocalTestNode.java | 164 + .../apache/commons/collections/TestAll.java | 9 +- .../collections/TestDoubleOrderedMap.java | 2798 +++++++++++++++++ 5 files changed, 5016 insertions(+), 6 deletions(-) create mode 100644 src/java/org/apache/commons/collections/DoubleOrderedMap.java create mode 100644 src/test/org/apache/commons/collections/LocalTestNode.java create mode 100644 src/test/org/apache/commons/collections/TestDoubleOrderedMap.java 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); + } +}