You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sc...@apache.org on 2003/11/08 19:52:51 UTC

cvs commit: jakarta-commons/collections/src/java/org/apache/commons/collections TreeBidiMap.java

scolebourne    2003/11/08 10:52:51

  Modified:    collections/src/test/org/apache/commons/collections
                        TestAll.java
  Added:       collections/src/test/org/apache/commons/collections
                        TestTreeBidiMap.java
               collections/src/java/org/apache/commons/collections
                        TreeBidiMap.java
  Log:
  Add TreeBidiMap implementation
  
  Revision  Changes    Path
  1.53      +3 -2      jakarta-commons/collections/src/test/org/apache/commons/collections/TestAll.java
  
  Index: TestAll.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/collections/src/test/org/apache/commons/collections/TestAll.java,v
  retrieving revision 1.52
  retrieving revision 1.53
  diff -u -r1.52 -r1.53
  --- TestAll.java	31 Oct 2003 01:26:25 -0000	1.52
  +++ TestAll.java	8 Nov 2003 18:52:51 -0000	1.53
  @@ -115,6 +115,7 @@
           suite.addTest(TestSequencedHashMap.suite());
           suite.addTest(TestStaticBucketMap.suite());
           suite.addTest(TestTreeBag.suite());
  +        suite.addTest(TestTreeBidiMap.suite());
           suite.addTest(TestUnboundedFifoBuffer.suite());
           suite.addTest(TestEnumerationUtils.suite());
           return suite;
  
  
  
  1.1                  jakarta-commons/collections/src/test/org/apache/commons/collections/TestTreeBidiMap.java
  
  Index: TestTreeBidiMap.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/collections/src/test/org/apache/commons/collections/TestTreeBidiMap.java,v 1.1 2003/11/08 18:52:51 scolebourne Exp $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001-2003 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 acknowledgement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgement may appear in the software itself,
   *    if and wherever such third-party acknowledgements 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 Software Foundation.
   *
   * 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
   * <http://www.apache.org/>.
   *
   */
  package org.apache.commons.collections;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.TreeMap;
  
  import junit.framework.Test;
  import junit.textui.TestRunner;
  
  /**
   * JUnit tests.
   * 
   * @version $Revision: 1.1 $ $Date: 2003/11/08 18:52:51 $
   * 
   * @author Stephen Colebourne
   */
  public class TestTreeBidiMap extends AbstractTestBidiMap {
  
      public static void main(String[] args) {
          TestRunner.run(suite());
      }
      
      public static Test suite() {
          return BulkTest.makeSuite(TestTreeBidiMap.class);
      }
  
      public TestTreeBidiMap(String testName) {
          super(testName);
      }
  
      protected BidiMap makeEmptyBidiMap() {
          return new TreeBidiMap();
      }
      
      protected Map makeConfirmedMap() {
          return new TreeMap();
      }
  
      /**
       * Override to prevent infinite recursion of tests.
       */
      protected String[] ignoredTests() {
          return new String[] {"TestTreeBidiMap.bulkTestInverseMap.bulkTestInverseMap"};
      }
      
      protected boolean isAllowNullKey() {
          return false;
      }
      
      protected boolean isAllowNullValue() {
          return false;
      }
      
      protected boolean isSetValueSupported() {
          return false;
      }
      
      public void testFirstKey() {
          TreeBidiMap bidi = (TreeBidiMap) makeEmptyMap();
          try {
              bidi.firstKey();
              fail();
          } catch (NoSuchElementException ex) {}
          
          resetFull();
          bidi = (TreeBidiMap) map;
          Object confirmedFirst = confirmed.keySet().iterator().next();
          assertEquals(confirmedFirst, bidi.firstKey());
      }
      
      public void testLastKey() {
          TreeBidiMap bidi = (TreeBidiMap) makeEmptyMap();
          try {
              bidi.lastKey();
              fail();
          } catch (NoSuchElementException ex) {}
          
          resetFull();
          bidi = (TreeBidiMap) map;
          Object confirmedLast = null;
          for (Iterator it = confirmed.keySet().iterator(); it.hasNext();) {
              confirmedLast = it.next();
          }
          assertEquals(confirmedLast, bidi.lastKey());
      }
      
      public void testNextKey() {
          TreeBidiMap bidi = (TreeBidiMap) makeEmptyMap();
          try {
              bidi.nextKey(null);
              fail();
          } catch (NullPointerException ex) {}
          try {
              bidi.nextKey(new Object());
              fail();
          } catch (ClassCastException ex) {}
          
          resetFull();
          bidi = (TreeBidiMap) map;
          Iterator it = confirmed.keySet().iterator();
          Object confirmedLast = it.next();
          while (it.hasNext()) {
              Object confirmedObject = it.next();
              assertEquals(confirmedObject, bidi.nextKey(confirmedLast));
              confirmedLast = confirmedObject;
          }
          assertEquals(null, bidi.nextKey(confirmedLast));
          
          try {
              bidi.nextKey(null);
              fail();
          } catch (NullPointerException ex) {}
          try {
              bidi.nextKey(new Object());
              fail();
          } catch (ClassCastException ex) {}
      }
      
      public void testPreviousKey() {
          TreeBidiMap bidi = (TreeBidiMap) makeEmptyMap();
          try {
              bidi.previousKey(null);
              fail();
          } catch (NullPointerException ex) {}
          try {
              bidi.previousKey(new Object());
              fail();
          } catch (ClassCastException ex) {}
          
          resetFull();
          bidi = (TreeBidiMap) map;
          List list = new ArrayList(confirmed.keySet());
          Collections.reverse(list);
          Iterator it = list.iterator();
          Object confirmedLast = it.next();
          while (it.hasNext()) {
              Object confirmedObject = it.next();
              assertEquals(confirmedObject, bidi.previousKey(confirmedLast));
              confirmedLast = confirmedObject;
          }
          assertEquals(null, bidi.previousKey(confirmedLast));
          
          try {
              bidi.previousKey(null);
              fail();
          } catch (NullPointerException ex) {}
          try {
              bidi.previousKey(new Object());
              fail();
          } catch (ClassCastException ex) {}
      }
      
  }
  
  
  
  1.1                  jakarta-commons/collections/src/java/org/apache/commons/collections/TreeBidiMap.java
  
  Index: TreeBidiMap.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/TreeBidiMap.java,v 1.1 2003/11/08 18:52:51 scolebourne Exp $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002-2003 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 acknowledgement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgement may appear in the software itself,
   *    if and wherever such third-party acknowledgements 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 Software Foundation.
   *
   * 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
   * <http://www.apache.org/>.
   *
   */
  package org.apache.commons.collections;
  
  import java.util.AbstractSet;
  import java.util.Collection;
  import java.util.ConcurrentModificationException;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Set;
  
  import org.apache.commons.collections.iterators.MapIterator;
  import org.apache.commons.collections.iterators.OrderedMapIterator;
  import org.apache.commons.collections.pairs.UnmodifiableMapEntry;
  
  /**
   * Red-Black tree-based implementation of BidiMap.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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. (See also the
   * {@link org.apache.commons.collections.DualTreeBidiMap DualTreeBidiMap} and
   * {@link org.apache.commons.collections.DualHashBidiMap DualHashBidiMap}
   * implementations.)
   * <p>
   * 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.
   * <p>
   * The Map.Entry instances returned by the appropriate methods will
   * not allow setValue() and will throw an
   * UnsupportedOperationException on attempts to call that method.
   *
   * @see BidiMap
   * @see DualTreeBidiMap
   * @see DualHashBidiMap
   * @since Commons Collections 2.0 (previously DoubleOrderedMap)
   * @version $Revision: 1.1 $ $Date: 2003/11/08 18:52:51 $
   * 
   * @author Marc Johnson
   * @author Stephen Colebourne
   */
  public class TreeBidiMap implements BidiMap {
  
      private static final int KEY = 0;
      private static final int VALUE = 1;
      private static final int MAPENTRY = 2;
      private static final int INVERSEMAPENTRY = 3;
      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" };
      
      private Node[] rootNode = new Node[2];
      private int nodeCount = 0;
      private int modifications = 0;
      private Set keySet;
      private Set valuesSet;
      private Set entrySet;
      private TreeBidiMap.Inverse inverse = null;
  
      //-----------------------------------------------------------------------
      /**
       * Constructs a new empty TreeBidiMap.
       */
      public TreeBidiMap() {
          super();
      }
  
      /**
       * Constructs a new TreeBidiMap by copying an existing Map.
       *
       * @param map  the map to copy
       * @throws ClassCastException if the keys/values in the map are
       *  not Comparable or are not mutually comparable
       * @throws NullPointerException if any key or value in the map is null
       */
      public TreeBidiMap(final Map map) {
          super();
          putAll(map);
      }
  
      //-----------------------------------------------------------------------
      /**
       * Returns the number of key-value mappings in this map.
       *
       * @return the number of key-value mappings in this map
       */
      public int size() {
          return nodeCount;
      }
  
      /**
       * Checks whether the map is empty or not.
       *
       * @return true if the map is empty
       */
      public boolean isEmpty() {
          return (nodeCount == 0);
      }
  
      /**
       * Checks whether this map contains the 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
       * @throws ClassCastException if the key is of an inappropriate type
       * @throws NullPointerException if the key is null
       */
      public boolean containsKey(final Object key) {
          checkKey(key);
          return (lookup((Comparable) key, KEY) != null);
      }
  
      /**
       * Checks whether this map contains the a mapping for the specified value.
       *
       * @param value  value whose presence in this map is to be tested
       * @return true if this map contains a mapping for the specified value
       * @throws ClassCastException if the value is of an inappropriate type
       * @throws NullPointerException if the value is null
       */
      public boolean containsValue(final Object value) {
          checkValue(value);
          return (lookup((Comparable) value, VALUE) != null);
      }
  
      /**
       * Gets 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
       * @throws ClassCastException if the key is of an inappropriate type
       * @throws NullPointerException if the key is null
       */
      public Object get(final Object key) {
          return doGet((Comparable) key, KEY);
      }
  
      /**
       * Puts the key-value pair into the map, replacing any previous pair.
       * <p>
       * When adding a key-value pair, the value may already exist in the map
       * against a different key. That mapping is removed, to ensure that the
       * value only occurs once in the inverse map.
       * <pre>
       *  BidiMap map1 = new TreeBidiMap();
       *  map.put("A","B");  // contains A mapped to B, as per Map
       *  map.put("A","C");  // contains A mapped to C, as per Map
       * 
       *  BidiMap map2 = new TreeBidiMap();
       *  map.put("A","B");  // contains A mapped to B, as per Map
       *  map.put("C","B");  // contains C mapped to B, key A is removed
       * </pre>
       *
       * @param key  key with which the specified value is to be  associated
       * @param value  value to be associated with the specified key
       * @return the previous value for the key
       * @throws ClassCastException if the key is of an inappropriate type
       * @throws NullPointerException if the key is null
       */
      public Object put(final Object key, final Object value) {
          return doPut((Comparable) key, (Comparable) value, KEY);
      }
  
      /**
       * Puts all the mappings from the specified map into this map.
       * 
       * @param map  the map to copy from
       */
      public void putAll(Map map) {
          Iterator it = map.entrySet().iterator();
          while (it.hasNext()) {
              Map.Entry entry = (Map.Entry) it.next();
              put(entry.getKey(), entry.getValue());
          }
      }
          
      /**
       * 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.
       * @throws ClassCastException if the key is of an inappropriate type
       * @throws NullPointerException if the key is null
       */
      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 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.
       * @throws ClassCastException if the value is of an inappropriate type
       * @throws NullPointerException if the value is null
       */
      public Object getKey(final Object value) {
          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.
       * @throws ClassCastException if the value is of an inappropriate type
       * @throws NullPointerException if the value is null
       */
      public Object removeValue(final Object value) {
          return doRemove((Comparable) value, VALUE);
      }
  
      //-----------------------------------------------------------------------
      /**
       * Gets the first (lowest) key currently in this map.
       *
       * @return the first (lowest) key currently in this sorted map
       * @throws NoSuchElementException if this map is empty
       */
      public Object firstKey() {
          if (nodeCount == 0) {
              throw new NoSuchElementException("Map is empty");
          }
          return leastNode(rootNode[KEY], KEY).getKey();
      }
  
      /**
       * Gets the last (highest) key currently in this map.
       *
       * @return the last (highest) key currently in this sorted map
       * @throws NoSuchElementException if this map is empty
       */
      public Object lastKey() {
          if (nodeCount == 0) {
              throw new NoSuchElementException("Map is empty");
          }
          return greatestNode(rootNode[KEY], KEY).getKey();
      }
      
      /**
       * Gets the next key after the one specified.
       *
       * @param the key to search for next from
       * @return the next key, null if no match or at end
       */
      public Object nextKey(Object key) {
          checkKey(key);
          Node node = nextGreater(lookup((Comparable) key, KEY), KEY);
          return (node == null ? null : node.getKey());
      }
  
      /**
       * Gets the previous key before the one specified.
       *
       * @param the key to search for previous from
       * @return the previous key, null if no match or at start
       */
      public Object previousKey(Object key) {
          checkKey(key);
          Node node = nextSmaller(lookup((Comparable) key, KEY), KEY);
          return (node == null ? null : node.getKey());
      }
      
      //-----------------------------------------------------------------------
      /**
       * Returns a set view of the keys contained in this map in key order.
       * <p>
       * 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.
       * <p>
       * The set supports element removal, which removes the corresponding mapping
       * from the map. It does not support the add or addAll operations.
       *
       * @return a set view of the keys contained in this map.
       */
      public Set keySet() {
          if (keySet == null) {
              keySet = new View(this, KEY, KEY);
          }
          return keySet;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Returns a set view of the values contained in this map in key order.
       * The returned object can be cast to a Set.
       * <p>
       * 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.
       * <p>
       * The set supports element removal, which removes the corresponding mapping
       * from the map. It does not support the add or addAll operations.
       *
       * @return a set view of the values contained in this map.
       */
      public Collection values() {
          if (valuesSet == null) {
              valuesSet = new View(this, KEY, VALUE);
          }
          return valuesSet;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Returns a set view of the entries contained in this map in key order.
       * For simple iteration through the map, the MapIterator is quicker.
       * <p>
       * 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.
       * <p>
       * The set supports element removal, which removes the corresponding mapping
       * from the map. It does not support the add or addAll operations.
       * The returned MapEntry objects do not support setValue.
       *
       * @return a set view of the values contained in this map.
       */
      public Set entrySet() {
          if (entrySet == null) {
              return new EntryView(this, KEY, MAPENTRY);
          }
          return entrySet;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Gets an iterator over the map entries.
       * <p>
       * For this map, this iterator is the fastest way to iterate over the entries.
       * 
       * @return an iterator
       */
      public MapIterator mapIterator() {
          if (isEmpty()) {
              return IteratorUtils.EMPTY_MAP_ITERATOR;
          }
          return new ViewMapIterator(this, KEY);
      }
  
      /**
       * Gets the inverse map for comparison.
       * 
       * @return the inverse map
       */
      public BidiMap inverseBidiMap() {
          if (inverse == null) {
              inverse = new Inverse(this);
          }
          return inverse;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Compares for equals as per the API.
       *
       * @param obj  the object to compare to
       * @param index  the KEY or VALUE int
       * @return true if equal
       */
      public boolean equals(Object obj) {
          return this.doEquals(obj, KEY);
      }
      
      /**
       * Gets the hash code value for this map as per the API.
       *
       * @return the hash code value for this map
       */
      public int hashCode() {
          return this.doHashCode(KEY);
      }
      
      /**
       * Returns a string version of this Map in standard format.
       * 
       * @return a standard format string version of the map
       */
      public String toString() {
          return this.doToString(KEY);
      }
      
      //-----------------------------------------------------------------------
      /**
       * Common get logic, used to get by key or get by value
       *
       * @param obj  the key or value that we're looking for
       * @param index  the KEY or VALUE int
       * @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 obj, final int index) {
          checkNonNullComparable(obj, index);
          Node node = lookup(obj, index);
          return ((node == null) ? null : node.getData(oppositeIndex(index)));
      }
  
      /**
       * Common put logic, differing only in the return value.
       * 
       * @param key  the key, always the main map key
       * @param value  the value, always the main map value
       * @param index  the KEY or VALUE int, for the return value only
       * @return the previously mapped value
       */
      private Object doPut(final Comparable key, final Comparable value, final int index) {
          checkKeyAndValue(key, value);
          
          // store previous and remove previous mappings
          Object prev = (index == KEY ? doGet(key, KEY) :  doGet(value, VALUE));
          doRemove((Comparable) key, KEY);
          doRemove((Comparable) value, VALUE);
          
          Node node = rootNode[KEY];
          if (node == null) {
              // map is empty
              Node root = new Node(key, value);
              rootNode[KEY] = root;
              rootNode[VALUE] = root;
              grow();
              
          } else {
              // add new mapping
              while (true) {
                  int cmp = compare(key, node.getData(KEY));
          
                  if (cmp == 0) {
                      // shouldn't happen
                      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(key, 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(key, value);
          
                          insertValue(newNode);
                          node.setRight(newNode, KEY);
                          newNode.setParent(node, KEY);
                          doRedBlackInsert(newNode, KEY);
                          grow();
          
                          break;
                      }
                  }
              }
          }
          return prev;
      }
  
      /**
       * Remove by object (remove by key or remove by value)
       *
       * @param o the key, or value, that we're looking for
       * @param index  the KEY or VALUE int
       *
       * @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;
      }
  
      /**
       * do the actual lookup of a piece of data
       *
       * @param data the key or value to be looked up
       * @param index  the KEY or VALUE int
       * @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;
      }
  
      /**
       * get the next larger node from the specified node
       *
       * @param node the node to be searched from
       * @param index  the KEY or VALUE int
       * @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 descendants 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;
      }
  
      /**
       * get the next larger node from the specified node
       *
       * @param node the node to be searched from
       * @param index  the KEY or VALUE int
       * @return the specified node
       */
      private Node nextSmaller(final Node node, final int index) {
          Node rval = null;
          if (node == null) {
              rval = null;
          } else if (node.getLeft(index) != null) {
              // everything to the node's left is smaller. The greatest of
              // the left node's descendants is the next smaller node
              rval = greatestNode(node.getLeft(index), index);
          } else {
              // traverse up our ancestry until we find an ancestor that
              // is null or one whose right 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 right
              // ... and that ancestor is the next greatest node
              Node parent = node.getParent(index);
              Node child = node;
  
              while ((parent != null) && (child == parent.getLeft(index))) {
                  child = parent;
                  parent = parent.getParent(index);
              }
              rval = parent;
          }
          return rval;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Get the opposite index of the specified index
       *
       * @param index  the KEY or VALUE int
       * @return VALUE (if KEY was specified), else KEY
       */
      private static 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;
      }
  
      /**
       * 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.
       *
       * @param node  the node from which we will start searching
       * @param index  the KEY or VALUE int
       * @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;
      }
  
      /**
       * Find the greatest node from a given node.
       *
       * @param node  the node from which we will start searching
       * @param index  the KEY or VALUE int
       * @return the greatest node, from the specified node
       */
      private static Node greatestNode(final Node node, final int index) {
          Node rval = node;
          if (rval != null) {
              while (rval.getRight(index) != null) {
                  rval = rval.getRight(index);
              }
          }
          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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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 recognizable any more
       *
       * @param insertedNode the node to be inserted
       * @param index  the KEY or VALUE int
       */
      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 recognizable 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 recognizable 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  the KEY or VALUE int
       */
      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  the KEY or VALUE int
       */
      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  the KEY or VALUE int (used to put the right word in the
       *              exception message)
       *
       * @throws NullPointerException if o is null
       * @throws 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
       *
       * @throws NullPointerException if key is null
       * @throws 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
       *
       * @throws NullPointerException if value is null
       * @throws 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
       *
       * @throws NullPointerException if key or value is null
       * @throws 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
       *
       * @throws 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;
                  }
              }
          }
      }
      
      //-----------------------------------------------------------------------
      /**
       * Compares for equals as per the API.
       *
       * @param obj  the object to compare to
       * @param index  the KEY or VALUE int
       * @return true if equal
       */
      private boolean doEquals(Object obj, final int type) {
          if (obj == this) {
              return true;
          }
          if (obj instanceof Map == false) {
              return false;
          }
          Map other = (Map) obj;
          if (other.size() != size()) {
              return false;
          }
  
          if (nodeCount > 0) {
              try {
                  for (MapIterator it = new ViewMapIterator(this, type); it.hasNext(); ) {
                      Object key = it.next();
                      Object value = it.getValue();
                      if (value.equals(other.get(key)) == false) {
                          return false;
                      }
                  }
              } catch (ClassCastException ex) {
                  return false;
              } catch (NullPointerException ex) {
                  return false;
              }
          }
          return true;
      }
  
      /**
       * Gets the hash code value for this map as per the API.
       *
       * @param index  the KEY or VALUE int
       * @return the hash code value for this map
       */
      private int doHashCode(final int type) {
          int total = 0;
          if (nodeCount > 0) {
              for (MapIterator it = new ViewMapIterator(this, type); it.hasNext(); ) {
                  Object key = it.next();
                  Object value = it.getValue();
                  total += (key.hashCode() ^ value.hashCode());
              }
          }
          return total;
      }
      
      /**
       * Gets the string form of this map as per AbstractMap.
       *
       * @param index  the KEY or VALUE int
       * @return the string form of this map
       */
      private String doToString(final int type) {
          if (nodeCount == 0) {
              return "{}";
          }
          StringBuffer buf = new StringBuffer(nodeCount * 32);
          buf.append('{');
          MapIterator it = new ViewMapIterator(this, type);
          boolean hasNext = it.hasNext();
          while (hasNext) {
              Object key = it.next();
              Object value = it.getValue();
              buf.append(key == this ? "(this Map)" : key)
                 .append('=')
                 .append(value == this ? "(this Map)" : value);
  
              hasNext = it.hasNext();
              if (hasNext) {
                  buf.append(", ");
              }
          }
  
          buf.append('}');
          return buf.toString();
      }
  
      //-----------------------------------------------------------------------
      /**
       * A view of this map.
       */
      static class View extends AbstractSet {
          
          protected final TreeBidiMap main;
          protected final int orderType;
          protected final int dataType;
  
          /**
           * Constructor.
           *
           * @param main  the main map
           * @param orderType  the KEY or VALUE int for the order
           * @param dataType  the KEY, VALUE, MAPENTRY or INVERSEMAPENTRY int
           */
          View(final TreeBidiMap main, final int orderType, final int dataType) {
              super();
              this.main = main;
              this.orderType = orderType;
              this.dataType = dataType;
          }
          
          public Iterator iterator() {
              return new ViewIterator(main, orderType, dataType);
          }
  
          public int size() {
              return main.size();
          }
  
          public boolean contains(final Object obj) {
              checkNonNullComparable(obj, dataType);
              return (main.lookup((Comparable) obj, dataType) != null);
          }
  
          public boolean remove(final Object obj) {
              return (main.doRemove((Comparable) obj, dataType) != null);
          }
  
          public void clear() {
              main.clear();
          }
      }
  
      //-----------------------------------------------------------------------
      /**
       * An iterator over the map.
       */
      static class ViewIterator implements Iterator {
  
          protected final TreeBidiMap main;
          protected final int orderType;
          protected final int dataType;
          protected Node lastReturnedNode;
          protected Node nextNode;
          protected Node previousNode;
          private int expectedModifications;
  
          /**
           * Constructor.
           *
           * @param main  the main map
           * @param orderType  the KEY or VALUE int for the order
           * @param dataType  the KEY, VALUE, MAPENTRY or INVERSEMAPENTRY int
           */
          ViewIterator(final TreeBidiMap main, final int orderType, final int dataType) {
              super();
              this.main = main;
              this.orderType = orderType;
              this.dataType = dataType;
              expectedModifications = main.modifications;
              nextNode = leastNode(main.rootNode[orderType], orderType);
              lastReturnedNode = null;
              previousNode = null;
          }
  
          public final boolean hasNext() {
              return (nextNode != null);
          }
  
          public final Object next() {
              if (nextNode == null) {
                  throw new NoSuchElementException();
              }
              if (main.modifications != expectedModifications) {
                  throw new ConcurrentModificationException();
              }
              lastReturnedNode = nextNode;
              previousNode = nextNode;
              nextNode = main.nextGreater(nextNode, orderType);
              return doGetData();
          }
  
          public boolean hasPrevious() {
              return (previousNode != null);
          }
  
          public Object previous() {
              if (previousNode == null) {
                  throw new NoSuchElementException();
              }
              if (main.modifications != expectedModifications) {
                  throw new ConcurrentModificationException();
              }
              nextNode = lastReturnedNode;
              if (nextNode == null) {
                  nextNode = main.nextGreater(previousNode, orderType);
              }
              lastReturnedNode = previousNode;
              previousNode = main.nextSmaller(previousNode, orderType);
              return doGetData();
          }
  
          protected Object doGetData() {
              switch (dataType) {
                  case KEY:
                      return lastReturnedNode.getKey();
                  case VALUE:
                      return lastReturnedNode.getValue();
                  case MAPENTRY:
                      return lastReturnedNode;
                  case INVERSEMAPENTRY:
                      return new UnmodifiableMapEntry(lastReturnedNode.getValue(), lastReturnedNode.getKey());
              }
              return null;
          }
  
          public final void remove() {
              if (lastReturnedNode == null) {
                  throw new IllegalStateException();
              }
              if (main.modifications != expectedModifications) {
                  throw new ConcurrentModificationException();
              }
              main.doRedBlackDelete(lastReturnedNode);
              expectedModifications++;
              lastReturnedNode = null;
              if (nextNode == null) {
                  previousNode = main.greatestNode(main.rootNode[orderType], orderType);
              } else {
                  previousNode = main.nextSmaller(nextNode, orderType);
              }
          }
      }
  
      //-----------------------------------------------------------------------
      /**
       * An iterator over the map.
       */
      static class ViewMapIterator extends ViewIterator implements OrderedMapIterator {
  
          private final int oppositeType;
          
          /**
           * Constructor.
           *
           * @param main  the main map
           * @param orderType  the KEY or VALUE int for the order
           */
          ViewMapIterator(final TreeBidiMap main, final int orderType) {
              super(main, orderType, orderType);
              this.oppositeType = oppositeIndex(dataType);
          }
          
          public Object getKey() {
              return lastReturnedNode.getData(dataType);
          }
  
          public Object getValue() {
              return lastReturnedNode.getData(oppositeType);
          }
  
          public Object setValue(final Object obj) {
              throw new UnsupportedOperationException();
          }
      }
  
      //-----------------------------------------------------------------------
      /**
       * A view of this map.
       */
      static class EntryView extends View {
          
          private final int oppositeType;
          
          /**
           * Constructor.
           *
           * @param main  the main map
           * @param orderType  the KEY or VALUE int for the order
           * @param dataType  the MAPENTRY or INVERSEMAPENTRY int for the returned data
           */
          EntryView(final TreeBidiMap main, final int orderType, final int dataType) {
              super(main, orderType, dataType);
              this.oppositeType = main.oppositeIndex(orderType);
          }
          
          public boolean contains(Object obj) {
              if (obj instanceof Map.Entry == false) {
                  return false;
              }
              Map.Entry entry = (Map.Entry) obj;
              Object value = entry.getValue();
              Node node = main.lookup((Comparable) entry.getKey(), orderType);
              return (node != null && node.getData(oppositeType).equals(value));
          }
  
          public boolean remove(Object obj) {
              if (obj instanceof Map.Entry == false) {
                  return false;
              }
              Map.Entry entry = (Map.Entry) obj;
              Object value = entry.getValue();
              Node node = main.lookup((Comparable) entry.getKey(), orderType);
              if (node != null && node.getData(oppositeType).equals(value)) {
                  main.doRedBlackDelete(node);
                  return true;
              }
              return false;
          }
      }
  
      //-----------------------------------------------------------------------
      /**
       * A node used to store the data.
       */
      static 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) {
              super();
              data = new Comparable[] { key, value };
              leftNode = new Node[2];
              rightNode = new Node[2];
              parentNode = new Node[2];
              blackColor = new boolean[] { true, true };
              calculatedHashCode = false;
          }
  
          /**
           * Get the specified data.
           *
           * @param index  the KEY or VALUE int
           * @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  the KEY or VALUE int
           */
          private void setLeft(final Node node, final int index) {
              leftNode[index] = node;
          }
  
          /**
           * Get the left node.
           *
           * @param index  the KEY or VALUE int
           * @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  the KEY or VALUE int
           */
          private void setRight(final Node node, final int index) {
              rightNode[index] = node;
          }
  
          /**
           * Get the right node.
           *
           * @param index  the KEY or VALUE int
           * @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  the KEY or VALUE int
           */
          private void setParent(final Node node, final int index) {
              parentNode[index] = node;
          }
  
          /**
           * Get the parent node.
           *
           * @param index  the KEY or VALUE int
           * @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  the KEY or VALUE int
           */
          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  the KEY or VALUE int
           * @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  the KEY or VALUE int
           * @return true if non-black
           */
          private boolean isRed(final int index) {
              return !blackColor[index];
          }
  
          /**
           * Make this node black.
           *
           * @param index  the KEY or VALUE int
           */
          private void setBlack(final int index) {
              blackColor[index] = true;
          }
  
          /**
           * Make this node red.
           *
           * @param index  the KEY or VALUE int
           */
          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  the KEY or VALUE int
           */
          private void copyColor(final Node node, final int index) {
              blackColor[index] = node.blackColor[index];
          }
  
          //-------------------------------------------------------------------
          /**
           * Gets the key.
           * 
           * @return the key corresponding to this entry.
           */
          public Object getKey() {
              return data[KEY];
          }
  
          /**
           * Gets the value.
           * 
           * @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
           * @throws UnsupportedOperationException always
           */
          public Object setValue(final 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(final Object obj) {
              if (obj == this) {
                  return true;
              }
              if (!(obj instanceof Map.Entry)) {
                  return false;
              }
              Map.Entry e = (Map.Entry) obj;
              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;
          }
      }
      
      //-----------------------------------------------------------------------
      /**
       * A node used to store the data.
       */
      static class Inverse implements BidiMap {
          
          private final TreeBidiMap main;
          private Set keySet;
          private Set valuesSet;
          private Set entrySet;
          
          Inverse(final TreeBidiMap main) {
              super();
              this.main = main;
          }
  
          public int size() {
              return main.size();
          }
  
          public boolean isEmpty() {
              return main.isEmpty();
          }
  
          public Object get(final Object key) {
              return main.getKey(key);
          }
  
          public Object getKey(final Object value) {
              return main.get(value);
          }
  
          public boolean containsKey(final Object key) {
              return main.containsValue(key);
          }
  
          public boolean containsValue(final Object value) {
              return main.containsKey(value);
          }
  
          public Object put(final Object key, final Object value) {
              return main.doPut((Comparable) value, (Comparable) key, VALUE);
          }
  
          public void putAll(Map map) {
              Iterator it = map.entrySet().iterator();
              while (it.hasNext()) {
                  Map.Entry entry = (Map.Entry) it.next();
                  put(entry.getKey(), entry.getValue());
              }
          }
          
          public Object remove(final Object key) {
              return main.removeValue(key);
          }
  
          public Object removeValue(final Object value) {
              return main.remove(value);
          }
  
          public void clear() {
              main.clear();
          }
  
          public Set keySet() {
              if (keySet == null) {
                  keySet = new View(main, VALUE, VALUE);
              }
              return keySet;
          }
  
          public Collection values() {
              if (valuesSet == null) {
                  valuesSet = new View(main, VALUE, KEY);
              }
              return valuesSet;
          }
  
          public Set entrySet() {
              if (entrySet == null) {
                  return new EntryView(main, VALUE, INVERSEMAPENTRY);
              }
              return entrySet;
          }
          
          public MapIterator mapIterator() {
              if (isEmpty()) {
                  return IteratorUtils.EMPTY_MAP_ITERATOR;
              }
              return new ViewMapIterator(main, VALUE);
          }
  
          public BidiMap inverseBidiMap() {
              return main;
          }
          
          public boolean equals(Object obj) {
              return main.doEquals(obj, VALUE);
          }
      
          public int hashCode() {
              return main.doHashCode(VALUE);
          }
      
          public String toString() {
              return main.doToString(VALUE);
          }
      }
  
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org