blob: 1c6be1d629830bfd1429ef98bf8763aa84f16eb6 [file] [log] [blame]
/**
* 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.hadoop.hdfs.util;
import org.apache.hadoop.util.Time;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.SortedSet;
/**
* A memory efficient implementation of RBTree. Instead of having a Node for
* each entry each node contains an array holding 64 entries.
*
* Based on the Apache Harmony folded TreeMap.
*
* @param <E> Entry type
*/
public class FoldedTreeSet<E> implements SortedSet<E> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private final Comparator<E> comparator;
private Node<E> root;
private int size;
private int nodeCount;
private int modCount;
private Node<E> cachedNode;
/**
* Internal tree node that holds a sorted array of entries.
*
* @param <E> type of the elements
*/
private static class Node<E> {
private static final int NODE_SIZE = 64;
// Tree structure
private Node<E> parent, left, right;
private boolean color;
private final E[] entries;
private int leftIndex = 0, rightIndex = -1;
private int size = 0;
// List for fast ordered iteration
private Node<E> prev, next;
@SuppressWarnings("unchecked")
public Node() {
entries = (E[]) new Object[NODE_SIZE];
}
public boolean isRed() {
return color == RED;
}
public boolean isBlack() {
return color == BLACK;
}
public Node<E> getLeftMostNode() {
Node<E> node = this;
while (node.left != null) {
node = node.left;
}
return node;
}
public Node<E> getRightMostNode() {
Node<E> node = this;
while (node.right != null) {
node = node.right;
}
return node;
}
public void addEntryLeft(E entry) {
assert rightIndex < entries.length;
assert !isFull();
if (leftIndex == 0) {
rightIndex++;
// Shift entries right/up
System.arraycopy(entries, 0, entries, 1, size);
} else {
leftIndex--;
}
size++;
entries[leftIndex] = entry;
}
public void addEntryRight(E entry) {
assert !isFull();
if (rightIndex == NODE_SIZE - 1) {
assert leftIndex > 0;
// Shift entries left/down
System.arraycopy(entries, leftIndex, entries, --leftIndex, size);
} else {
rightIndex++;
}
size++;
entries[rightIndex] = entry;
}
public void addEntryAt(E entry, int index) {
assert !isFull();
if (leftIndex == 0 || ((rightIndex != Node.NODE_SIZE - 1)
&& (rightIndex - index <= index - leftIndex))) {
rightIndex++;
System.arraycopy(entries, index,
entries, index + 1, rightIndex - index);
entries[index] = entry;
} else {
int newLeftIndex = leftIndex - 1;
System.arraycopy(entries, leftIndex,
entries, newLeftIndex, index - leftIndex);
leftIndex = newLeftIndex;
entries[index - 1] = entry;
}
size++;
}
public void addEntriesLeft(Node<E> from) {
leftIndex -= from.size;
size += from.size;
System.arraycopy(from.entries, from.leftIndex,
entries, leftIndex, from.size);
}
public void addEntriesRight(Node<E> from) {
System.arraycopy(from.entries, from.leftIndex,
entries, rightIndex + 1, from.size);
size += from.size;
rightIndex += from.size;
}
public E insertEntrySlideLeft(E entry, int index) {
E pushedEntry = entries[0];
System.arraycopy(entries, 1, entries, 0, index - 1);
entries[index - 1] = entry;
return pushedEntry;
}
public E insertEntrySlideRight(E entry, int index) {
E movedEntry = entries[rightIndex];
System.arraycopy(entries, index, entries, index + 1, rightIndex - index);
entries[index] = entry;
return movedEntry;
}
public E removeEntryLeft() {
assert !isEmpty();
E entry = entries[leftIndex];
entries[leftIndex] = null;
leftIndex++;
size--;
return entry;
}
public E removeEntryRight() {
assert !isEmpty();
E entry = entries[rightIndex];
entries[rightIndex] = null;
rightIndex--;
size--;
return entry;
}
public E removeEntryAt(int index) {
assert !isEmpty();
E entry = entries[index];
int rightSize = rightIndex - index;
int leftSize = index - leftIndex;
if (rightSize <= leftSize) {
System.arraycopy(entries, index + 1, entries, index, rightSize);
entries[rightIndex] = null;
rightIndex--;
} else {
System.arraycopy(entries, leftIndex, entries, leftIndex + 1, leftSize);
entries[leftIndex] = null;
leftIndex++;
}
size--;
return entry;
}
public boolean isFull() {
return size == NODE_SIZE;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
if (leftIndex < rightIndex) {
Arrays.fill(entries, leftIndex, rightIndex + 1, null);
}
size = 0;
leftIndex = 0;
rightIndex = -1;
prev = null;
next = null;
parent = null;
left = null;
right = null;
color = BLACK;
}
}
private static final class TreeSetIterator<E> implements Iterator<E> {
private final FoldedTreeSet<E> tree;
private int iteratorModCount;
private Node<E> node;
private int index;
private E lastEntry;
private int lastIndex;
private Node<E> lastNode;
private TreeSetIterator(FoldedTreeSet<E> tree) {
this.tree = tree;
this.iteratorModCount = tree.modCount;
if (!tree.isEmpty()) {
this.node = tree.root.getLeftMostNode();
this.index = this.node.leftIndex;
}
}
@Override
public boolean hasNext() {
checkForModification();
return node != null;
}
@Override
public E next() {
if (hasNext()) {
lastEntry = node.entries[index];
lastIndex = index;
lastNode = node;
if (++index > node.rightIndex) {
node = node.next;
if (node != null) {
index = node.leftIndex;
}
}
return lastEntry;
} else {
throw new NoSuchElementException("Iterator exhausted");
}
}
@Override
public void remove() {
if (lastEntry == null) {
throw new IllegalStateException("No current element");
}
checkForModification();
if (lastNode.size == 1) {
// Safe to remove lastNode, the iterator is on the next node
tree.deleteNode(lastNode);
} else if (lastNode.leftIndex == lastIndex) {
// Safe to remove leftmost entry, the iterator is on the next index
lastNode.removeEntryLeft();
} else if (lastNode.rightIndex == lastIndex) {
// Safe to remove the rightmost entry, the iterator is on the next node
lastNode.removeEntryRight();
} else {
// Remove entry in the middle of the array
assert node == lastNode;
int oldRIndex = lastNode.rightIndex;
lastNode.removeEntryAt(lastIndex);
if (oldRIndex > lastNode.rightIndex) {
// Entries moved to the left in the array so index must be reset
index = lastIndex;
}
}
lastEntry = null;
iteratorModCount++;
tree.modCount++;
tree.size--;
}
private void checkForModification() {
if (iteratorModCount != tree.modCount) {
throw new ConcurrentModificationException("Tree has been modified "
+ "outside of iterator");
}
}
}
/**
* Create a new TreeSet that uses the natural ordering of objects. The element
* type must implement Comparable.
*/
public FoldedTreeSet() {
this(null);
}
/**
* Create a new TreeSet that orders the elements using the supplied
* Comparator.
*
* @param comparator Comparator able to compare elements of type E
*/
public FoldedTreeSet(Comparator<E> comparator) {
this.comparator = comparator;
}
private Node<E> cachedOrNewNode(E entry) {
Node<E> node = (cachedNode != null) ? cachedNode : new Node<E>();
cachedNode = null;
nodeCount++;
// Since BlockIDs are always increasing for new blocks it is best to
// add values on the left side to enable quicker inserts on the right
node.addEntryLeft(entry);
return node;
}
private void cacheAndClear(Node<E> node) {
if (cachedNode == null) {
node.clear();
cachedNode = node;
}
}
@Override
public Comparator<? super E> comparator() {
return comparator;
}
@Override
public SortedSet<E> subSet(E fromElement, E toElement) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public SortedSet<E> headSet(E toElement) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public SortedSet<E> tailSet(E fromElement) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public E first() {
if (!isEmpty()) {
Node<E> node = root.getLeftMostNode();
return node.entries[node.leftIndex];
}
return null;
}
@Override
public E last() {
if (!isEmpty()) {
Node<E> node = root.getRightMostNode();
return node.entries[node.rightIndex];
}
return null;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return root == null;
}
/**
* Lookup and return a stored object using a user provided comparator.
*
* @param obj Lookup key
* @param cmp User provided Comparator. The comparator should expect that the
* proved obj will always be the first method parameter and any
* stored object will be the second parameter.
*
* @return A matching stored object or null if non is found
*/
public E get(Object obj, Comparator<?> cmp) {
Objects.requireNonNull(obj);
Node<E> node = root;
while (node != null) {
E[] entries = node.entries;
int leftIndex = node.leftIndex;
int result = compare(obj, entries[leftIndex], cmp);
if (result < 0) {
node = node.left;
} else if (result == 0) {
return entries[leftIndex];
} else {
int rightIndex = node.rightIndex;
if (leftIndex != rightIndex) {
result = compare(obj, entries[rightIndex], cmp);
}
if (result == 0) {
return entries[rightIndex];
} else if (result > 0) {
node = node.right;
} else {
int low = leftIndex + 1;
int high = rightIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
result = compare(obj, entries[mid], cmp);
if (result > 0) {
low = mid + 1;
} else if (result < 0) {
high = mid - 1;
} else {
return entries[mid];
}
}
return null;
}
}
}
return null;
}
/**
* Lookup and return a stored object.
*
* @param entry Lookup entry
*
* @return A matching stored object or null if non is found
*/
public E get(E entry) {
return get(entry, comparator);
}
@Override
@SuppressWarnings("unchecked")
public boolean contains(Object obj) {
return get((E) obj) != null;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static int compare(Object lookup, Object stored, Comparator cmp) {
return cmp != null
? cmp.compare(lookup, stored)
: ((Comparable<Object>) lookup).compareTo(stored);
}
@Override
public Iterator<E> iterator() {
return new TreeSetIterator<>(this);
}
@Override
public Object[] toArray() {
Object[] objects = new Object[size];
if (!isEmpty()) {
int pos = 0;
for (Node<E> node = root.getLeftMostNode(); node != null;
pos += node.size, node = node.next) {
System.arraycopy(node.entries, node.leftIndex, objects, pos, node.size);
}
}
return objects;
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
T[] r = a.length >= size ? a
: (T[]) java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
if (!isEmpty()) {
Node<E> node = root.getLeftMostNode();
int pos = 0;
while (node != null) {
System.arraycopy(node.entries, node.leftIndex, r, pos, node.size);
pos += node.size;
node = node.next;
}
if (r.length > pos) {
r[pos] = null;
}
} else if (a.length > 0) {
a[0] = null;
}
return r;
}
/**
* Add or replace an entry in the TreeSet.
*
* @param entry Entry to add or replace/update.
*
* @return the previous entry, or null if this set did not already contain the
* specified entry
*/
public E addOrReplace(E entry) {
return add(entry, true);
}
@Override
public boolean add(E entry) {
return add(entry, false) == null;
}
/**
* Internal add method to add a entry to the set.
*
* @param entry Entry to add
* @param replace Should the entry replace an old entry which is equal to the
* new entry
*
* @return null if entry added and didn't exist or the previous value (which
* might not have been overwritten depending on the replace parameter)
*/
private E add(E entry, boolean replace) {
Objects.requireNonNull(entry);
// Empty tree
if (isEmpty()) {
root = cachedOrNewNode(entry);
size = 1;
modCount++;
return null;
}
// Compare right entry first since inserts of comperatively larger entries
// is more likely to be inserted. BlockID is always increasing in HDFS.
Node<E> node = root;
Node<E> prevNode = null;
int result = 0;
while (node != null) {
prevNode = node;
E[] entries = node.entries;
int rightIndex = node.rightIndex;
result = compare(entry, entries[rightIndex], comparator);
if (result > 0) {
node = node.right;
} else if (result == 0) {
E prevEntry = entries[rightIndex];
if (replace) {
entries[rightIndex] = entry;
}
return prevEntry;
} else {
int leftIndex = node.leftIndex;
if (leftIndex != rightIndex) {
result = compare(entry, entries[leftIndex], comparator);
}
if (result < 0) {
node = node.left;
} else if (result == 0) {
E prevEntry = entries[leftIndex];
if (replace) {
entries[leftIndex] = entry;
}
return prevEntry;
} else {
// Insert in this node
int low = leftIndex + 1, high = rightIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
result = compare(entry, entries[mid], comparator);
if (result > 0) {
low = mid + 1;
} else if (result == 0) {
E prevEntry = entries[mid];
if (replace) {
entries[mid] = entry;
}
return prevEntry;
} else {
high = mid - 1;
}
}
addElementInNode(node, entry, low);
return null;
}
}
}
assert prevNode != null;
size++;
modCount++;
if (!prevNode.isFull()) {
// The previous node still has space
if (result < 0) {
prevNode.addEntryLeft(entry);
} else {
prevNode.addEntryRight(entry);
}
} else if (result < 0) {
// The previous node is full, add to adjencent node or a new node
if (prevNode.prev != null && !prevNode.prev.isFull()) {
prevNode.prev.addEntryRight(entry);
} else {
attachNodeLeft(prevNode, cachedOrNewNode(entry));
}
} else if (prevNode.next != null && !prevNode.next.isFull()) {
prevNode.next.addEntryLeft(entry);
} else {
attachNodeRight(prevNode, cachedOrNewNode(entry));
}
return null;
}
/**
* Insert an entry last in the sorted tree. The entry must be the considered
* larger than the currently largest entry in the set when doing
* current.compareTo(entry), if entry is not the largest entry the method will
* fall back on the regular add method.
*
* @param entry entry to add
*
* @return True if added, false if already existed in the set
*/
public boolean addSortedLast(E entry) {
if (isEmpty()) {
root = cachedOrNewNode(entry);
size = 1;
modCount++;
return true;
} else {
Node<E> node = root.getRightMostNode();
if (compare(node.entries[node.rightIndex], entry, comparator) < 0) {
size++;
modCount++;
if (!node.isFull()) {
node.addEntryRight(entry);
} else {
attachNodeRight(node, cachedOrNewNode(entry));
}
return true;
}
}
// Fallback on normal add if entry is unsorted
return add(entry);
}
private void addElementInNode(Node<E> node, E entry, int index) {
size++;
modCount++;
if (!node.isFull()) {
node.addEntryAt(entry, index);
} else {
// Node is full, insert and push old entry
Node<E> prev = node.prev;
Node<E> next = node.next;
if (prev == null) {
// First check if we have space in the the next node
if (next != null && !next.isFull()) {
E movedEntry = node.insertEntrySlideRight(entry, index);
next.addEntryLeft(movedEntry);
} else {
// Since prev is null the left child must be null
assert node.left == null;
E movedEntry = node.insertEntrySlideLeft(entry, index);
Node<E> newNode = cachedOrNewNode(movedEntry);
attachNodeLeft(node, newNode);
}
} else if (!prev.isFull()) {
// Prev has space
E movedEntry = node.insertEntrySlideLeft(entry, index);
prev.addEntryRight(movedEntry);
} else if (next == null) {
// Since next is null the right child must be null
assert node.right == null;
E movedEntry = node.insertEntrySlideRight(entry, index);
Node<E> newNode = cachedOrNewNode(movedEntry);
attachNodeRight(node, newNode);
} else if (!next.isFull()) {
// Next has space
E movedEntry = node.insertEntrySlideRight(entry, index);
next.addEntryLeft(movedEntry);
} else {
// Both prev and next nodes exist and are full
E movedEntry = node.insertEntrySlideRight(entry, index);
Node<E> newNode = cachedOrNewNode(movedEntry);
if (node.right == null) {
attachNodeRight(node, newNode);
} else {
// Since our right node exist,
// the left node of our next node must be empty
assert next.left == null;
attachNodeLeft(next, newNode);
}
}
}
}
private void attachNodeLeft(Node<E> node, Node<E> newNode) {
newNode.parent = node;
node.left = newNode;
newNode.next = node;
newNode.prev = node.prev;
if (newNode.prev != null) {
newNode.prev.next = newNode;
}
node.prev = newNode;
balanceInsert(newNode);
}
private void attachNodeRight(Node<E> node, Node<E> newNode) {
newNode.parent = node;
node.right = newNode;
newNode.prev = node;
newNode.next = node.next;
if (newNode.next != null) {
newNode.next.prev = newNode;
}
node.next = newNode;
balanceInsert(newNode);
}
/**
* Balance the RB Tree after insert.
*
* @param node Added node
*/
private void balanceInsert(Node<E> node) {
node.color = RED;
while (node != root && node.parent.isRed()) {
if (node.parent == node.parent.parent.left) {
Node<E> uncle = node.parent.parent.right;
if (uncle != null && uncle.isRed()) {
node.parent.color = BLACK;
uncle.color = BLACK;
node.parent.parent.color = RED;
node = node.parent.parent;
} else {
if (node == node.parent.right) {
node = node.parent;
rotateLeft(node);
}
node.parent.color = BLACK;
node.parent.parent.color = RED;
rotateRight(node.parent.parent);
}
} else {
Node<E> uncle = node.parent.parent.left;
if (uncle != null && uncle.isRed()) {
node.parent.color = BLACK;
uncle.color = BLACK;
node.parent.parent.color = RED;
node = node.parent.parent;
} else {
if (node == node.parent.left) {
node = node.parent;
rotateRight(node);
}
node.parent.color = BLACK;
node.parent.parent.color = RED;
rotateLeft(node.parent.parent);
}
}
}
root.color = BLACK;
}
private void rotateRight(Node<E> node) {
Node<E> pivot = node.left;
node.left = pivot.right;
if (pivot.right != null) {
pivot.right.parent = node;
}
pivot.parent = node.parent;
if (node.parent == null) {
root = pivot;
} else if (node == node.parent.right) {
node.parent.right = pivot;
} else {
node.parent.left = pivot;
}
pivot.right = node;
node.parent = pivot;
}
private void rotateLeft(Node<E> node) {
Node<E> pivot = node.right;
node.right = pivot.left;
if (pivot.left != null) {
pivot.left.parent = node;
}
pivot.parent = node.parent;
if (node.parent == null) {
root = pivot;
} else if (node == node.parent.left) {
node.parent.left = pivot;
} else {
node.parent.right = pivot;
}
pivot.left = node;
node.parent = pivot;
}
/**
* Remove object using a provided comparator, and return the removed entry.
*
* @param obj Lookup entry
* @param cmp User provided Comparator. The comparator should expect that the
* proved obj will always be the first method parameter and any
* stored object will be the second parameter.
*
* @return The removed entry or null if not found
*/
public E removeAndGet(Object obj, Comparator<?> cmp) {
Objects.requireNonNull(obj);
if (!isEmpty()) {
Node<E> node = root;
while (node != null) {
E[] entries = node.entries;
int leftIndex = node.leftIndex;
int result = compare(obj, entries[leftIndex], cmp);
if (result < 0) {
node = node.left;
} else if (result == 0) {
return removeElementLeft(node);
} else {
int rightIndex = node.rightIndex;
if (leftIndex != rightIndex) {
result = compare(obj, entries[rightIndex], cmp);
}
if (result == 0) {
return removeElementRight(node);
} else if (result > 0) {
node = node.right;
} else {
int low = leftIndex + 1, high = rightIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
result = compare(obj, entries[mid], cmp);
if (result > 0) {
low = mid + 1;
} else if (result == 0) {
return removeElementAt(node, mid);
} else {
high = mid - 1;
}
}
return null;
}
}
}
}
return null;
}
/**
* Remove object and return the removed entry.
*
* @param obj Lookup entry
*
* @return The removed entry or null if not found
*/
public E removeAndGet(Object obj) {
return removeAndGet(obj, comparator);
}
/**
* Remove object using a provided comparator.
*
* @param obj Lookup entry
* @param cmp User provided Comparator. The comparator should expect that the
* proved obj will always be the first method parameter and any
* stored object will be the second parameter.
*
* @return True if found and removed, else false
*/
public boolean remove(Object obj, Comparator<?> cmp) {
return removeAndGet(obj, cmp) != null;
}
@Override
public boolean remove(Object obj) {
return removeAndGet(obj, comparator) != null;
}
private E removeElementLeft(Node<E> node) {
modCount++;
size--;
E entry = node.removeEntryLeft();
if (node.isEmpty()) {
deleteNode(node);
} else if (node.prev != null
&& (Node.NODE_SIZE - 1 - node.prev.rightIndex) >= node.size) {
// Remaining entries fit in the prev node, move them and delete this node
node.prev.addEntriesRight(node);
deleteNode(node);
} else if (node.next != null && node.next.leftIndex >= node.size) {
// Remaining entries fit in the next node, move them and delete this node
node.next.addEntriesLeft(node);
deleteNode(node);
} else if (node.prev != null && node.prev.size < node.leftIndex) {
// Entries in prev node will fit in this node, move them and delete prev
node.addEntriesLeft(node.prev);
deleteNode(node.prev);
}
return entry;
}
private E removeElementRight(Node<E> node) {
modCount++;
size--;
E entry = node.removeEntryRight();
if (node.isEmpty()) {
deleteNode(node);
} else if (node.prev != null
&& (Node.NODE_SIZE - 1 - node.prev.rightIndex) >= node.size) {
// Remaining entries fit in the prev node, move them and delete this node
node.prev.addEntriesRight(node);
deleteNode(node);
} else if (node.next != null && node.next.leftIndex >= node.size) {
// Remaining entries fit in the next node, move them and delete this node
node.next.addEntriesLeft(node);
deleteNode(node);
} else if (node.next != null
&& node.next.size < (Node.NODE_SIZE - 1 - node.rightIndex)) {
// Entries in next node will fit in this node, move them and delete next
node.addEntriesRight(node.next);
deleteNode(node.next);
}
return entry;
}
private E removeElementAt(Node<E> node, int index) {
modCount++;
size--;
E entry = node.removeEntryAt(index);
if (node.prev != null
&& (Node.NODE_SIZE - 1 - node.prev.rightIndex) >= node.size) {
// Remaining entries fit in the prev node, move them and delete this node
node.prev.addEntriesRight(node);
deleteNode(node);
} else if (node.next != null && (node.next.leftIndex) >= node.size) {
// Remaining entries fit in the next node, move them and delete this node
node.next.addEntriesLeft(node);
deleteNode(node);
} else if (node.prev != null && node.prev.size < node.leftIndex) {
// Entries in prev node will fit in this node, move them and delete prev
node.addEntriesLeft(node.prev);
deleteNode(node.prev);
} else if (node.next != null
&& node.next.size < (Node.NODE_SIZE - 1 - node.rightIndex)) {
// Entries in next node will fit in this node, move them and delete next
node.addEntriesRight(node.next);
deleteNode(node.next);
}
return entry;
}
/**
* Delete the node and ensure the tree is balanced.
*
* @param node node to delete
*/
private void deleteNode(final Node<E> node) {
if (node.right == null) {
if (node.left != null) {
attachToParent(node, node.left);
} else {
attachNullToParent(node);
}
} else if (node.left == null) {
attachToParent(node, node.right);
} else {
// node.left != null && node.right != null
// node.next should replace node in tree
// node.next != null guaranteed since node.left != null
// node.next.left == null since node.next.prev is node
// node.next.right may be null or non-null
Node<E> toMoveUp = node.next;
if (toMoveUp.right == null) {
attachNullToParent(toMoveUp);
} else {
attachToParent(toMoveUp, toMoveUp.right);
}
toMoveUp.left = node.left;
if (toMoveUp.left != null) {
toMoveUp.left.parent = toMoveUp;
}
toMoveUp.right = node.right;
if (toMoveUp.right != null) {
toMoveUp.right.parent = toMoveUp;
}
attachToParentNoBalance(node, toMoveUp);
toMoveUp.color = node.color;
}
// Remove node from ordered list of nodes
if (node.prev != null) {
node.prev.next = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
nodeCount--;
cacheAndClear(node);
}
private void attachToParentNoBalance(Node<E> toDelete, Node<E> toConnect) {
Node<E> parent = toDelete.parent;
toConnect.parent = parent;
if (parent == null) {
root = toConnect;
} else if (toDelete == parent.left) {
parent.left = toConnect;
} else {
parent.right = toConnect;
}
}
private void attachToParent(Node<E> toDelete, Node<E> toConnect) {
attachToParentNoBalance(toDelete, toConnect);
if (toDelete.isBlack()) {
balanceDelete(toConnect);
}
}
private void attachNullToParent(Node<E> toDelete) {
Node<E> parent = toDelete.parent;
if (parent == null) {
root = null;
} else {
if (toDelete == parent.left) {
parent.left = null;
} else {
parent.right = null;
}
if (toDelete.isBlack()) {
balanceDelete(parent);
}
}
}
/**
* Balance tree after removing a node.
*
* @param node Node to balance after deleting another node
*/
private void balanceDelete(Node<E> node) {
while (node != root && node.isBlack()) {
if (node == node.parent.left) {
Node<E> sibling = node.parent.right;
if (sibling == null) {
node = node.parent;
continue;
}
if (sibling.isRed()) {
sibling.color = BLACK;
node.parent.color = RED;
rotateLeft(node.parent);
sibling = node.parent.right;
if (sibling == null) {
node = node.parent;
continue;
}
}
if ((sibling.left == null || !sibling.left.isRed())
&& (sibling.right == null || !sibling.right.isRed())) {
sibling.color = RED;
node = node.parent;
} else {
if (sibling.right == null || !sibling.right.isRed()) {
sibling.left.color = BLACK;
sibling.color = RED;
rotateRight(sibling);
sibling = node.parent.right;
}
sibling.color = node.parent.color;
node.parent.color = BLACK;
sibling.right.color = BLACK;
rotateLeft(node.parent);
node = root;
}
} else {
Node<E> sibling = node.parent.left;
if (sibling == null) {
node = node.parent;
continue;
}
if (sibling.isRed()) {
sibling.color = BLACK;
node.parent.color = RED;
rotateRight(node.parent);
sibling = node.parent.left;
if (sibling == null) {
node = node.parent;
continue;
}
}
if ((sibling.left == null || sibling.left.isBlack())
&& (sibling.right == null || sibling.right.isBlack())) {
sibling.color = RED;
node = node.parent;
} else {
if (sibling.left == null || sibling.left.isBlack()) {
sibling.right.color = BLACK;
sibling.color = RED;
rotateLeft(sibling);
sibling = node.parent.left;
}
sibling.color = node.parent.color;
node.parent.color = BLACK;
sibling.left.color = BLACK;
rotateRight(node.parent);
node = root;
}
}
}
node.color = BLACK;
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object entry : c) {
if (!contains(entry)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E entry : c) {
if (add(entry)) {
modified = true;
}
}
return modified;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean modified = false;
for (Object entry : c) {
if (remove(entry)) {
modified = true;
}
}
return modified;
}
@Override
public void clear() {
modCount++;
if (!isEmpty()) {
size = 0;
nodeCount = 0;
cacheAndClear(root);
root = null;
}
}
/**
* Returns the current size divided by the capacity of the tree. A value
* between 0.0 and 1.0, where 1.0 means that every allocated node in the tree
* is completely full.
*
* An empty set will return 1.0
*
* @return the fill ratio of the tree
*/
public double fillRatio() {
if (nodeCount > 1) {
// Count the last node as completely full since it can't be compacted
return (size + (Node.NODE_SIZE - root.getRightMostNode().size))
/ (double) (nodeCount * Node.NODE_SIZE);
}
return 1.0;
}
/**
* Compact all the entries to use the fewest number of nodes in the tree.
*
* Having a compact tree minimize memory usage, but can cause inserts to get
* slower due to new nodes needs to be allocated as there is no space in any
* of the existing nodes anymore for entries added in the middle of the set.
*
* Useful to do to reduce memory consumption and if the tree is know to not
* change after compaction or mainly added to at either extreme.
*
* @param timeout Maximum time to spend compacting the tree set in
* milliseconds.
*
* @return true if compaction completed, false if aborted
*/
public boolean compact(long timeout) {
if (!isEmpty()) {
long start = Time.monotonicNow();
Node<E> node = root.getLeftMostNode();
while (node != null) {
if (node.prev != null && !node.prev.isFull()) {
Node<E> prev = node.prev;
int count = Math.min(Node.NODE_SIZE - prev.size, node.size);
System.arraycopy(node.entries, node.leftIndex,
prev.entries, prev.rightIndex + 1, count);
node.leftIndex += count;
node.size -= count;
prev.rightIndex += count;
prev.size += count;
}
if (node.isEmpty()) {
Node<E> temp = node.next;
deleteNode(node);
node = temp;
continue;
} else if (!node.isFull()) {
if (node.leftIndex != 0) {
System.arraycopy(node.entries, node.leftIndex,
node.entries, 0, node.size);
Arrays.fill(node.entries, node.size, node.rightIndex + 1, null);
node.leftIndex = 0;
node.rightIndex = node.size - 1;
}
}
node = node.next;
if (Time.monotonicNow() - start > timeout) {
return false;
}
}
}
return true;
}
}