/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.util; 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; /** * Red-Black tree-based implementation of BidiMap where all objects added implement the <code>Comparable</code> interface. * <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. If the data contained in the TreeMaps is large, the cost of redundant storage becomes significant. The and implementations use this approach. * <p> * This solution keeps minimizes the data storage by holding data only once. The red-black algorithm is based on java util TreeMap, 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. * * @since Commons Collections 3.0 (previously DoubleOrderedMap v2.0) * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ * * @author Marc Johnson * @author Stephen Colebourne */ public class TreeBidiMap<K extends Comparable<K>, V extends Comparable<V>> implements Map<K, V> { private static final int KEY = 0; private static final int VALUE = 1; private static final int MAPENTRY = 2; 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 final Node[] rootNode = new Node[2]; private int nodeCount = 0; private int modifications = 0; private Set<K> keySet; private Set<V> valuesSet; private Set<Map.Entry<K, V>> entrySet; //----------------------------------------------------------------------- /** * 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< ? extends K, ? extends V> 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. * <p> * The key must implement <code>Comparable</code>. * * @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. * <p> * The value must implement <code>Comparable</code>. * * @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. * <p> * The key must implement <code>Comparable</code>. * * @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 V get(final Object key) { return (V)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> * * <p> * Both key and value must implement <code>Comparable</code>. * * @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 V put(final K key, final V value) { return (V)doPut(key, value, KEY); } /** * Puts all the mappings from the specified map into this map. * <p> * All keys and values must implement <code>Comparable</code>. * * @param map the map to copy from */ public void putAll(Map< ? extends K, ? extends V> map) { Iterator< ? extends Map.Entry< ? extends K, ? extends V>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry< ? extends K, ? extends V> entry = it.next(); put(entry.getKey(), entry.getValue()); } } /** * Removes the mapping for this key from this map if present. * <p> * The key must implement <code>Comparable</code>. * * @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 V remove(final Object key) { return (V)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. * <p> * The value must implement <code>Comparable</code>. * * @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 K getKey(final Object value) { return (K)doGet((Comparable)value, VALUE); } /** * Removes the mapping for this value from this map if present. * <p> * The value must implement <code>Comparable</code>. * * @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 K removeValue(final Object value) { return (K)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 K firstKey() { if (nodeCount == 0) { throw new NoSuchElementException("Map is empty"); } return (K)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 K lastKey() { if (nodeCount == 0) { throw new NoSuchElementException("Map is empty"); } return (K)greatestNode(rootNode[KEY], KEY).getKey(); } /** * Gets the next key after the one specified. * <p> * The key must implement <code>Comparable</code>. * * @param key the key to search for next from * @return the next key, null if no match or at end */ public K nextKey(Object key) { checkKey(key); Node node = nextGreater(lookup((Comparable)key, KEY), KEY); return (K)(node == null ? null : node.getKey()); } /** * Gets the previous key before the one specified. * <p> * The key must implement <code>Comparable</code>. * * @param key the key to search for previous from * @return the previous key, null if no match or at start */ public K previousKey(Object key) { checkKey(key); Node node = nextSmaller(lookup((Comparable)key, KEY), KEY); return (K)(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<K> 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<V> 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<Map.Entry<K, V>> entrySet() { if (entrySet == null) { return new EntryView(this, KEY, MAPENTRY); } return entrySet; } //----------------------------------------------------------------------- /** * Compares for equals as per the API. * * @param obj the object to compare to * @return true if equal */ @Override 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 */ @Override 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 */ @Override 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(key, KEY); doRemove(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 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 type 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 type 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 type 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 { /** The parent map. */ protected final TreeBidiMap main; /** Whether to return KEY or VALUE order. */ protected final int orderType; /** Whether to return KEY, VALUE, MAPENTRY or INVERSEMAPENTRY data. */ 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 int */ View(final TreeBidiMap main, final int orderType, final int dataType) { super(); this.main = main; this.orderType = orderType; this.dataType = dataType; } @Override public Iterator iterator() { return new ViewIterator(main, orderType, dataType); } @Override public int size() { return main.size(); } @Override public boolean contains(final Object obj) { checkNonNullComparable(obj, dataType); return (main.lookup((Comparable)obj, dataType) != null); } @Override public boolean remove(final Object obj) { return (main.doRemove((Comparable)obj, dataType) != null); } @Override public void clear() { main.clear(); } } //----------------------------------------------------------------------- /** * An iterator over the map. */ static class ViewIterator implements OrderedIterator { /** The parent map. */ protected final TreeBidiMap main; /** Whether to return KEY or VALUE order. */ protected final int orderType; /** Whether to return KEY, VALUE, MAPENTRY data. */ protected final int dataType; /** The last node returned by the iterator. */ protected Node lastReturnedNode; /** The next node to be returned by the iterator. */ protected Node nextNode; /** The previous node in the sequence returned by the iterator. */ protected Node previousNode; /** The modification count. */ 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 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(); } /** * Gets the data value for the lastReturnedNode field. * * @return the data value */ protected Object doGetData() { switch (dataType) { case KEY : return lastReturnedNode.getKey(); case VALUE : return lastReturnedNode.getValue(); case MAPENTRY : return lastReturnedNode; } 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 = TreeBidiMap.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() { if (lastReturnedNode == null) { throw new IllegalStateException("Iterator getKey() can only be called after next() and before remove()"); } return lastReturnedNode.getData(dataType); } public Object getValue() { if (lastReturnedNode == null) { throw new IllegalStateException("Iterator getValue() can only be called after next() and before remove()"); } 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 for the returned data */ EntryView(final TreeBidiMap main, final int orderType, final int dataType) { super(main, orderType, dataType); this.oppositeType = TreeBidiMap.oppositeIndex(orderType); } @Override 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)); } @Override 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 final Comparable[] data; private final Node[] leftNode; private final Node[] rightNode; private final Node[] parentNode; private final 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 obj the object to be compared for equality with this entry. * @return true if the specified object is equal to this entry. */ @Override 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. */ @Override public int hashCode() { if (!calculatedHashCode) { hashcodeValue = data[KEY].hashCode() ^ data[VALUE].hashCode(); calculatedHashCode = true; } return hashcodeValue; } } public interface MapIterator extends Iterator { /** * Checks to see if there are more entries still to be iterated. * * @return <code>true</code> if the iterator has more elements */ boolean hasNext(); /** * Gets the next <em>key</em> from the <code>Map</code>. * * @return the next key in the iteration * @throws java.util.NoSuchElementException if the iteration is finished */ Object next(); //----------------------------------------------------------------------- /** * Gets the current key, which is the key returned by the last call to <code>next()</code>. * * @return the current key * @throws IllegalStateException if <code>next()</code> has not yet been called */ Object getKey(); /** * Gets the current value, which is the value associated with the last key returned by <code>next()</code>. * * @return the current value * @throws IllegalStateException if <code>next()</code> has not yet been called */ Object getValue(); //----------------------------------------------------------------------- /** * Removes the last returned key from the underlying <code>Map</code> (optional operation). * <p> * This method can be called once per call to <code>next()</code>. * * @throws UnsupportedOperationException if remove is not supported by the map * @throws IllegalStateException if <code>next()</code> has not yet been called * @throws IllegalStateException if <code>remove()</code> has already been called since the last call to <code>next()</code> */ void remove(); /** * Sets the value associated with the current key (optional operation). * * @param value the new value * @return the previous value * @throws UnsupportedOperationException if setValue is not supported by the map * @throws IllegalStateException if <code>next()</code> has not yet been called * @throws IllegalStateException if <code>remove()</code> has been called since the last call to <code>next()</code> */ Object setValue(Object value); } public interface OrderedIterator extends Iterator { /** * Checks to see if there is a previous element that can be iterated to. * * @return <code>true</code> if the iterator has a previous element */ boolean hasPrevious(); /** * Gets the previous element from the collection. * * @return the previous element in the iteration * @throws java.util.NoSuchElementException if the iteration is finished */ Object previous(); } public interface OrderedMapIterator extends MapIterator, OrderedIterator { /** * Checks to see if there is a previous entry that can be iterated to. * * @return <code>true</code> if the iterator has a previous element */ boolean hasPrevious(); /** * Gets the previous <em>key</em> from the <code>Map</code>. * * @return the previous key in the iteration * @throws java.util.NoSuchElementException if the iteration is finished */ Object previous(); } }