org.apache.commons.collections.DoubleOrderedMap Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections;
import java.util.AbstractCollection;
import java.util.AbstractMap;
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 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. (See also the new
* {@link org.apache.commons.collections.bidimap.DualTreeBidiMap DualTreeBidiMap} and
* {@link org.apache.commons.collections.bidimap.DualHashBidiMap DualHashBidiMap}
* implementations.)
*
* 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.
*
* @deprecated Replaced by TreeBidiMap in bidimap subpackage. Due to be removed in v4.0.
* @see BidiMap
* @see org.apache.commons.collections.bidimap.DualTreeBidiMap
* @see org.apache.commons.collections.bidimap.DualHashBidiMap
* @since Commons Collections 2.0
* @version $Revision: 646777 $ $Date: 2008-04-10 14:33:15 +0200 (Thu, 10 Apr 2008) $
*
* @author Marc Johnson
*/
public final class DoubleOrderedMap extends AbstractMap {
// final for performance
private static final int KEY = 0;
private static final int VALUE = 1;
private static final int SUM_OF_INDICES = KEY + VALUE;
private static final int FIRST_INDEX = 0;
private static final int NUMBER_OF_INDICES = 2;
private static final String[] dataName = new String[] { "key", "value" };
private Node[] rootNode = new Node[] { null, null };
private int nodeCount = 0;
private int modifications = 0;
private Set[] setOfKeys = new Set[] { null, null };
private Set[] setOfEntries = new Set[] { null, null };
private Collection[] collectionOfValues = new Collection[] { null, null };
/**
* Construct a new DoubleOrderedMap
*/
public DoubleOrderedMap() {
}
/**
* Constructs a new DoubleOrderedMap from an existing Map, with keys and
* values sorted
*
* @param map the map whose mappings are to be placed in this map.
*
* @throws 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
* @throws NullPointerException if any key or value in the map
* is null
* @throws IllegalArgumentException if there are duplicate keys
* or duplicate values in the
* map
*/
public DoubleOrderedMap(final Map map)
throws ClassCastException, NullPointerException,
IllegalArgumentException {
putAll(map);
}
/**
* Returns the key to which this map maps the specified value.
* Returns null if the map contains no mapping for this value.
*
* @param value value whose associated key is to be returned.
*
* @return the key to which this map maps the specified value, or
* null if the map contains no mapping for this value.
*
* @throws ClassCastException if the value is of an
* inappropriate type for this map.
* @throws NullPointerException if the value is null
*/
public Object getKeyForValue(final Object value)
throws ClassCastException, NullPointerException {
return doGet((Comparable) value, VALUE);
}
/**
* Removes the mapping for this value from this map if present
*
* @param value value whose mapping is to be removed from the map.
*
* @return previous key associated with specified value, or null
* if there was no mapping for value.
*/
public Object removeValue(final Object value) {
return doRemove((Comparable) value, VALUE);
}
/**
* Returns a set view of the mappings contained in this map. Each
* element in the returned set is a Map.Entry. The set is backed
* by the map, so changes to the map are reflected in the set, and
* vice-versa. If the map is modified while an iteration over the
* set is in progress, the results of the iteration are
* undefined. The set supports element removal, which removes the
* corresponding mapping from the map, via the Iterator.remove,
* Set.remove, removeAll, retainAll and clear operations. It does
* not support the add or addAll operations.
*
* The difference between this method and entrySet is that
* entrySet's iterator() method returns an iterator that iterates
* over the mappings in ascending order by key. This method's
* iterator method iterates over the mappings in ascending order
* by value.
*
* @return a set view of the mappings contained in this map.
*/
public Set entrySetByValue() {
if (setOfEntries[VALUE] == null) {
setOfEntries[VALUE] = new AbstractSet() {
public Iterator iterator() {
return new DoubleOrderedMapIterator(VALUE) {
protected Object doGetNext() {
return lastReturnedNode;
}
};
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
Node node = lookup((Comparable) entry.getValue(),
VALUE);
return (node != null) && node.getData(KEY).equals(key);
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
Node node = lookup((Comparable) entry.getValue(),
VALUE);
if ((node != null) && node.getData(KEY).equals(key)) {
doRedBlackDelete(node);
return true;
}
return false;
}
public int size() {
return DoubleOrderedMap.this.size();
}
public void clear() {
DoubleOrderedMap.this.clear();
}
};
}
return setOfEntries[VALUE];
}
/**
* Returns a set view of the keys contained in this map. The set
* is backed by the map, so changes to the map are reflected in
* the set, and vice-versa. If the map is modified while an
* iteration over the set is in progress, the results of the
* iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* Iterator.remove, Set.remove, removeAll, retainAll, and clear
* operations. It does not support the add or addAll
* operations.
*
* The difference between this method and keySet is that keySet's
* iterator() method returns an iterator that iterates over the
* keys in ascending order by key. This method's iterator method
* iterates over the keys in ascending order by value.
*
* @return a set view of the keys contained in this map.
*/
public Set keySetByValue() {
if (setOfKeys[VALUE] == null) {
setOfKeys[VALUE] = new AbstractSet() {
public Iterator iterator() {
return new DoubleOrderedMapIterator(VALUE) {
protected Object doGetNext() {
return lastReturnedNode.getData(KEY);
}
};
}
public int size() {
return DoubleOrderedMap.this.size();
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
int oldnodeCount = nodeCount;
DoubleOrderedMap.this.remove(o);
return nodeCount != oldnodeCount;
}
public void clear() {
DoubleOrderedMap.this.clear();
}
};
}
return setOfKeys[VALUE];
}
/**
* Returns a collection view of the values contained in this
* map. The collection is backed by the map, so changes to the map
* are reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress,
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the Iterator.remove,
* Collection.remove, removeAll, retainAll and clear operations.
* It does not support the add or addAll operations.
*
* The difference between this method and values is that values's
* iterator() method returns an iterator that iterates over the
* values in ascending order by key. This method's iterator method
* iterates over the values in ascending order by key.
*
* @return a collection view of the values contained in this map.
*/
public Collection valuesByValue() {
if (collectionOfValues[VALUE] == null) {
collectionOfValues[VALUE] = new AbstractCollection() {
public Iterator iterator() {
return new DoubleOrderedMapIterator(VALUE) {
protected Object doGetNext() {
return lastReturnedNode.getData(VALUE);
}
};
}
public int size() {
return DoubleOrderedMap.this.size();
}
public boolean contains(Object o) {
return containsValue(o);
}
public boolean remove(Object o) {
int oldnodeCount = nodeCount;
removeValue(o);
return nodeCount != oldnodeCount;
}
public boolean removeAll(Collection c) {
boolean modified = false;
Iterator iter = c.iterator();
while (iter.hasNext()) {
if (removeValue(iter.next()) != null) {
modified = true;
}
}
return modified;
}
public void clear() {
DoubleOrderedMap.this.clear();
}
};
}
return collectionOfValues[VALUE];
}
/**
* common remove logic (remove by key or remove by value)
*
* @param o the key, or value, that we're looking for
* @param index KEY or VALUE
*
* @return the key, if remove by value, or the value, if remove by
* key. null if the specified key or value could not be
* found
*/
private Object doRemove(final Comparable o, final int index) {
Node node = lookup(o, index);
Object rval = null;
if (node != null) {
rval = node.getData(oppositeIndex(index));
doRedBlackDelete(node);
}
return rval;
}
/**
* common get logic, used to get by key or get by value
*
* @param o the key or value that we're looking for
* @param index KEY or VALUE
*
* @return the key (if the value was mapped) or the value (if the
* key was mapped); null if we couldn't find the specified
* object
*/
private Object doGet(final Comparable o, final int index) {
checkNonNullComparable(o, index);
Node node = lookup(o, index);
return ((node == null)
? null
: node.getData(oppositeIndex(index)));
}
/**
* Get the opposite index of the specified index
*
* @param index KEY or VALUE
*
* @return VALUE (if KEY was specified), else KEY
*/
private int oppositeIndex(final int index) {
// old trick ... to find the opposite of a value, m or n,
// subtract the value from the sum of the two possible
// values. (m + n) - m = n; (m + n) - n = m
return SUM_OF_INDICES - index;
}
/**
* do the actual lookup of a piece of data
*
* @param data the key or value to be looked up
* @param index KEY or VALUE
*
* @return the desired Node, or null if there is no mapping of the
* specified data
*/
private Node lookup(final Comparable data, final int index) {
Node rval = null;
Node node = rootNode[index];
while (node != null) {
int cmp = compare(data, node.getData(index));
if (cmp == 0) {
rval = node;
break;
} else {
node = (cmp < 0)
? node.getLeft(index)
: node.getRight(index);
}
}
return rval;
}
/**
* Compare two objects
*
* @param o1 the first object
* @param o2 the second object
*
* @return negative value if o1 < o2; 0 if o1 == o2; positive
* value if o1 > o2
*/
private static int compare(final Comparable o1, final Comparable o2) {
return 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 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;
}
/**
* copy the color from one node to another, dealing with the fact
* that one or both nodes may, in fact, be null
*
* @param from the node whose color we're copying; may be null
* @param to the node whose color we're changing; may be null
* @param index KEY or VALUE
*/
private static void copyColor(final Node from, final Node to,
final int index) {
if (to != null) {
if (from == null) {
// by default, make it black
to.setBlack(index);
} else {
to.copyColor(from, index);
}
}
}
/**
* is the specified node red? if the node does not exist, no, it's
* black, thank you
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static boolean isRed(final Node node, final int index) {
return ((node == null)
? false
: node.isRed(index));
}
/**
* is the specified black red? if the node does not exist, sure,
* it's black, thank you
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static boolean isBlack(final Node node, final int index) {
return ((node == null)
? true
: node.isBlack(index));
}
/**
* force a node (if it exists) red
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static void makeRed(final Node node, final int index) {
if (node != null) {
node.setRed(index);
}
}
/**
* force a node (if it exists) black
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static void makeBlack(final Node node, final int index) {
if (node != null) {
node.setBlack(index);
}
}
/**
* get a node's grandparent. mind you, the node, its parent, or
* its grandparent may not exist. no problem
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static Node getGrandParent(final Node node, final int index) {
return getParent(getParent(node, index), index);
}
/**
* get a node's parent. mind you, the node, or its parent, may not
* exist. no problem
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static Node getParent(final Node node, final int index) {
return ((node == null)
? null
: node.getParent(index));
}
/**
* get a node's right child. mind you, the node may not exist. no
* problem
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static Node getRightChild(final Node node, final int index) {
return (node == null)
? null
: node.getRight(index);
}
/**
* get a node's left child. mind you, the node may not exist. no
* problem
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static Node getLeftChild(final Node node, final int index) {
return (node == null)
? null
: node.getLeft(index);
}
/**
* is this node its parent's left child? mind you, the node, or
* its parent, may not exist. no problem. if the node doesn't
* exist ... it's its non-existent parent's left child. If the
* node does exist but has no parent ... no, we're not the
* non-existent parent's left child. Otherwise (both the specified
* node AND its parent exist), check.
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static boolean isLeftChild(final Node node, final int index) {
return (node == null)
? true
: ((node.getParent(index) == null)
? false
: (node == node.getParent(index).getLeft(index)));
}
/**
* is this node its parent's right child? mind you, the node, or
* its parent, may not exist. no problem. if the node doesn't
* exist ... it's its non-existent parent's right child. If the
* node does exist but has no parent ... no, we're not the
* non-existent parent's right child. Otherwise (both the
* specified node AND its parent exist), check.
*
* @param node the node (may be null) in question
* @param index KEY or VALUE
*/
private static boolean isRightChild(final Node node, final int index) {
return (node == null)
? true
: ((node.getParent(index) == null)
? false
: (node == node.getParent(index).getRight(index)));
}
/**
* do a rotate left. standard fare in the world of balanced trees
*
* @param node the node to be rotated
* @param index KEY or VALUE
*/
private void rotateLeft(final Node node, final int index) {
Node rightChild = node.getRight(index);
node.setRight(rightChild.getLeft(index), index);
if (rightChild.getLeft(index) != null) {
rightChild.getLeft(index).setParent(node, index);
}
rightChild.setParent(node.getParent(index), index);
if (node.getParent(index) == null) {
// node was the root ... now its right child is the root
rootNode[index] = rightChild;
} else if (node.getParent(index).getLeft(index) == node) {
node.getParent(index).setLeft(rightChild, index);
} else {
node.getParent(index).setRight(rightChild, index);
}
rightChild.setLeft(node, index);
node.setParent(rightChild, index);
}
/**
* do a rotate right. standard fare in the world of balanced trees
*
* @param node the node to be rotated
* @param index KEY or VALUE
*/
private void rotateRight(final Node node, final int index) {
Node leftChild = node.getLeft(index);
node.setLeft(leftChild.getRight(index), index);
if (leftChild.getRight(index) != null) {
leftChild.getRight(index).setParent(node, index);
}
leftChild.setParent(node.getParent(index), index);
if (node.getParent(index) == null) {
// node was the root ... now its left child is the root
rootNode[index] = leftChild;
} else if (node.getParent(index).getRight(index) == node) {
node.getParent(index).setRight(leftChild, index);
} else {
node.getParent(index).setLeft(leftChild, index);
}
leftChild.setRight(node, index);
node.setParent(leftChild, index);
}
/**
* complicated red-black insert stuff. Based on Sun's TreeMap
* implementation, though it's barely recognizable any more
*
* @param insertedNode the node to be inserted
* @param index KEY or VALUE
*/
private void doRedBlackInsert(final Node insertedNode, final int index) {
Node currentNode = insertedNode;
makeRed(currentNode, index);
while ((currentNode != null) && (currentNode != rootNode[index])
&& (isRed(currentNode.getParent(index), index))) {
if (isLeftChild(getParent(currentNode, index), index)) {
Node y = getRightChild(getGrandParent(currentNode, index),
index);
if (isRed(y, index)) {
makeBlack(getParent(currentNode, index), index);
makeBlack(y, index);
makeRed(getGrandParent(currentNode, index), index);
currentNode = getGrandParent(currentNode, index);
} else {
if (isRightChild(currentNode, index)) {
currentNode = getParent(currentNode, index);
rotateLeft(currentNode, index);
}
makeBlack(getParent(currentNode, index), index);
makeRed(getGrandParent(currentNode, index), index);
if (getGrandParent(currentNode, index) != null) {
rotateRight(getGrandParent(currentNode, index),
index);
}
}
} else {
// just like clause above, except swap left for right
Node y = getLeftChild(getGrandParent(currentNode, index),
index);
if (isRed(y, index)) {
makeBlack(getParent(currentNode, index), index);
makeBlack(y, index);
makeRed(getGrandParent(currentNode, index), index);
currentNode = getGrandParent(currentNode, index);
} else {
if (isLeftChild(currentNode, index)) {
currentNode = getParent(currentNode, index);
rotateRight(currentNode, index);
}
makeBlack(getParent(currentNode, index), index);
makeRed(getGrandParent(currentNode, index), index);
if (getGrandParent(currentNode, index) != null) {
rotateLeft(getGrandParent(currentNode, index), index);
}
}
}
}
makeBlack(rootNode[index], index);
}
/**
* complicated red-black delete stuff. Based on Sun's TreeMap
* implementation, though it's barely 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 KEY or VALUE
*/
private void doRedBlackDeleteFixup(final Node replacementNode,
final int index) {
Node currentNode = replacementNode;
while ((currentNode != rootNode[index])
&& (isBlack(currentNode, index))) {
if (isLeftChild(currentNode, index)) {
Node siblingNode =
getRightChild(getParent(currentNode, index), index);
if (isRed(siblingNode, index)) {
makeBlack(siblingNode, index);
makeRed(getParent(currentNode, index), index);
rotateLeft(getParent(currentNode, index), index);
siblingNode = getRightChild(getParent(currentNode, index), index);
}
if (isBlack(getLeftChild(siblingNode, index), index)
&& isBlack(getRightChild(siblingNode, index),
index)) {
makeRed(siblingNode, index);
currentNode = getParent(currentNode, index);
} else {
if (isBlack(getRightChild(siblingNode, index), index)) {
makeBlack(getLeftChild(siblingNode, index), index);
makeRed(siblingNode, index);
rotateRight(siblingNode, index);
siblingNode =
getRightChild(getParent(currentNode, index), index);
}
copyColor(getParent(currentNode, index), siblingNode,
index);
makeBlack(getParent(currentNode, index), index);
makeBlack(getRightChild(siblingNode, index), index);
rotateLeft(getParent(currentNode, index), index);
currentNode = rootNode[index];
}
} else {
Node siblingNode = getLeftChild(getParent(currentNode, index), index);
if (isRed(siblingNode, index)) {
makeBlack(siblingNode, index);
makeRed(getParent(currentNode, index), index);
rotateRight(getParent(currentNode, index), index);
siblingNode = getLeftChild(getParent(currentNode, index), index);
}
if (isBlack(getRightChild(siblingNode, index), index)
&& isBlack(getLeftChild(siblingNode, index), index)) {
makeRed(siblingNode, index);
currentNode = getParent(currentNode, index);
} else {
if (isBlack(getLeftChild(siblingNode, index), index)) {
makeBlack(getRightChild(siblingNode, index), index);
makeRed(siblingNode, index);
rotateLeft(siblingNode, index);
siblingNode =
getLeftChild(getParent(currentNode, index), index);
}
copyColor(getParent(currentNode, index), siblingNode,
index);
makeBlack(getParent(currentNode, index), index);
makeBlack(getLeftChild(siblingNode, index), index);
rotateRight(getParent(currentNode, index), index);
currentNode = rootNode[index];
}
}
}
makeBlack(currentNode, index);
}
/**
* swap two nodes (except for their content), taking care of
* special cases where one is the other's parent ... hey, it
* happens.
*
* @param x one node
* @param y another node
* @param index KEY or VALUE
*/
private void swapPosition(final Node x, final Node y, final int index) {
// Save initial values.
Node xFormerParent = x.getParent(index);
Node xFormerLeftChild = x.getLeft(index);
Node xFormerRightChild = x.getRight(index);
Node yFormerParent = y.getParent(index);
Node yFormerLeftChild = y.getLeft(index);
Node yFormerRightChild = y.getRight(index);
boolean xWasLeftChild =
(x.getParent(index) != null)
&& (x == x.getParent(index).getLeft(index));
boolean yWasLeftChild =
(y.getParent(index) != null)
&& (y == y.getParent(index).getLeft(index));
// Swap, handling special cases of one being the other's parent.
if (x == yFormerParent) { // x was y's parent
x.setParent(y, index);
if (yWasLeftChild) {
y.setLeft(x, index);
y.setRight(xFormerRightChild, index);
} else {
y.setRight(x, index);
y.setLeft(xFormerLeftChild, index);
}
} else {
x.setParent(yFormerParent, index);
if (yFormerParent != null) {
if (yWasLeftChild) {
yFormerParent.setLeft(x, index);
} else {
yFormerParent.setRight(x, index);
}
}
y.setLeft(xFormerLeftChild, index);
y.setRight(xFormerRightChild, index);
}
if (y == xFormerParent) { // y was x's parent
y.setParent(x, index);
if (xWasLeftChild) {
x.setLeft(y, index);
x.setRight(yFormerRightChild, index);
} else {
x.setRight(y, index);
x.setLeft(yFormerLeftChild, index);
}
} else {
y.setParent(xFormerParent, index);
if (xFormerParent != null) {
if (xWasLeftChild) {
xFormerParent.setLeft(y, index);
} else {
xFormerParent.setRight(y, index);
}
}
x.setLeft(yFormerLeftChild, index);
x.setRight(yFormerRightChild, index);
}
// Fix children's parent pointers
if (x.getLeft(index) != null) {
x.getLeft(index).setParent(x, index);
}
if (x.getRight(index) != null) {
x.getRight(index).setParent(x, index);
}
if (y.getLeft(index) != null) {
y.getLeft(index).setParent(y, index);
}
if (y.getRight(index) != null) {
y.getRight(index).setParent(y, index);
}
x.swapColors(y, index);
// Check if root changed
if (rootNode[index] == x) {
rootNode[index] = y;
} else if (rootNode[index] == y) {
rootNode[index] = x;
}
}
/**
* check if an object is fit to be proper input ... has to be
* Comparable and non-null
*
* @param o the object being checked
* @param index KEY or VALUE (used to put the right word in the
* exception message)
*
* @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;
}
}
}
}
/* ********** START implementation of Map ********** */
/**
* Returns the number of key-value mappings in this map. If the
* map contains more than Integer.MAXVALUE elements, returns
* Integer.MAXVALUE.
*
* @return the number of key-value mappings in this map.
*/
public int size() {
return nodeCount;
}
/**
* Returns true if this map contains a mapping for the specified
* key.
*
* @param key key whose presence in this map is to be tested.
*
* @return true if this map contains a mapping for the specified
* key.
*
* @throws ClassCastException if the key is of an inappropriate
* type for this map.
* @throws 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.
*
* @throws ClassCastException if the key is of an inappropriate
* type for this map.
* @throws 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
*
* @throws ClassCastException if the class of the specified key
* or value prevents it from being
* stored in this map.
* @throws NullPointerException if the specified key or value
* is null
* @throws IllegalArgumentException if the key duplicates an
* existing key, or if the
* value duplicates an
* existing value
*/
public Object put(final Object key, final Object value)
throws ClassCastException, NullPointerException,
IllegalArgumentException {
checkKeyAndValue(key, value);
Node node = rootNode[KEY];
if (node == null) {
Node root = new Node((Comparable) key, (Comparable) value);
rootNode[KEY] = root;
rootNode[VALUE] = root;
grow();
} else {
while (true) {
int cmp = compare((Comparable) key, node.getData(KEY));
if (cmp == 0) {
throw new IllegalArgumentException(
"Cannot store a duplicate key (\"" + key
+ "\") in this Map");
} else if (cmp < 0) {
if (node.getLeft(KEY) != null) {
node = node.getLeft(KEY);
} else {
Node newNode = new Node((Comparable) key,
(Comparable) value);
insertValue(newNode);
node.setLeft(newNode, KEY);
newNode.setParent(node, KEY);
doRedBlackInsert(newNode, KEY);
grow();
break;
}
} else { // cmp > 0
if (node.getRight(KEY) != null) {
node = node.getRight(KEY);
} else {
Node newNode = new Node((Comparable) key,
(Comparable) value);
insertValue(newNode);
node.setRight(newNode, KEY);
newNode.setParent(node, KEY);
doRedBlackInsert(newNode, KEY);
grow();
break;
}
}
}
}
return null;
}
/**
* Removes the mapping for this key from this map if present
*
* @param key key whose mapping is to be removed from the map.
*
* @return previous value associated with specified key, or null
* if there was no mapping for key.
*/
public Object remove(final Object key) {
return doRemove((Comparable) key, KEY);
}
/**
* Removes all mappings from this map
*/
public void clear() {
modify();
nodeCount = 0;
rootNode[KEY] = null;
rootNode[VALUE] = null;
}
/**
* Returns a set view of the keys contained in this map. The set
* is backed by the map, so changes to the map are reflected in
* the set, and vice-versa. If the map is modified while an
* iteration over the set is in progress, the results of the
* iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* Iterator.remove, Set.remove, removeAll, retainAll, and clear
* operations. It does not support the add or addAll operations.
*
* @return a set view of the keys contained in this map.
*/
public Set keySet() {
if (setOfKeys[KEY] == null) {
setOfKeys[KEY] = new AbstractSet() {
public Iterator iterator() {
return new DoubleOrderedMapIterator(KEY) {
protected Object doGetNext() {
return lastReturnedNode.getData(KEY);
}
};
}
public int size() {
return DoubleOrderedMap.this.size();
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
int oldNodeCount = nodeCount;
DoubleOrderedMap.this.remove(o);
return nodeCount != oldNodeCount;
}
public void clear() {
DoubleOrderedMap.this.clear();
}
};
}
return setOfKeys[KEY];
}
/**
* Returns a collection view of the values contained in this
* map. The collection is backed by the map, so changes to the map
* are reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress,
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the Iterator.remove,
* Collection.remove, removeAll, retainAll and clear operations.
* It does not support the add or addAll operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection values() {
if (collectionOfValues[KEY] == null) {
collectionOfValues[KEY] = new AbstractCollection() {
public Iterator iterator() {
return new DoubleOrderedMapIterator(KEY) {
protected Object doGetNext() {
return lastReturnedNode.getData(VALUE);
}
};
}
public int size() {
return DoubleOrderedMap.this.size();
}
public boolean contains(Object o) {
return containsValue(o);
}
public boolean remove(Object o) {
int oldNodeCount = nodeCount;
removeValue(o);
return nodeCount != oldNodeCount;
}
public boolean removeAll(Collection c) {
boolean modified = false;
Iterator iter = c.iterator();
while (iter.hasNext()) {
if (removeValue(iter.next()) != null) {
modified = true;
}
}
return modified;
}
public void clear() {
DoubleOrderedMap.this.clear();
}
};
}
return collectionOfValues[KEY];
}
/**
* Returns a set view of the mappings contained in this map. Each
* element in the returned set is a Map.Entry. The set is backed
* by the map, so changes to the map are reflected in the set, and
* vice-versa. If the map is modified while an iteration over the
* set is in progress, the results of the iteration are
* undefined.
*
* The set supports element removal, which removes the corresponding
* mapping from the map, via the Iterator.remove, Set.remove, removeAll,
* retainAll and clear operations.
* It does not support the add or addAll operations.
* The setValue method is not supported on the Map Entry.
*
* @return a set view of the mappings contained in this map.
*/
public Set entrySet() {
if (setOfEntries[KEY] == null) {
setOfEntries[KEY] = new AbstractSet() {
public Iterator iterator() {
return new DoubleOrderedMapIterator(KEY) {
protected Object doGetNext() {
return lastReturnedNode;
}
};
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) o;
Object value = entry.getValue();
Node node = lookup((Comparable) entry.getKey(),
KEY);
return (node != null)
&& node.getData(VALUE).equals(value);
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) o;
Object value = entry.getValue();
Node node = lookup((Comparable) entry.getKey(),
KEY);
if ((node != null) && node.getData(VALUE).equals(value)) {
doRedBlackDelete(node);
return true;
}
return false;
}
public int size() {
return DoubleOrderedMap.this.size();
}
public void clear() {
DoubleOrderedMap.this.clear();
}
};
}
return setOfEntries[KEY];
}
/* ********** END implementation of Map ********** */
private abstract class DoubleOrderedMapIterator implements Iterator {
private int expectedModifications;
protected Node lastReturnedNode;
private Node nextNode;
private int iteratorType;
/**
* Constructor
*
* @param type
*/
DoubleOrderedMapIterator(final int type) {
iteratorType = type;
expectedModifications = DoubleOrderedMap.this.modifications;
lastReturnedNode = null;
nextNode = leastNode(rootNode[iteratorType],
iteratorType);
}
/**
* @return 'next', whatever that means for a given kind of
* DoubleOrderedMapIterator
*/
protected abstract Object doGetNext();
/* ********** START implementation of Iterator ********** */
/**
* @return true if the iterator has more elements.
*/
public final boolean hasNext() {
return nextNode != null;
}
/**
* @return the next element in the iteration.
*
* @throws NoSuchElementException if iteration has no more
* elements.
* @throws ConcurrentModificationException if the
* DoubleOrderedMap is
* modified behind
* the iterator's
* back
*/
public final Object next()
throws NoSuchElementException,
ConcurrentModificationException {
if (nextNode == null) {
throw new NoSuchElementException();
}
if (modifications != expectedModifications) {
throw new ConcurrentModificationException();
}
lastReturnedNode = nextNode;
nextNode = nextGreater(nextNode, iteratorType);
return doGetNext();
}
/**
* Removes from the underlying collection the last element
* returned by the iterator. This method can be called only
* once per call to next. The behavior of an iterator is
* unspecified if the underlying collection is modified while
* the iteration is in progress in any way other than by
* calling this method.
*
* @throws 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.
* @throws ConcurrentModificationException if the
* DoubleOrderedMap is
* modified behind
* the iterator's
* back
*/
public final void remove()
throws IllegalStateException,
ConcurrentModificationException {
if (lastReturnedNode == null) {
throw new IllegalStateException();
}
if (modifications != expectedModifications) {
throw new ConcurrentModificationException();
}
doRedBlackDelete(lastReturnedNode);
expectedModifications++;
lastReturnedNode = null;
}
/* ********** END implementation of Iterator ********** */
} // end private abstract class DoubleOrderedMapIterator
// final for performance
private static final class Node implements Map.Entry, KeyValue {
private Comparable[] data;
private Node[] leftNode;
private Node[] rightNode;
private Node[] parentNode;
private boolean[] blackColor;
private int hashcodeValue;
private boolean calculatedHashCode;
/**
* Make a new cell with given key and value, and with null
* links, and black (true) colors.
*
* @param key
* @param value
*/
Node(final Comparable key, final Comparable value) {
data = new Comparable[]{ key, value };
leftNode = new Node[]{ null, null };
rightNode = new Node[]{ null, null };
parentNode = new Node[]{ null, null };
blackColor = new boolean[]{ true, true };
calculatedHashCode = false;
}
/**
* get the specified data
*
* @param index KEY or VALUE
*
* @return the key or value
*/
private Comparable getData(final int index) {
return data[index];
}
/**
* Set this node's left node
*
* @param node the new left node
* @param index KEY or VALUE
*/
private void setLeft(final Node node, final int index) {
leftNode[index] = node;
}
/**
* get the left node
*
* @param index KEY or VALUE
*
* @return the left node -- may be null
*/
private Node getLeft(final int index) {
return leftNode[index];
}
/**
* Set this node's right node
*
* @param node the new right node
* @param index KEY or VALUE
*/
private void setRight(final Node node, final int index) {
rightNode[index] = node;
}
/**
* get the right node
*
* @param index KEY or VALUE
*
* @return the right node -- may be null
*/
private Node getRight(final int index) {
return rightNode[index];
}
/**
* Set this node's parent node
*
* @param node the new parent node
* @param index KEY or VALUE
*/
private void setParent(final Node node, final int index) {
parentNode[index] = node;
}
/**
* get the parent node
*
* @param index KEY or VALUE
*
* @return the parent node -- may be null
*/
private Node getParent(final int index) {
return parentNode[index];
}
/**
* exchange colors with another node
*
* @param node the node to swap with
* @param index KEY or VALUE
*/
private void swapColors(final Node node, final int index) {
// Swap colors -- old hacker's trick
blackColor[index] ^= node.blackColor[index];
node.blackColor[index] ^= blackColor[index];
blackColor[index] ^= node.blackColor[index];
}
/**
* is this node black?
*
* @param index KEY or VALUE
*
* @return true if black (which is represented as a true boolean)
*/
private boolean isBlack(final int index) {
return blackColor[index];
}
/**
* is this node red?
*
* @param index KEY or VALUE
*
* @return true if non-black
*/
private boolean isRed(final int index) {
return !blackColor[index];
}
/**
* make this node black
*
* @param index KEY or VALUE
*/
private void setBlack(final int index) {
blackColor[index] = true;
}
/**
* make this node red
*
* @param index KEY or VALUE
*/
private void setRed(final int index) {
blackColor[index] = false;
}
/**
* make this node the same color as another
*
* @param node the node whose color we're adopting
* @param index KEY or VALUE
*/
private void copyColor(final Node node, final int index) {
blackColor[index] = node.blackColor[index];
}
/* ********** START implementation of Map.Entry ********** */
/**
* @return the key corresponding to this entry.
*/
public Object getKey() {
return data[KEY];
}
/**
* @return the value corresponding to this entry.
*/
public Object getValue() {
return data[VALUE];
}
/**
* Optional operation that is not permitted in this
* implementation
*
* @param ignored
*
* @return does not return
*
* @throws UnsupportedOperationException
*/
public Object setValue(Object ignored)
throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"Map.Entry.setValue is not supported");
}
/**
* Compares the specified object with this entry for equality.
* Returns true if the given object is also a map entry and
* the two entries represent the same mapping.
*
* @param o object to be compared for equality with this map
* entry.
* @return true if the specified object is equal to this map
* entry.
*/
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) o;
return data[KEY].equals(e.getKey())
&& data[VALUE].equals(e.getValue());
}
/**
* @return the hash code value for this map entry.
*/
public int hashCode() {
if (!calculatedHashCode) {
hashcodeValue = data[KEY].hashCode()
^ data[VALUE].hashCode();
calculatedHashCode = true;
}
return hashcodeValue;
}
/* ********** END implementation of Map.Entry ********** */
}
} // end public class DoubleOrderedMap