/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 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 acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" and * "Apache POI" 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", * "Apache POI", nor may "Apache" appear in their name, 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 * . */ package org.apache.poi.util; 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 (mjohnson at apache dot org) */ public final class BinaryTree // final for performance extends AbstractMap { private Node[] _root = new Node[] { null, null }; private int _size = 0; private int _modifications = 0; private Set[] _key_set = new Set[] { null, null }; private Set[] _entry_set = new Set[] { null, null }; private Collection[] _value_collection = new Collection[] { null, null }; private static final int _KEY = 0; private static final int _VALUE = 1; private static final int _INDEX_SUM = _KEY + _VALUE; private static final int _MINIMUM_INDEX = 0; private static final int _INDEX_COUNT = 2; private static final String[] _data_name = new String[] { "key", "value" }; /** * Construct a new BinaryTree */ public BinaryTree() { } /** * Constructs a new BinaryTree 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 BinaryTree(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 (_entry_set[ _VALUE ] == null) { _entry_set[ _VALUE ] = new AbstractSet() { public Iterator iterator() { return new BinaryTreeIterator(_VALUE) { protected Object doGetNext() { return _last_returned_node; } }; } 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 BinaryTree.this.size(); } public void clear() { BinaryTree.this.clear(); } }; } return _entry_set[ _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 (_key_set[ _VALUE ] == null) { _key_set[ _VALUE ] = new AbstractSet() { public Iterator iterator() { return new BinaryTreeIterator(_VALUE) { protected Object doGetNext() { return _last_returned_node.getData(_KEY); } }; } public int size() { return BinaryTree.this.size(); } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { int old_size = _size; BinaryTree.this.remove(o); return _size != old_size; } public void clear() { BinaryTree.this.clear(); } }; } return _key_set[ _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 (_value_collection[ _VALUE ] == null) { _value_collection[ _VALUE ] = new AbstractCollection() { public Iterator iterator() { return new BinaryTreeIterator(_VALUE) { protected Object doGetNext() { return _last_returned_node.getData(_VALUE); } }; } public int size() { return BinaryTree.this.size(); } public boolean contains(Object o) { return containsValue(o); } public boolean remove(Object o) { int old_size = _size; removeValue(o); return _size != old_size; } 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() { BinaryTree.this.clear(); } }; } return _value_collection[ _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 _INDEX_SUM - 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 = _root[ 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 right_child = node.getRight(index); node.setRight(right_child.getLeft(index), index); if (right_child.getLeft(index) != null) { right_child.getLeft(index).setParent(node, index); } right_child.setParent(node.getParent(index), index); if (node.getParent(index) == null) { // node was the root ... now its right child is the root _root[ index ] = right_child; } else if (node.getParent(index).getLeft(index) == node) { node.getParent(index).setLeft(right_child, index); } else { node.getParent(index).setRight(right_child, index); } right_child.setLeft(node, index); node.setParent(right_child, 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 left_child = node.getLeft(index); node.setLeft(left_child.getRight(index), index); if (left_child.getRight(index) != null) { left_child.getRight(index).setParent(node, index); } left_child.setParent(node.getParent(index), index); if (node.getParent(index) == null) { // node was the root ... now its left child is the root _root[ index ] = left_child; } else if (node.getParent(index).getRight(index) == node) { node.getParent(index).setRight(left_child, index); } else { node.getParent(index).setLeft(left_child, index); } left_child.setRight(node, index); node.setParent(left_child, index); } /** * complicated red-black insert stuff. Based on Sun's TreeMap * implementation, though it's barely recognizeable any more * * @param inserted_node the node to be inserted * @param index _KEY or _VALUE */ private void doRedBlackInsert(final Node inserted_node, final int index) { Node current_node = inserted_node; makeRed(current_node, index); while ((current_node != null) && (current_node != _root[ index ]) && (isRed(current_node.getParent(index), index))) { if (isLeftChild(getParent(current_node, index), index)) { Node y = getRightChild(getGrandParent(current_node, index), index); if (isRed(y, index)) { makeBlack(getParent(current_node, index), index); makeBlack(y, index); makeRed(getGrandParent(current_node, index), index); current_node = getGrandParent(current_node, index); } else { if (isRightChild(current_node, index)) { current_node = getParent(current_node, index); rotateLeft(current_node, index); } makeBlack(getParent(current_node, index), index); makeRed(getGrandParent(current_node, index), index); if (getGrandParent(current_node, index) != null) { rotateRight(getGrandParent(current_node, index), index); } } } else { // just like clause above, except swap left for right Node y = getLeftChild(getGrandParent(current_node, index), index); if (isRed(y, index)) { makeBlack(getParent(current_node, index), index); makeBlack(y, index); makeRed(getGrandParent(current_node, index), index); current_node = getGrandParent(current_node, index); } else { if (isLeftChild(current_node, index)) { current_node = getParent(current_node, index); rotateRight(current_node, index); } makeBlack(getParent(current_node, index), index); makeRed(getGrandParent(current_node, index), index); if (getGrandParent(current_node, index) != null) { rotateLeft(getGrandParent(current_node, index), index); } } } } makeBlack(_root[ index ], index); } /** * complicated red-black delete stuff. Based on Sun's TreeMap * implementation, though it's barely recognizeable any more * * @param deleted_node the node to be deleted */ private void doRedBlackDelete(final Node deleted_node) { for (int index = _MINIMUM_INDEX; index < _INDEX_COUNT; index++) { // if deleted node has both left and children, swap with // the next greater node if ((deleted_node.getLeft(index) != null) && (deleted_node.getRight(index) != null)) { swapPosition(nextGreater(deleted_node, index), deleted_node, index); } Node replacement = ((deleted_node.getLeft(index) != null) ? deleted_node.getLeft(index) : deleted_node.getRight(index)); if (replacement != null) { replacement.setParent(deleted_node.getParent(index), index); if (deleted_node.getParent(index) == null) { _root[ index ] = replacement; } else if (deleted_node == deleted_node.getParent(index).getLeft(index)) { deleted_node.getParent(index).setLeft(replacement, index); } else { deleted_node.getParent(index).setRight(replacement, index); } deleted_node.setLeft(null, index); deleted_node.setRight(null, index); deleted_node.setParent(null, index); if (isBlack(deleted_node, index)) { doRedBlackDeleteFixup(replacement, index); } } else { // replacement is null if (deleted_node.getParent(index) == null) { // empty tree _root[ index ] = null; } else { // deleted node had no children if (isBlack(deleted_node, index)) { doRedBlackDeleteFixup(deleted_node, index); } if (deleted_node.getParent(index) != null) { if (deleted_node == deleted_node.getParent(index) .getLeft(index)) { deleted_node.getParent(index).setLeft(null, index); } else { deleted_node.getParent(index).setRight(null, index); } deleted_node.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 replacement_node the node being replaced * @param index _KEY or _VALUE */ private void doRedBlackDeleteFixup(final Node replacement_node, final int index) { Node current_node = replacement_node; while ((current_node != _root[ index ]) && (isBlack(current_node, index))) { if (isLeftChild(current_node, index)) { Node sibling_node = getRightChild(getParent(current_node, index), index); if (isRed(sibling_node, index)) { makeBlack(sibling_node, index); makeRed(getParent(current_node, index), index); rotateLeft(getParent(current_node, index), index); sibling_node = getRightChild(getParent(current_node, index), index); } if (isBlack(getLeftChild(sibling_node, index), index) && isBlack(getRightChild(sibling_node, index), index)) { makeRed(sibling_node, index); current_node = getParent(current_node, index); } else { if (isBlack(getRightChild(sibling_node, index), index)) { makeBlack(getLeftChild(sibling_node, index), index); makeRed(sibling_node, index); rotateRight(sibling_node, index); sibling_node = getRightChild(getParent(current_node, index), index); } copyColor(getParent(current_node, index), sibling_node, index); makeBlack(getParent(current_node, index), index); makeBlack(getRightChild(sibling_node, index), index); rotateLeft(getParent(current_node, index), index); current_node = _root[ index ]; } } else { Node sibling_node = getLeftChild(getParent(current_node, index), index); if (isRed(sibling_node, index)) { makeBlack(sibling_node, index); makeRed(getParent(current_node, index), index); rotateRight(getParent(current_node, index), index); sibling_node = getLeftChild(getParent(current_node, index), index); } if (isBlack(getRightChild(sibling_node, index), index) && isBlack(getLeftChild(sibling_node, index), index)) { makeRed(sibling_node, index); current_node = getParent(current_node, index); } else { if (isBlack(getLeftChild(sibling_node, index), index)) { makeBlack(getRightChild(sibling_node, index), index); makeRed(sibling_node, index); rotateLeft(sibling_node, index); sibling_node = getLeftChild(getParent(current_node, index), index); } copyColor(getParent(current_node, index), sibling_node, index); makeBlack(getParent(current_node, index), index); makeBlack(getLeftChild(sibling_node, index), index); rotateRight(getParent(current_node, index), index); current_node = _root[ index ]; } } } makeBlack(current_node, 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 x_old_parent = x.getParent(index); Node x_old_left_child = x.getLeft(index); Node x_old_right_child = x.getRight(index); Node y_old_parent = y.getParent(index); Node y_old_left_child = y.getLeft(index); Node y_old_right_child = y.getRight(index); boolean x_was_left_child = (x.getParent(index) != null) && (x == x.getParent(index).getLeft(index)); boolean y_was_left_child = (y.getParent(index) != null) && (y == y.getParent(index).getLeft(index)); // Swap, handling special cases of one being the other's parent. if (x == y_old_parent) { // x was y's parent x.setParent(y, index); if (y_was_left_child) { y.setLeft(x, index); y.setRight(x_old_right_child, index); } else { y.setRight(x, index); y.setLeft(x_old_left_child, index); } } else { x.setParent(y_old_parent, index); if (y_old_parent != null) { if (y_was_left_child) { y_old_parent.setLeft(x, index); } else { y_old_parent.setRight(x, index); } } y.setLeft(x_old_left_child, index); y.setRight(x_old_right_child, index); } if (y == x_old_parent) { // y was x's parent y.setParent(x, index); if (x_was_left_child) { x.setLeft(y, index); x.setRight(y_old_right_child, index); } else { x.setRight(y, index); x.setLeft(y_old_left_child, index); } } else { y.setParent(x_old_parent, index); if (x_old_parent != null) { if (x_was_left_child) { x_old_parent.setLeft(y, index); } else { x_old_parent.setRight(y, index); } } x.setLeft(y_old_left_child, index); x.setRight(y_old_right_child, 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 (_root[ index ] == x) { _root[ index ] = y; } else if (_root[ index ] == y) { _root[ 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(_data_name[ index ] + " cannot be null"); } if (!(o instanceof Comparable)) { throw new ClassCastException(_data_name[ 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(); _size++; } /** * decrement the size and note that the map has changed */ private void shrink() { modify(); _size--; } /** * 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 = _root[ _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.MAX_VALUE elements, returns * Integer.MAX_VALUE. * * @return the number of key-value mappings in this map. */ public int size() { return _size; } /** * 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 = _root[ _KEY ]; if (node == null) { Node root = new Node(( Comparable ) key, ( Comparable ) value); _root[ _KEY ] = root; _root[ _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(); _size = 0; _root[ _KEY ] = null; _root[ _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 (_key_set[ _KEY ] == null) { _key_set[ _KEY ] = new AbstractSet() { public Iterator iterator() { return new BinaryTreeIterator(_KEY) { protected Object doGetNext() { return _last_returned_node.getData(_KEY); } }; } public int size() { return BinaryTree.this.size(); } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { int old_size = _size; BinaryTree.this.remove(o); return _size != old_size; } public void clear() { BinaryTree.this.clear(); } }; } return _key_set[ _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 (_value_collection[ _KEY ] == null) { _value_collection[ _KEY ] = new AbstractCollection() { public Iterator iterator() { return new BinaryTreeIterator(_KEY) { protected Object doGetNext() { return _last_returned_node.getData(_VALUE); } }; } public int size() { return BinaryTree.this.size(); } public boolean contains(Object o) { return containsValue(o); } public boolean remove(Object o) { int old_size = _size; removeValue(o); return _size != old_size; } 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() { BinaryTree.this.clear(); } }; } return _value_collection[ _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 (_entry_set[ _KEY ] == null) { _entry_set[ _KEY ] = new AbstractSet() { public Iterator iterator() { return new BinaryTreeIterator(_KEY) { protected Object doGetNext() { return _last_returned_node; } }; } 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 BinaryTree.this.size(); } public void clear() { BinaryTree.this.clear(); } }; } return _entry_set[ _KEY ]; } /* ********** END implementation of Map ********** */ private abstract class BinaryTreeIterator implements Iterator { private int _expected_modifications; protected Node _last_returned_node; private Node _next_node; private int _type; /** * Constructor * * @param type */ BinaryTreeIterator(final int type) { _type = type; _expected_modifications = BinaryTree.this._modifications; _last_returned_node = null; _next_node = leastNode(_root[ _type ], _type); } /** * @return 'next', whatever that means for a given kind of * BinaryTreeIterator */ protected abstract Object doGetNext(); /* ********** START implementation of Iterator ********** */ /** * @return true if the iterator has more elements. */ public final boolean hasNext() { return _next_node != null; } /** * @return the next element in the iteration. * * @exception NoSuchElementException if iteration has no more * elements. * @exception ConcurrentModificationException if the * BinaryTree is * modified behind * the iterator's * back */ public final Object next() throws NoSuchElementException, ConcurrentModificationException { if (_next_node == null) { throw new NoSuchElementException(); } if (_modifications != _expected_modifications) { throw new ConcurrentModificationException(); } _last_returned_node = _next_node; _next_node = nextGreater(_next_node, _type); 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 * BinaryTree is * modified behind * the iterator's * back */ public final void remove() throws IllegalStateException, ConcurrentModificationException { if (_last_returned_node == null) { throw new IllegalStateException(); } if (_modifications != _expected_modifications) { throw new ConcurrentModificationException(); } doRedBlackDelete(_last_returned_node); _expected_modifications++; _last_returned_node = null; } /* ********** END implementation of Iterator ********** */ } // end private abstract class BinaryTreeIterator // final for performance private static final class Node implements Map.Entry { private Comparable[] _data; private Node[] _left; private Node[] _right; private Node[] _parent; private boolean[] _black; private int _hashcode; private boolean _calculated_hashcode; /** * 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 }; _left = new Node[] { null, null }; _right = new Node[] { null, null }; _parent = new Node[] { null, null }; _black = new boolean[] { true, true }; _calculated_hashcode = 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) { _left[ 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 _left[ 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) { _right[ 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 _right[ 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) { _parent[ 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 _parent[ 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 _black[ index ] ^= node._black[ index ]; node._black[ index ] ^= _black[ index ]; _black[ index ] ^= node._black[ 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 _black[ index ]; } /** * is this node red? * * @param index _KEY or _VALUE * * @return true if non-black */ private boolean isRed(final int index) { return !_black[ index ]; } /** * make this node black * * @param index _KEY or _VALUE */ private void setBlack(final int index) { _black[ index ] = true; } /** * make this node red * * @param index _KEY or _VALUE */ private void setRed(final int index) { _black[ 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) { _black[ index ] = node._black[ 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 (!_calculated_hashcode) { _hashcode = _data[ _KEY ].hashCode() ^ _data[ _VALUE ].hashCode(); _calculated_hashcode = true; } return _hashcode; } /* ********** END implementation of Map.Entry ********** */ } } // end public class BinaryTree