org.apache.commons.collections4.bidimap.TreeBidiMap Maven / Gradle / Ivy
Show all versions of com.liferay.dynamic.data.lists.service
/*
* 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.collections4.bidimap;
import static org.apache.commons.collections4.bidimap.TreeBidiMap.DataElement.KEY;
import static org.apache.commons.collections4.bidimap.TreeBidiMap.DataElement.VALUE;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.apache.commons.collections4.KeyValue;
import org.apache.commons.collections4.MapIterator;
import org.apache.commons.collections4.OrderedBidiMap;
import org.apache.commons.collections4.OrderedIterator;
import org.apache.commons.collections4.OrderedMapIterator;
import org.apache.commons.collections4.iterators.EmptyOrderedMapIterator;
import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry;
/**
* Red-Black tree-based implementation of BidiMap where all objects added
* implement the Comparable
interface.
*
* 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.
* If the data contained in the TreeMaps is large, the cost of redundant
* storage becomes significant. The {@link DualTreeBidiMap} and
* {@link DualHashBidiMap} implementations use this approach.
*
* This solution keeps minimizes the data storage by holding data only once.
* The red-black algorithm is based on {@link 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.
*
* The Map.Entry instances returned by the appropriate methods will
* not allow setValue() and will throw an
* UnsupportedOperationException on attempts to call that method.
*
* @param the type of the keys in this map
* @param the type of the values in this map
*
* @since 3.0 (previously DoubleOrderedMap v2.0)
*/
public class TreeBidiMap, V extends Comparable>
implements OrderedBidiMap, Serializable {
enum DataElement {
KEY("key"), VALUE("value");
private final String description;
/**
* Create a new TreeBidiMap.DataElement.
*
* @param description the description for the element
*/
private DataElement(final String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
}
private static final long serialVersionUID = 721969328361807L;
private transient Node[] rootNode;
private transient int nodeCount = 0;
private transient int modifications = 0;
private transient Set keySet;
private transient Set valuesSet;
private transient Set> entrySet;
private transient Inverse inverse = null;
//-----------------------------------------------------------------------
/**
* Constructs a new empty TreeBidiMap.
*/
@SuppressWarnings("unchecked")
public TreeBidiMap() {
super();
rootNode = new Node[2];
}
/**
* 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) {
this();
putAll(map);
}
//-----------------------------------------------------------------------
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
return nodeCount;
}
/**
* Checks whether the map is empty or not.
*
* @return true if the map is empty
*/
@Override
public boolean isEmpty() {
return nodeCount == 0;
}
/**
* Checks whether this map contains the a mapping for the specified key.
*
* The key must implement Comparable
.
*
* @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
*/
@Override
public boolean containsKey(final Object key) {
checkKey(key);
return lookupKey(key) != null;
}
/**
* Checks whether this map contains the a mapping for the specified value.
*
* The value must implement Comparable
.
*
* @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
*/
@Override
public boolean containsValue(final Object value) {
checkValue(value);
return lookupValue(value) != null;
}
/**
* Gets the value to which this map maps the specified key.
* Returns null if the map contains no mapping for this key.
*
* The key must implement Comparable
.
*
* @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
*/
@Override
public V get(final Object key) {
checkKey(key);
final Node node = lookupKey(key);
return node == null ? null : node.getValue();
}
/**
* Puts the key-value pair into the map, replacing any previous pair.
*
* 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.
*
* 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
*
*
* Both key and value must implement Comparable
.
*
* @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
*/
@Override
public V put(final K key, final V value) {
final V result = get(key);
doPut(key, value);
return result;
}
/**
* Puts all the mappings from the specified map into this map.
*
* All keys and values must implement Comparable
.
*
* @param map the map to copy from
*/
@Override
public void putAll(final Map extends K, ? extends V> map) {
for (final Map.Entry extends K, ? extends V> e : map.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Removes the mapping for this key from this map if present.
*
* The key must implement Comparable
.
*
* @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
*/
@Override
public V remove(final Object key) {
return doRemoveKey(key);
}
/**
* Removes all mappings from this map.
*/
@Override
public void clear() {
modify();
nodeCount = 0;
rootNode[KEY.ordinal()] = null;
rootNode[VALUE.ordinal()] = null;
}
//-----------------------------------------------------------------------
/**
* Returns the key to which this map maps the specified value.
* Returns null if the map contains no mapping for this value.
*
* The value must implement Comparable
.
*
* @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
*/
@Override
public K getKey(final Object value) {
checkValue(value);
final Node node = lookupValue(value);
return node == null ? null : node.getKey();
}
/**
* Removes the mapping for this value from this map if present.
*
* The value must implement Comparable
.
*
* @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
*/
@Override
public K removeValue(final Object value) {
return doRemoveValue(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
*/
@Override
public K firstKey() {
if (nodeCount == 0) {
throw new NoSuchElementException("Map is empty");
}
return leastNode(rootNode[KEY.ordinal()], 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
*/
@Override
public K lastKey() {
if (nodeCount == 0) {
throw new NoSuchElementException("Map is empty");
}
return greatestNode(rootNode[KEY.ordinal()], KEY).getKey();
}
/**
* Gets the next key after the one specified.
*
* The key must implement Comparable
.
*
* @param key the key to search for next from
* @return the next key, null if no match or at end
*/
@Override
public K nextKey(final K key) {
checkKey(key);
final Node node = nextGreater(lookupKey(key), KEY);
return node == null ? null : node.getKey();
}
/**
* Gets the previous key before the one specified.
*
* The key must implement Comparable
.
*
* @param key the key to search for previous from
* @return the previous key, null if no match or at start
*/
@Override
public K previousKey(final K key) {
checkKey(key);
final Node node = nextSmaller(lookupKey(key), KEY);
return node == null ? null : node.getKey();
}
//-----------------------------------------------------------------------
/**
* Returns a set view of the keys contained in this map in key order.
*
* 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. It does not support the add or addAll operations.
*
* @return a set view of the keys contained in this map.
*/
@Override
public Set keySet() {
if (keySet == null) {
keySet = new KeyView(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.
*
* 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. It does not support the add or addAll operations.
*
* @return a set view of the values contained in this map.
*/
@Override
public Set values() {
if (valuesSet == null) {
valuesSet = new ValueView(KEY);
}
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.
*
* 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. 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.
*/
@Override
public Set> entrySet() {
if (entrySet == null) {
entrySet = new EntryView();
}
return entrySet;
}
//-----------------------------------------------------------------------
@Override
public OrderedMapIterator mapIterator() {
if (isEmpty()) {
return EmptyOrderedMapIterator.emptyOrderedMapIterator();
}
return new ViewMapIterator(KEY);
}
//-----------------------------------------------------------------------
/**
* Gets the inverse map for comparison.
*
* @return the inverse map
*/
@Override
public OrderedBidiMap inverseBidiMap() {
if (inverse == null) {
inverse = new Inverse();
}
return inverse;
}
//-----------------------------------------------------------------------
/**
* Compares for equals as per the API.
*
* @param obj the object to compare to
* @return true if equal
*/
@Override
public boolean equals(final 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);
}
//-----------------------------------------------------------------------
/**
* Put logic.
*
* @param key the key, always the main map key
* @param value the value, always the main map value
*/
private void doPut(final K key, final V value) {
checkKeyAndValue(key, value);
// store previous and remove previous mappings
doRemoveKey(key);
doRemoveValue(value);
Node node = rootNode[KEY.ordinal()];
if (node == null) {
// map is empty
final Node root = new Node<>(key, value);
rootNode[KEY.ordinal()] = root;
rootNode[VALUE.ordinal()] = root;
grow();
} else {
// add new mapping
while (true) {
final int cmp = compare(key, node.getKey());
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 {
final 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 {
final Node newNode = new Node<>(key, value);
insertValue(newNode);
node.setRight(newNode, KEY);
newNode.setParent(node, KEY);
doRedBlackInsert(newNode, KEY);
grow();
break;
}
}
}
}
}
private V doRemoveKey(final Object key) {
final Node node = lookupKey(key);
if (node == null) {
return null;
}
doRedBlackDelete(node);
return node.getValue();
}
private K doRemoveValue(final Object value) {
final Node node = lookupValue(value);
if (node == null) {
return null;
}
doRedBlackDelete(node);
return node.getKey();
}
/**
* do the actual lookup of a piece of data
*
* @param data the key or value to be looked up
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the desired Node, or null if there is no mapping of the
* specified data
*/
@SuppressWarnings("unchecked")
private > Node lookup(final Object data, final DataElement dataElement) {
Node rval = null;
Node node = rootNode[dataElement.ordinal()];
while (node != null) {
final int cmp = compare((T) data, (T) node.getData(dataElement));
if (cmp == 0) {
rval = node;
break;
}
node = cmp < 0 ? node.getLeft(dataElement) : node.getRight(dataElement);
}
return rval;
}
private Node lookupKey(final Object key) {
return this.lookup(key, KEY);
}
private Node lookupValue(final Object value) {
return this.lookup(value, VALUE);
}
/**
* get the next larger node from the specified node
*
* @param node the node to be searched from
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the specified node
*/
private Node nextGreater(final Node node, final DataElement dataElement) {
Node rval;
if (node == null) {
rval = null;
} else if (node.getRight(dataElement) != 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(dataElement), dataElement);
} 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(dataElement);
Node child = node;
while (parent != null && child == parent.getRight(dataElement)) {
child = parent;
parent = parent.getParent(dataElement);
}
rval = parent;
}
return rval;
}
/**
* get the next larger node from the specified node
*
* @param node the node to be searched from
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the specified node
*/
private Node nextSmaller(final Node node, final DataElement dataElement) {
Node rval;
if (node == null) {
rval = null;
} else if (node.getLeft(dataElement) != 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(dataElement), dataElement);
} 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(dataElement);
Node child = node;
while (parent != null && child == parent.getLeft(dataElement)) {
child = parent;
parent = parent.getParent(dataElement);
}
rval = parent;
}
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 T o1, final T o2) {
return o1.compareTo(o2);
}
/**
* Find the least node from a given node.
*
* @param node the node from which we will start searching
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the smallest node, from the specified node, in the
* specified mapping
*/
private Node leastNode(final Node node, final DataElement dataElement) {
Node rval = node;
if (rval != null) {
while (rval.getLeft(dataElement) != null) {
rval = rval.getLeft(dataElement);
}
}
return rval;
}
/**
* Find the greatest node from a given node.
*
* @param node the node from which we will start searching
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the greatest node, from the specified node
*/
private Node greatestNode(final Node node, final DataElement dataElement) {
Node rval = node;
if (rval != null) {
while (rval.getRight(dataElement) != null) {
rval = rval.getRight(dataElement);
}
}
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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void copyColor(final Node from, final Node to, final DataElement dataElement) {
if (to != null) {
if (from == null) {
// by default, make it black
to.setBlack(dataElement);
} else {
to.copyColor(from, dataElement);
}
}
}
/**
* 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private static boolean isRed(final Node, ?> node, final DataElement dataElement) {
return node != null && node.isRed(dataElement);
}
/**
* 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private static boolean isBlack(final Node, ?> node, final DataElement dataElement) {
return node == null || node.isBlack(dataElement);
}
/**
* force a node (if it exists) red
*
* @param node the node (may be null) in question
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private static void makeRed(final Node, ?> node, final DataElement dataElement) {
if (node != null) {
node.setRed(dataElement);
}
}
/**
* force a node (if it exists) black
*
* @param node the node (may be null) in question
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private static void makeBlack(final Node, ?> node, final DataElement dataElement) {
if (node != null) {
node.setBlack(dataElement);
}
}
/**
* 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private Node getGrandParent(final Node node, final DataElement dataElement) {
return getParent(getParent(node, dataElement), dataElement);
}
/**
* 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private Node getParent(final Node node, final DataElement dataElement) {
return node == null ? null : node.getParent(dataElement);
}
/**
* 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private Node getRightChild(final Node node, final DataElement dataElement) {
return node == null ? null : node.getRight(dataElement);
}
/**
* 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private Node getLeftChild(final Node node, final DataElement dataElement) {
return node == null ? null : node.getLeft(dataElement);
}
/**
* do a rotate left. standard fare in the world of balanced trees
*
* @param node the node to be rotated
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void rotateLeft(final Node node, final DataElement dataElement) {
final Node rightChild = node.getRight(dataElement);
node.setRight(rightChild.getLeft(dataElement), dataElement);
if (rightChild.getLeft(dataElement) != null) {
rightChild.getLeft(dataElement).setParent(node, dataElement);
}
rightChild.setParent(node.getParent(dataElement), dataElement);
if (node.getParent(dataElement) == null) {
// node was the root ... now its right child is the root
rootNode[dataElement.ordinal()] = rightChild;
} else if (node.getParent(dataElement).getLeft(dataElement) == node) {
node.getParent(dataElement).setLeft(rightChild, dataElement);
} else {
node.getParent(dataElement).setRight(rightChild, dataElement);
}
rightChild.setLeft(node, dataElement);
node.setParent(rightChild, dataElement);
}
/**
* do a rotate right. standard fare in the world of balanced trees
*
* @param node the node to be rotated
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void rotateRight(final Node node, final DataElement dataElement) {
final Node leftChild = node.getLeft(dataElement);
node.setLeft(leftChild.getRight(dataElement), dataElement);
if (leftChild.getRight(dataElement) != null) {
leftChild.getRight(dataElement).setParent(node, dataElement);
}
leftChild.setParent(node.getParent(dataElement), dataElement);
if (node.getParent(dataElement) == null) {
// node was the root ... now its left child is the root
rootNode[dataElement.ordinal()] = leftChild;
} else if (node.getParent(dataElement).getRight(dataElement) == node) {
node.getParent(dataElement).setRight(leftChild, dataElement);
} else {
node.getParent(dataElement).setLeft(leftChild, dataElement);
}
leftChild.setRight(node, dataElement);
node.setParent(leftChild, dataElement);
}
/**
* 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 dataElement the KEY or VALUE int
*/
private void doRedBlackInsert(final Node insertedNode, final DataElement dataElement) {
Node currentNode = insertedNode;
makeRed(currentNode, dataElement);
while (currentNode != null
&& currentNode != rootNode[dataElement.ordinal()]
&& isRed(currentNode.getParent(dataElement), dataElement)) {
if (currentNode.isLeftChild(dataElement)) {
final Node y = getRightChild(getGrandParent(currentNode, dataElement), dataElement);
if (isRed(y, dataElement)) {
makeBlack(getParent(currentNode, dataElement), dataElement);
makeBlack(y, dataElement);
makeRed(getGrandParent(currentNode, dataElement), dataElement);
currentNode = getGrandParent(currentNode, dataElement);
} else {
//dead code?
if (currentNode.isRightChild(dataElement)) {
currentNode = getParent(currentNode, dataElement);
rotateLeft(currentNode, dataElement);
}
makeBlack(getParent(currentNode, dataElement), dataElement);
makeRed(getGrandParent(currentNode, dataElement), dataElement);
if (getGrandParent(currentNode, dataElement) != null) {
rotateRight(getGrandParent(currentNode, dataElement), dataElement);
}
}
} else {
// just like clause above, except swap left for right
final Node y = getLeftChild(getGrandParent(currentNode, dataElement), dataElement);
if (isRed(y, dataElement)) {
makeBlack(getParent(currentNode, dataElement), dataElement);
makeBlack(y, dataElement);
makeRed(getGrandParent(currentNode, dataElement), dataElement);
currentNode = getGrandParent(currentNode, dataElement);
} else {
//dead code?
if (currentNode.isLeftChild(dataElement)) {
currentNode = getParent(currentNode, dataElement);
rotateRight(currentNode, dataElement);
}
makeBlack(getParent(currentNode, dataElement), dataElement);
makeRed(getGrandParent(currentNode, dataElement), dataElement);
if (getGrandParent(currentNode, dataElement) != null) {
rotateLeft(getGrandParent(currentNode, dataElement), dataElement);
}
}
}
}
makeBlack(rootNode[dataElement.ordinal()], dataElement);
}
/**
* 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 (final DataElement dataElement : DataElement.values()) {
// if deleted node has both left and children, swap with
// the next greater node
if (deletedNode.getLeft(dataElement) != null && deletedNode.getRight(dataElement) != null) {
swapPosition(nextGreater(deletedNode, dataElement), deletedNode, dataElement);
}
final Node replacement = deletedNode.getLeft(dataElement) != null ?
deletedNode.getLeft(dataElement) : deletedNode.getRight(dataElement);
if (replacement != null) {
replacement.setParent(deletedNode.getParent(dataElement), dataElement);
if (deletedNode.getParent(dataElement) == null) {
rootNode[dataElement.ordinal()] = replacement;
} else if (deletedNode == deletedNode.getParent(dataElement).getLeft(dataElement)) {
deletedNode.getParent(dataElement).setLeft(replacement, dataElement);
} else {
deletedNode.getParent(dataElement).setRight(replacement, dataElement);
}
deletedNode.setLeft(null, dataElement);
deletedNode.setRight(null, dataElement);
deletedNode.setParent(null, dataElement);
if (isBlack(deletedNode, dataElement)) {
doRedBlackDeleteFixup(replacement, dataElement);
}
} else {
// replacement is null
if (deletedNode.getParent(dataElement) == null) {
// empty tree
rootNode[dataElement.ordinal()] = null;
} else {
// deleted node had no children
if (isBlack(deletedNode, dataElement)) {
doRedBlackDeleteFixup(deletedNode, dataElement);
}
if (deletedNode.getParent(dataElement) != null) {
if (deletedNode == deletedNode.getParent(dataElement).getLeft(dataElement)) {
deletedNode.getParent(dataElement).setLeft(null, dataElement);
} else {
deletedNode.getParent(dataElement).setRight(null, dataElement);
}
deletedNode.setParent(null, dataElement);
}
}
}
}
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 dataElement the KEY or VALUE int
*/
private void doRedBlackDeleteFixup(final Node replacementNode, final DataElement dataElement) {
Node currentNode = replacementNode;
while (currentNode != rootNode[dataElement.ordinal()] && isBlack(currentNode, dataElement)) {
if (currentNode.isLeftChild(dataElement)) {
Node siblingNode = getRightChild(getParent(currentNode, dataElement), dataElement);
if (isRed(siblingNode, dataElement)) {
makeBlack(siblingNode, dataElement);
makeRed(getParent(currentNode, dataElement), dataElement);
rotateLeft(getParent(currentNode, dataElement), dataElement);
siblingNode = getRightChild(getParent(currentNode, dataElement), dataElement);
}
if (isBlack(getLeftChild(siblingNode, dataElement), dataElement)
&& isBlack(getRightChild(siblingNode, dataElement), dataElement)) {
makeRed(siblingNode, dataElement);
currentNode = getParent(currentNode, dataElement);
} else {
if (isBlack(getRightChild(siblingNode, dataElement), dataElement)) {
makeBlack(getLeftChild(siblingNode, dataElement), dataElement);
makeRed(siblingNode, dataElement);
rotateRight(siblingNode, dataElement);
siblingNode = getRightChild(getParent(currentNode, dataElement), dataElement);
}
copyColor(getParent(currentNode, dataElement), siblingNode, dataElement);
makeBlack(getParent(currentNode, dataElement), dataElement);
makeBlack(getRightChild(siblingNode, dataElement), dataElement);
rotateLeft(getParent(currentNode, dataElement), dataElement);
currentNode = rootNode[dataElement.ordinal()];
}
} else {
Node siblingNode = getLeftChild(getParent(currentNode, dataElement), dataElement);
if (isRed(siblingNode, dataElement)) {
makeBlack(siblingNode, dataElement);
makeRed(getParent(currentNode, dataElement), dataElement);
rotateRight(getParent(currentNode, dataElement), dataElement);
siblingNode = getLeftChild(getParent(currentNode, dataElement), dataElement);
}
if (isBlack(getRightChild(siblingNode, dataElement), dataElement)
&& isBlack(getLeftChild(siblingNode, dataElement), dataElement)) {
makeRed(siblingNode, dataElement);
currentNode = getParent(currentNode, dataElement);
} else {
if (isBlack(getLeftChild(siblingNode, dataElement), dataElement)) {
makeBlack(getRightChild(siblingNode, dataElement), dataElement);
makeRed(siblingNode, dataElement);
rotateLeft(siblingNode, dataElement);
siblingNode = getLeftChild(getParent(currentNode, dataElement), dataElement);
}
copyColor(getParent(currentNode, dataElement), siblingNode, dataElement);
makeBlack(getParent(currentNode, dataElement), dataElement);
makeBlack(getLeftChild(siblingNode, dataElement), dataElement);
rotateRight(getParent(currentNode, dataElement), dataElement);
currentNode = rootNode[dataElement.ordinal()];
}
}
}
makeBlack(currentNode, dataElement);
}
/**
* 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 dataElement the KEY or VALUE int
*/
private void swapPosition(final Node x, final Node y, final DataElement dataElement) {
// Save initial values.
final Node xFormerParent = x.getParent(dataElement);
final Node xFormerLeftChild = x.getLeft(dataElement);
final Node xFormerRightChild = x.getRight(dataElement);
final Node yFormerParent = y.getParent(dataElement);
final Node yFormerLeftChild = y.getLeft(dataElement);
final Node yFormerRightChild = y.getRight(dataElement);
final boolean xWasLeftChild =
x.getParent(dataElement) != null && x == x.getParent(dataElement).getLeft(dataElement);
final boolean yWasLeftChild =
y.getParent(dataElement) != null && y == y.getParent(dataElement).getLeft(dataElement);
// Swap, handling special cases of one being the other's parent.
if (x == yFormerParent) { // x was y's parent
x.setParent(y, dataElement);
if (yWasLeftChild) {
y.setLeft(x, dataElement);
y.setRight(xFormerRightChild, dataElement);
} else {
y.setRight(x, dataElement);
y.setLeft(xFormerLeftChild, dataElement);
}
} else {
x.setParent(yFormerParent, dataElement);
if (yFormerParent != null) {
if (yWasLeftChild) {
yFormerParent.setLeft(x, dataElement);
} else {
yFormerParent.setRight(x, dataElement);
}
}
y.setLeft(xFormerLeftChild, dataElement);
y.setRight(xFormerRightChild, dataElement);
}
if (y == xFormerParent) { // y was x's parent
y.setParent(x, dataElement);
if (xWasLeftChild) {
x.setLeft(y, dataElement);
x.setRight(yFormerRightChild, dataElement);
} else {
x.setRight(y, dataElement);
x.setLeft(yFormerLeftChild, dataElement);
}
} else {
y.setParent(xFormerParent, dataElement);
if (xFormerParent != null) {
if (xWasLeftChild) {
xFormerParent.setLeft(y, dataElement);
} else {
xFormerParent.setRight(y, dataElement);
}
}
x.setLeft(yFormerLeftChild, dataElement);
x.setRight(yFormerRightChild, dataElement);
}
// Fix children's parent pointers
if (x.getLeft(dataElement) != null) {
x.getLeft(dataElement).setParent(x, dataElement);
}
if (x.getRight(dataElement) != null) {
x.getRight(dataElement).setParent(x, dataElement);
}
if (y.getLeft(dataElement) != null) {
y.getLeft(dataElement).setParent(y, dataElement);
}
if (y.getRight(dataElement) != null) {
y.getRight(dataElement).setParent(y, dataElement);
}
x.swapColors(y, dataElement);
// Check if root changed
if (rootNode[dataElement.ordinal()] == x) {
rootNode[dataElement.ordinal()] = y;
} else if (rootNode[dataElement.ordinal()] == y) {
rootNode[dataElement.ordinal()] = 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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*
* @throws NullPointerException if o is null
* @throws ClassCastException if o is not Comparable
*/
private static void checkNonNullComparable(final Object o, final DataElement dataElement) {
if (o == null) {
throw new NullPointerException(dataElement + " cannot be null");
}
if (!(o instanceof Comparable)) {
throw new ClassCastException(dataElement + " 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.ordinal()];
while (true) {
final int cmp = compare(newNode.getValue(), node.getValue());
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 dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return true if equal
*/
private boolean doEquals(final Object obj, final DataElement dataElement) {
if (obj == this) {
return true;
}
if (obj instanceof Map == false) {
return false;
}
final Map, ?> other = (Map, ?>) obj;
if (other.size() != size()) {
return false;
}
if (nodeCount > 0) {
try {
for (final MapIterator, ?> it = getMapIterator(dataElement); it.hasNext(); ) {
final Object key = it.next();
final Object value = it.getValue();
if (value.equals(other.get(key)) == false) {
return false;
}
}
} catch (final ClassCastException ex) {
return false;
} catch (final NullPointerException ex) {
return false;
}
}
return true;
}
/**
* Gets the hash code value for this map as per the API.
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the hash code value for this map
*/
private int doHashCode(final DataElement dataElement) {
int total = 0;
if (nodeCount > 0) {
for (final MapIterator, ?> it = getMapIterator(dataElement); it.hasNext(); ) {
final Object key = it.next();
final Object value = it.getValue();
total += key.hashCode() ^ value.hashCode();
}
}
return total;
}
/**
* Gets the string form of this map as per AbstractMap.
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the string form of this map
*/
private String doToString(final DataElement dataElement) {
if (nodeCount == 0) {
return "{}";
}
final StringBuilder buf = new StringBuilder(nodeCount * 32);
buf.append('{');
final MapIterator, ?> it = getMapIterator(dataElement);
boolean hasNext = it.hasNext();
while (hasNext) {
final Object key = it.next();
final 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();
}
private MapIterator, ?> getMapIterator(final DataElement dataElement) {
switch (dataElement) {
case KEY:
return new ViewMapIterator(KEY);
case VALUE:
return new InverseViewMapIterator(VALUE);
default:
throw new IllegalArgumentException();
}
}
/**
* Reads the content of the stream.
*
* @param stream the input stream
* @throws IOException if an error occurs while reading from the stream
* @throws ClassNotFoundException if an object read from the stream can not be loaded
*/
@SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException{
stream.defaultReadObject();
rootNode = new Node[2];
final int size = stream.readInt();
for(int i = 0; i < size; i++){
final K k =(K) stream.readObject();
final V v =(V) stream.readObject();
put(k, v);
}
}
/**
* Writes the content to the stream for serialization.
*
* @param stream the output stream
* @throws IOException if an error occurs while writing to the stream
*/
private void writeObject(final ObjectOutputStream stream) throws IOException{
stream.defaultWriteObject();
stream.writeInt(this.size());
for (final Entry entry : entrySet()) {
stream.writeObject(entry.getKey());
stream.writeObject(entry.getValue());
}
}
//-----------------------------------------------------------------------
/**
* A view of this map.
*/
abstract class View extends AbstractSet {
/** Whether to return KEY or VALUE order. */
final DataElement orderType;
/**
* Constructor.
* @param orderType the KEY or VALUE int for the order
*/
View(final DataElement orderType) {
super();
this.orderType = orderType;
}
@Override
public int size() {
return TreeBidiMap.this.size();
}
@Override
public void clear() {
TreeBidiMap.this.clear();
}
}
class KeyView extends View {
/**
* Create a new TreeBidiMap.KeyView.
*/
public KeyView(final DataElement orderType) {
super(orderType);
}
@Override
public Iterator iterator() {
return new ViewMapIterator(orderType);
}
@Override
public boolean contains(final Object obj) {
checkNonNullComparable(obj, KEY);
return lookupKey(obj) != null;
}
@Override
public boolean remove(final Object o) {
return doRemoveKey(o) != null;
}
}
class ValueView extends View {
/**
* Create a new TreeBidiMap.ValueView.
*/
public ValueView(final DataElement orderType) {
super(orderType);
}
@Override
public Iterator iterator() {
return new InverseViewMapIterator(orderType);
}
@Override
public boolean contains(final Object obj) {
checkNonNullComparable(obj, VALUE);
return lookupValue(obj) != null;
}
@Override
public boolean remove(final Object o) {
return doRemoveValue(o) != null;
}
}
/**
* A view of this map.
*/
class EntryView extends View> {
EntryView() {
super(KEY);
}
@Override
public boolean contains(final Object obj) {
if (obj instanceof Map.Entry == false) {
return false;
}
final Map.Entry, ?> entry = (Map.Entry, ?>) obj;
final Object value = entry.getValue();
final Node node = lookupKey(entry.getKey());
return node != null && node.getValue().equals(value);
}
@Override
public boolean remove(final Object obj) {
if (obj instanceof Map.Entry == false) {
return false;
}
final Map.Entry, ?> entry = (Map.Entry, ?>) obj;
final Object value = entry.getValue();
final Node node = lookupKey(entry.getKey());
if (node != null && node.getValue().equals(value)) {
doRedBlackDelete(node);
return true;
}
return false;
}
@Override
public Iterator> iterator() {
return new ViewMapEntryIterator();
}
}
/**
* A view of this map.
*/
class InverseEntryView extends View> {
InverseEntryView() {
super(VALUE);
}
@Override
public boolean contains(final Object obj) {
if (obj instanceof Map.Entry == false) {
return false;
}
final Map.Entry, ?> entry = (Map.Entry, ?>) obj;
final Object value = entry.getValue();
final Node node = lookupValue(entry.getKey());
return node != null && node.getKey().equals(value);
}
@Override
public boolean remove(final Object obj) {
if (obj instanceof Map.Entry == false) {
return false;
}
final Map.Entry, ?> entry = (Map.Entry, ?>) obj;
final Object value = entry.getValue();
final Node node = lookupValue(entry.getKey());
if (node != null && node.getKey().equals(value)) {
doRedBlackDelete(node);
return true;
}
return false;
}
@Override
public Iterator> iterator() {
return new InverseViewMapEntryIterator();
}
}
//-----------------------------------------------------------------------
/**
* An iterator over the map.
*/
abstract class ViewIterator {
/** Whether to return KEY or VALUE order. */
private final DataElement orderType;
/** The last node returned by the iterator. */
Node lastReturnedNode;
/** The next node to be returned by the iterator. */
private Node nextNode;
/** The previous node in the sequence returned by the iterator. */
private Node previousNode;
/** The modification count. */
private int expectedModifications;
/**
* Constructor.
* @param orderType the KEY or VALUE int for the order
*/
ViewIterator(final DataElement orderType) {
super();
this.orderType = orderType;
expectedModifications = modifications;
nextNode = leastNode(rootNode[orderType.ordinal()], orderType);
lastReturnedNode = null;
previousNode = null;
}
public final boolean hasNext() {
return nextNode != null;
}
protected Node navigateNext() {
if (nextNode == null) {
throw new NoSuchElementException();
}
if (modifications != expectedModifications) {
throw new ConcurrentModificationException();
}
lastReturnedNode = nextNode;
previousNode = nextNode;
nextNode = nextGreater(nextNode, orderType);
return lastReturnedNode;
}
public boolean hasPrevious() {
return previousNode != null;
}
protected Node navigatePrevious() {
if (previousNode == null) {
throw new NoSuchElementException();
}
if (modifications != expectedModifications) {
throw new ConcurrentModificationException();
}
nextNode = lastReturnedNode;
if (nextNode == null) {
nextNode = nextGreater(previousNode, orderType);
}
lastReturnedNode = previousNode;
previousNode = nextSmaller(previousNode, orderType);
return lastReturnedNode;
}
public final void remove() {
if (lastReturnedNode == null) {
throw new IllegalStateException();
}
if (modifications != expectedModifications) {
throw new ConcurrentModificationException();
}
doRedBlackDelete(lastReturnedNode);
expectedModifications++;
lastReturnedNode = null;
if (nextNode == null) {
previousNode = greatestNode(rootNode[orderType.ordinal()], orderType);
} else {
previousNode = nextSmaller(nextNode, orderType);
}
}
}
//-----------------------------------------------------------------------
/**
* An iterator over the map.
*/
class ViewMapIterator extends ViewIterator implements OrderedMapIterator {
/**
* Constructor.
*/
ViewMapIterator(final DataElement orderType) {
super(orderType);
}
@Override
public K getKey() {
if (lastReturnedNode == null) {
throw new IllegalStateException(
"Iterator getKey() can only be called after next() and before remove()");
}
return lastReturnedNode.getKey();
}
@Override
public V getValue() {
if (lastReturnedNode == null) {
throw new IllegalStateException(
"Iterator getValue() can only be called after next() and before remove()");
}
return lastReturnedNode.getValue();
}
@Override
public V setValue(final V obj) {
throw new UnsupportedOperationException();
}
@Override
public K next() {
return navigateNext().getKey();
}
@Override
public K previous() {
return navigatePrevious().getKey();
}
}
/**
* An iterator over the map.
*/
class InverseViewMapIterator extends ViewIterator implements OrderedMapIterator {
/**
* Create a new TreeBidiMap.InverseViewMapIterator.
*/
public InverseViewMapIterator(final DataElement orderType) {
super(orderType);
}
@Override
public V getKey() {
if (lastReturnedNode == null) {
throw new IllegalStateException(
"Iterator getKey() can only be called after next() and before remove()");
}
return lastReturnedNode.getValue();
}
@Override
public K getValue() {
if (lastReturnedNode == null) {
throw new IllegalStateException(
"Iterator getValue() can only be called after next() and before remove()");
}
return lastReturnedNode.getKey();
}
@Override
public K setValue(final K obj) {
throw new UnsupportedOperationException();
}
@Override
public V next() {
return navigateNext().getValue();
}
@Override
public V previous() {
return navigatePrevious().getValue();
}
}
/**
* An iterator over the map entries.
*/
class ViewMapEntryIterator extends ViewIterator implements OrderedIterator> {
/**
* Constructor.
*/
ViewMapEntryIterator() {
super(KEY);
}
@Override
public Map.Entry next() {
return navigateNext();
}
@Override
public Map.Entry previous() {
return navigatePrevious();
}
}
/**
* An iterator over the inverse map entries.
*/
class InverseViewMapEntryIterator extends ViewIterator implements OrderedIterator> {
/**
* Constructor.
*/
InverseViewMapEntryIterator() {
super(VALUE);
}
@Override
public Map.Entry next() {
return createEntry(navigateNext());
}
@Override
public Map.Entry previous() {
return createEntry(navigatePrevious());
}
private Map.Entry createEntry(final Node node) {
return new UnmodifiableMapEntry<>(node.getValue(), node.getKey());
}
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
/**
* A node used to store the data.
*/
static class Node, V extends Comparable> implements Map.Entry, KeyValue {
private final K key;
private final V value;
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 the key of this node
* @param value the value of this node
*/
@SuppressWarnings("unchecked")
Node(final K key, final V value) {
super();
this.key = key;
this.value = value;
leftNode = new Node[2];
rightNode = new Node[2];
parentNode = new Node[2];
blackColor = new boolean[] { true, true };
calculatedHashCode = false;
}
private Object getData(final DataElement dataElement) {
switch (dataElement) {
case KEY:
return getKey();
case VALUE:
return getValue();
default:
throw new IllegalArgumentException();
}
}
private void setLeft(final Node node, final DataElement dataElement) {
leftNode[dataElement.ordinal()] = node;
}
private Node getLeft(final DataElement dataElement) {
return leftNode[dataElement.ordinal()];
}
private void setRight(final Node node, final DataElement dataElement) {
rightNode[dataElement.ordinal()] = node;
}
private Node getRight(final DataElement dataElement) {
return rightNode[dataElement.ordinal()];
}
/**
* Set this node's parent node.
*
* @param node the new parent node
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void setParent(final Node node, final DataElement dataElement) {
parentNode[dataElement.ordinal()] = node;
}
/**
* Get the parent node.
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return the parent node, may be null
*/
private Node getParent(final DataElement dataElement) {
return parentNode[dataElement.ordinal()];
}
/**
* Exchange colors with another node.
*
* @param node the node to swap with
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void swapColors(final Node node, final DataElement dataElement) {
// Swap colors -- old hacker's trick
blackColor[dataElement.ordinal()] ^= node.blackColor[dataElement.ordinal()];
node.blackColor[dataElement.ordinal()] ^= blackColor[dataElement.ordinal()];
blackColor[dataElement.ordinal()] ^= node.blackColor[dataElement.ordinal()];
}
/**
* Is this node black?
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return true if black (which is represented as a true boolean)
*/
private boolean isBlack(final DataElement dataElement) {
return blackColor[dataElement.ordinal()];
}
/**
* Is this node red?
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
* @return true if non-black
*/
private boolean isRed(final DataElement dataElement) {
return !blackColor[dataElement.ordinal()];
}
/**
* Make this node black.
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void setBlack(final DataElement dataElement) {
blackColor[dataElement.ordinal()] = true;
}
/**
* Make this node red.
*
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void setRed(final DataElement dataElement) {
blackColor[dataElement.ordinal()] = false;
}
/**
* Make this node the same color as another
*
* @param node the node whose color we're adopting
* @param dataElement either {@link DataElement#KEY} key}
* or the {@link DataElement#VALUE value}.
*/
private void copyColor(final Node node, final DataElement dataElement) {
blackColor[dataElement.ordinal()] = node.blackColor[dataElement.ordinal()];
}
private boolean isLeftChild(final DataElement dataElement) {
return parentNode[dataElement.ordinal()] != null
&& parentNode[dataElement.ordinal()].leftNode[dataElement.ordinal()] == this;
}
private boolean isRightChild(final DataElement dataElement) {
return parentNode[dataElement.ordinal()] != null
&& parentNode[dataElement.ordinal()].rightNode[dataElement.ordinal()] == this;
}
//-------------------------------------------------------------------
/**
* Gets the key.
*
* @return the key corresponding to this entry.
*/
@Override
public K getKey() {
return key;
}
/**
* Gets the value.
*
* @return the value corresponding to this entry.
*/
@Override
public V getValue() {
return value;
}
/**
* Optional operation that is not permitted in this implementation
*
* @param ignored this parameter is ignored.
* @return does not return
* @throws UnsupportedOperationException always
*/
@Override
public V setValue(final V 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;
}
final Map.Entry, ?> e = (Map.Entry, ?>) obj;
return getKey().equals(e.getKey()) && getValue().equals(e.getValue());
}
/**
* @return the hash code value for this map entry.
*/
@Override
public int hashCode() {
if (!calculatedHashCode) {
hashcodeValue = getKey().hashCode() ^ getValue().hashCode();
calculatedHashCode = true;
}
return hashcodeValue;
}
}
//-----------------------------------------------------------------------
/**
* The inverse map implementation.
*/
class Inverse implements OrderedBidiMap {
/** Store the keySet once created. */
private Set inverseKeySet;
/** Store the valuesSet once created. */
private Set inverseValuesSet;
/** Store the entrySet once created. */
private Set> inverseEntrySet;
@Override
public int size() {
return TreeBidiMap.this.size();
}
@Override
public boolean isEmpty() {
return TreeBidiMap.this.isEmpty();
}
@Override
public K get(final Object key) {
return TreeBidiMap.this.getKey(key);
}
@Override
public V getKey(final Object value) {
return TreeBidiMap.this.get(value);
}
@Override
public boolean containsKey(final Object key) {
return TreeBidiMap.this.containsValue(key);
}
@Override
public boolean containsValue(final Object value) {
return TreeBidiMap.this.containsKey(value);
}
@Override
public V firstKey() {
if (TreeBidiMap.this.nodeCount == 0) {
throw new NoSuchElementException("Map is empty");
}
return leastNode(TreeBidiMap.this.rootNode[VALUE.ordinal()], VALUE).getValue();
}
@Override
public V lastKey() {
if (TreeBidiMap.this.nodeCount == 0) {
throw new NoSuchElementException("Map is empty");
}
return greatestNode(TreeBidiMap.this.rootNode[VALUE.ordinal()], VALUE).getValue();
}
@Override
public V nextKey(final V key) {
checkKey(key);
final Node node = nextGreater(TreeBidiMap.this.lookup(key, VALUE), VALUE);
return node == null ? null : node.getValue();
}
@Override
public V previousKey(final V key) {
checkKey(key);
final Node node = TreeBidiMap.this.nextSmaller(TreeBidiMap.this.lookup(key, VALUE), VALUE);
return node == null ? null : node.getValue();
}
@Override
public K put(final V key, final K value) {
final K result = get(key);
TreeBidiMap.this.doPut(value, key);
return result;
}
@Override
public void putAll(final Map extends V, ? extends K> map) {
for (final Map.Entry extends V, ? extends K> e : map.entrySet()) {
put(e.getKey(), e.getValue());
}
}
@Override
public K remove(final Object key) {
return TreeBidiMap.this.removeValue(key);
}
@Override
public V removeValue(final Object value) {
return TreeBidiMap.this.remove(value);
}
@Override
public void clear() {
TreeBidiMap.this.clear();
}
@Override
public Set keySet() {
if (inverseKeySet == null) {
inverseKeySet = new ValueView(VALUE);
}
return inverseKeySet;
}
@Override
public Set values() {
if (inverseValuesSet == null) {
inverseValuesSet = new KeyView(VALUE);
}
return inverseValuesSet;
}
@Override
public Set> entrySet() {
if (inverseEntrySet == null) {
inverseEntrySet = new InverseEntryView();
}
return inverseEntrySet;
}
@Override
public OrderedMapIterator mapIterator() {
if (isEmpty()) {
return EmptyOrderedMapIterator.emptyOrderedMapIterator();
}
return new InverseViewMapIterator(VALUE);
}
@Override
public OrderedBidiMap inverseBidiMap() {
return TreeBidiMap.this;
}
@Override
public boolean equals(final Object obj) {
return TreeBidiMap.this.doEquals(obj, DataElement.VALUE);
}
@Override
public int hashCode() {
return TreeBidiMap.this.doHashCode(DataElement.VALUE);
}
@Override
public String toString() {
return TreeBidiMap.this.doToString(DataElement.VALUE);
}
}
}