| /** |
| * 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; |
| } |
| } |