diff dwtx/jface/viewers/deferred/LazySortedCollection.d @ 10:b6c35faf97c8

Viewers
author Frank Benoit <benoit@tionex.de>
date Mon, 31 Mar 2008 00:47:19 +0200
parents
children 04b47443bb01
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/viewers/deferred/LazySortedCollection.d	Mon Mar 31 00:47:19 2008 +0200
@@ -0,0 +1,1449 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.viewers.deferred.LazySortedCollection;
+
+import dwtx.jface.viewers.deferred.IntHashMap;
+import dwtx.jface.viewers.deferred.FastProgressReporter;
+// import java.util.Collection;
+// import java.util.Comparator;
+// import java.util.Iterator;
+import tango.util.collection.model.View;
+
+import dwtx.core.runtime.Assert;
+
+import dwt.dwthelper.utils;
+
+/**
+ * This object maintains a collection of elements, sorted by a comparator
+ * given in the constructor. The collection is lazily sorted, allowing
+ * more efficient runtimes for most methods. There are several methods on this
+ * object that allow objects to be queried by their position in the sorted
+ * collection.
+ *
+ * <p>
+ * This is a modified binary search tree. Each subtree has a value, a left and right subtree,
+ * a count of the number of children, and a set of unsorted children.
+ * Insertion happens lazily. When a new node N is inserted into a subtree T, it is initially
+ * added to the set of unsorted children for T without actually comparing it with the value for T.
+ * </p>
+ * <p>
+ * The unsorted children will remain in the unsorted set until some subsequent operation requires
+ * us to know the exact set of elements in one of the subtrees. At that time, we partition
+ * T by comparing all of its unsorted children with T's value and moving them into the left
+ * or right subtrees.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class LazySortedCollection {
+    private const int MIN_CAPACITY = 8;
+    private Object[] contents;
+    private int[] leftSubTree;
+    private int[] rightSubTree;
+    private int[] nextUnsorted;
+    private int[] treeSize;
+    private int[] parentTree;
+    private int root = -1;
+    private int lastNode = 0;
+    private int firstUnusedNode = -1;
+
+    private static const float loadFactor = 0.75f;
+
+    private IntHashMap objectIndices;
+    private Comparator comparator;
+    private static int counter = 0;
+
+    /**
+     * Disables randomization and enables additional runtime error checking.
+     * Severely degrades performance if set to true. Intended for use in test
+     * suites only.
+     */
+    public bool enableDebug = false;
+
+    // This object is inserted as the value into any node scheduled for lazy removal
+    private Object lazyRemovalFlag;
+
+    private void init_instance(){
+        contents = new Object[MIN_CAPACITY];
+        leftSubTree = new int[MIN_CAPACITY];
+        rightSubTree = new int[MIN_CAPACITY];
+        nextUnsorted = new int[MIN_CAPACITY];
+        treeSize = new int[MIN_CAPACITY];
+        parentTree = new int[MIN_CAPACITY];
+        lazyRemovalFlag = new class Object {
+            public String toString() {
+                return "Lazy removal flag";  //$NON-NLS-1$
+            }
+        };
+    }
+
+    private const static int DIR_LEFT = 0;
+    private const static int DIR_RIGHT = 1;
+    private const static int DIR_UNSORTED = 2;
+
+    // Direction constants indicating root nodes
+    private const static int DIR_ROOT = 3;
+    private const static int DIR_UNUSED = 4;
+
+    private final class Edge {
+        private int startNode;
+        private int direction;
+
+        private this() {
+            startNode = -1;
+            direction = -1;
+        }
+
+        private this(int node, int dir) {
+            startNode = node;
+            direction = dir;
+        }
+
+        private int getStart() {
+            return startNode;
+        }
+
+        private int getTarget() {
+            if (startNode is -1) {
+                if (direction is DIR_UNSORTED) {
+                    return firstUnusedNode;
+                } else if (direction is DIR_ROOT) {
+                    return root;
+                }
+                return -1;
+            }
+
+            if (direction is DIR_LEFT) {
+                return leftSubTree[startNode];
+            }
+            if (direction is DIR_RIGHT) {
+                return rightSubTree[startNode];
+            }
+            return nextUnsorted[startNode];
+        }
+
+        private bool isNull() {
+            return getTarget() is -1;
+        }
+
+        /**
+         * Redirects this edge to a new node
+         * @param newNode
+         * @since 3.1
+         */
+        private void setTarget(int newNode) {
+            if (direction is DIR_LEFT) {
+                leftSubTree[startNode] = newNode;
+            } else if (direction is DIR_RIGHT) {
+                rightSubTree[startNode] = newNode;
+            } else if (direction is DIR_UNSORTED) {
+                nextUnsorted[startNode] = newNode;
+            } else if (direction is DIR_ROOT) {
+                root = newNode;
+            } else if (direction is DIR_UNUSED) {
+                firstUnusedNode = newNode;
+            }
+
+            if (newNode !is -1) {
+                parentTree[newNode] = startNode;
+            }
+        }
+
+        private void advance(int direction) {
+            startNode = getTarget();
+            this.direction = direction;
+        }
+    }
+
+    private void setRootNode(int node) {
+        root = node;
+        if (node !is -1) {
+            parentTree[node] = -1;
+        }
+    }
+
+    /**
+     * Creates a new sorted collection using the given comparator to determine
+     * sort order.
+     *
+     * @param c comparator that determines the sort order
+     */
+    public this(Comparator c) {
+        this.comparator = c;
+        init_instance();
+    }
+
+    /**
+     * Tests if this object's internal state is valid. Throws a runtime
+     * exception if the state is invalid, indicating a programming error
+     * in this class. This method is intended for use in test
+     * suites and should not be called by clients.
+     */
+    public void testInvariants() {
+        if (!enableDebug) {
+            return;
+        }
+
+        testInvariants(root);
+    }
+
+    private void testInvariants(int node) {
+        if (node is -1) {
+            return;
+        }
+
+        // Get the current tree size (we will later force the tree size
+        // to be recomputed from scratch -- if everything works properly, then
+        // there should be no change.
+        int treeSize = getSubtreeSize(node);
+
+        int left = leftSubTree[node];
+        int right = rightSubTree[node];
+        int unsorted = nextUnsorted[node];
+
+        if (isUnsorted(node)) {
+            Assert.isTrue(left is -1, "unsorted nodes shouldn't have a left subtree"); //$NON-NLS-1$
+            Assert.isTrue(right is -1, "unsorted nodes shouldn't have a right subtree"); //$NON-NLS-1$
+        }
+
+        if (left !is -1) {
+            testInvariants(left);
+            Assert.isTrue(parentTree[left] is node, "left node has invalid parent pointer"); //$NON-NLS-1$
+        }
+        if (right !is -1) {
+            testInvariants(right);
+            Assert.isTrue(parentTree[right] is node, "right node has invalid parent pointer");             //$NON-NLS-1$
+        }
+
+        int previous = node;
+        while (unsorted !is -1) {
+            int oldTreeSize = this.treeSize[unsorted];
+            recomputeTreeSize(unsorted);
+
+            Assert.isTrue(this.treeSize[unsorted] is oldTreeSize,
+                    "Invalid node size for unsorted node"); //$NON-NLS-1$
+            Assert.isTrue(leftSubTree[unsorted] is -1, "unsorted nodes shouldn't have left subtrees"); //$NON-NLS-1$
+            Assert.isTrue(rightSubTree[unsorted] is -1, "unsorted nodes shouldn't have right subtrees"); //$NON-NLS-1$
+            Assert.isTrue(parentTree[unsorted] is previous, "unsorted node has invalid parent pointer"); //$NON-NLS-1$
+            Assert.isTrue(contents[unsorted] !is lazyRemovalFlag, "unsorted nodes should not be lazily removed"); //$NON-NLS-1$
+            previous = unsorted;
+            unsorted = nextUnsorted[unsorted];
+        }
+
+        // Note that we've already tested that the child sizes are correct... if our size is
+        // correct, then recomputing it now should not cause any change.
+        recomputeTreeSize(node);
+
+        Assert.isTrue(treeSize is getSubtreeSize(node), "invalid tree size"); //$NON-NLS-1$
+    }
+
+    private bool isUnsorted(int node) {
+        int parent = parentTree[node];
+
+        if (parent !is -1) {
+            return nextUnsorted[parent] is node;
+        }
+
+        return false;
+    }
+
+    private final bool isLess(int element1, int element2) {
+        return comparator.compare(contents[element1], contents[element2]) < 0;
+    }
+
+    /**
+     * Adds the given element to the given subtree. Returns the new
+     * root of the subtree.
+     *
+     * @param subTree index of the subtree to insert elementToAdd into. If -1,
+     *                then a new subtree will be created for elementToAdd
+     * @param elementToAdd index of the element to add to the subtree. If -1, this method
+     *                 is a NOP.
+     * @since 3.1
+     */
+    private final int addUnsorted(int subTree, int elementToAdd) {
+        if (elementToAdd is -1) {
+            return subTree;
+        }
+
+        if (subTree is -1) {
+            nextUnsorted[elementToAdd] = -1;
+            treeSize[elementToAdd] = 1;
+            return elementToAdd;
+        }
+
+        // If the subTree is empty (ie: it only contains nodes flagged for lazy removal),
+        // chop it off.
+        if (treeSize[subTree] is 0) {
+            removeSubTree(subTree);
+            nextUnsorted[elementToAdd] = -1;
+            treeSize[elementToAdd] = 1;
+            return elementToAdd;
+        }
+
+        // If neither subtree has any children, add a pseudorandom chance of the
+        // newly added element becoming the new pivot for this node. Note: instead
+        // of a real pseudorandom generator, we simply use a counter here.
+        if (!enableDebug && leftSubTree[subTree] is -1 && rightSubTree[subTree] is -1
+                && leftSubTree[elementToAdd] is -1 && rightSubTree[elementToAdd] is -1) {
+            counter--;
+
+            if (counter % treeSize[subTree] is 0) {
+                // Make the new node into the new pivot
+                nextUnsorted[elementToAdd] = subTree;
+                parentTree[elementToAdd] = parentTree[subTree];
+                parentTree[subTree] = elementToAdd;
+                treeSize[elementToAdd] = treeSize[subTree] + 1;
+                return elementToAdd;
+            }
+        }
+
+        int oldNextUnsorted = nextUnsorted[subTree];
+        nextUnsorted[elementToAdd] = oldNextUnsorted;
+
+        if (oldNextUnsorted is -1) {
+            treeSize[elementToAdd] = 1;
+        } else {
+            treeSize[elementToAdd] = treeSize[oldNextUnsorted] + 1;
+            parentTree[oldNextUnsorted] = elementToAdd;
+        }
+
+        parentTree[elementToAdd] = subTree;
+
+        nextUnsorted[subTree] = elementToAdd;
+        treeSize[subTree]++;
+        return subTree;
+    }
+
+    /**
+     * Returns the number of elements in the collection
+     *
+     * @return the number of elements in the collection
+     */
+    public int size() {
+        int result = getSubtreeSize(root);
+
+        testInvariants();
+
+        return result;
+    }
+
+    /**
+     * Given a tree and one of its unsorted children, this sorts the child by moving
+     * it into the left or right subtrees. Returns the next unsorted child or -1 if none
+     *
+     * @param subTree parent tree
+     * @param toMove child (unsorted) subtree
+     * @since 3.1
+     */
+    private final int partition(int subTree, int toMove) {
+        int result = nextUnsorted[toMove];
+
+        if (isLess(toMove, subTree)) {
+            int nextLeft = addUnsorted(leftSubTree[subTree], toMove);
+            leftSubTree[subTree] = nextLeft;
+            parentTree[nextLeft] = subTree;
+        } else {
+            int nextRight = addUnsorted(rightSubTree[subTree], toMove);
+            rightSubTree[subTree] = nextRight;
+            parentTree[nextRight] = subTree;
+        }
+
+        return result;
+    }
+
+    /**
+     * Partitions the given subtree. Moves all unsorted elements at the given node
+     * to either the left or right subtrees. If the node itself was scheduled for
+     * lazy removal, this will force the node to be removed immediately. Returns
+     * the new subTree.
+     *
+     * @param subTree
+     * @return the replacement node (this may be different from subTree if the subtree
+     * was replaced during the removal)
+     * @since 3.1
+     */
+    private final int partition(int subTree, FastProgressReporter mon) {
+        if (subTree is -1) {
+            return -1;
+        }
+
+        if (contents[subTree] is lazyRemovalFlag) {
+            subTree = removeNode(subTree);
+            if (subTree is -1) {
+                return -1;
+            }
+        }
+
+        for (int idx = nextUnsorted[subTree]; idx !is -1;) {
+            idx = partition(subTree, idx);
+            nextUnsorted[subTree] = idx;
+            if (idx !is -1) {
+                parentTree[idx] = subTree;
+            }
+
+            if (mon.isCanceled()) {
+                throw new InterruptedException();
+            }
+        }
+
+        // At this point, there are no remaining unsorted nodes in this subtree
+        nextUnsorted[subTree] = -1;
+
+        return subTree;
+    }
+
+    private final int getSubtreeSize(int subTree) {
+        if (subTree is -1) {
+            return 0;
+        }
+        return treeSize[subTree];
+    }
+
+    /**
+     * Increases the capacity of this collection, if necessary, so that it can hold the
+     * given number of elements. This can be used prior to a sequence of additions to
+     * avoid memory reallocation. This cannot be used to reduce the amount
+     * of memory used by the collection.
+     *
+     * @param newSize capacity for this collection
+     */
+    public final void setCapacity(int newSize) {
+        if (newSize > contents.length) {
+            setArraySize(newSize);
+        }
+    }
+
+    /**
+     * Adjusts the capacity of the array.
+     *
+     * @param newCapacity
+     */
+    private final void setArraySize(int newCapacity) {
+        Object[] newContents = new Object[newCapacity];
+        System.arraycopy(contents, 0, newContents, 0, lastNode);
+        contents = newContents;
+
+        int[] newLeftSubTree = new int[newCapacity];
+        System.arraycopy(leftSubTree, 0, newLeftSubTree, 0, lastNode);
+        leftSubTree = newLeftSubTree;
+
+        int[] newRightSubTree = new int[newCapacity];
+        System.arraycopy(rightSubTree, 0, newRightSubTree, 0, lastNode);
+        rightSubTree = newRightSubTree;
+
+        int[] newNextUnsorted = new int[newCapacity];
+        System.arraycopy(nextUnsorted, 0, newNextUnsorted, 0, lastNode);
+        nextUnsorted = newNextUnsorted;
+
+        int[] newTreeSize = new int[newCapacity];
+        System.arraycopy(treeSize, 0, newTreeSize, 0, lastNode);
+        treeSize = newTreeSize;
+
+        int[] newParentTree = new int[newCapacity];
+        System.arraycopy(parentTree, 0, newParentTree, 0, lastNode);
+        parentTree = newParentTree;
+    }
+
+    /**
+     * Creates a new node with the given value. Returns the index of the newly
+     * created node.
+     *
+     * @param value
+     * @return the index of the newly created node
+     * @since 3.1
+     */
+    private final int createNode(Object value) {
+        int result = -1;
+
+        if (firstUnusedNode is -1) {
+            // If there are no unused nodes from prior removals, then
+            // we add a node at the end
+            result = lastNode;
+
+            // If this would cause the array to overflow, reallocate the array
+            if (contents.length <= lastNode) {
+                setCapacity(lastNode * 2);
+            }
+
+            lastNode++;
+        } else {
+            // Reuse a node from a prior removal
+            result = firstUnusedNode;
+            firstUnusedNode = nextUnsorted[result];
+        }
+
+        contents[result] = value;
+        treeSize[result] = 1;
+
+        // Clear pointers
+        leftSubTree[result] = -1;
+        rightSubTree[result] = -1;
+        nextUnsorted[result] = -1;
+
+        // As long as we have a hash table of values onto tree indices, incrementally
+        // update the hash table. Note: the table is only constructed as needed, and it
+        // is destroyed whenever the arrays are reallocated instead of reallocating it.
+        if (objectIndices !is null) {
+            objectIndices.put(value, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the current tree index for the given object.
+     *
+     * @param value
+     * @return the current tree index
+     * @since 3.1
+     */
+    private int getObjectIndex(Object value) {
+        // If we don't have a map of values onto tree indices, build the map now.
+        if (objectIndices is null) {
+            int result = -1;
+
+            objectIndices = new IntHashMap(cast(int)(contents.length / loadFactor) + 1, loadFactor);
+
+            for (int i = 0; i < lastNode; i++) {
+                Object element = contents[i];
+
+                if (element !is null && element !is lazyRemovalFlag) {
+                    objectIndices.put(element, i);
+
+                    if (value is element) {
+                        result = i;
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        // If we have a map of values onto tree indices, return the result by looking it up in
+        // the map
+        return objectIndices.get(value, -1);
+    }
+
+    /**
+     * Redirects any pointers from the original to the replacement. If the replacement
+     * causes a change in the number of elements in the parent tree, the changes are
+     * propogated toward the root.
+     *
+     * @param nodeToReplace
+     * @param replacementNode
+     * @since 3.1
+     */
+    private void replaceNode(int nodeToReplace, int replacementNode) {
+        int parent = parentTree[nodeToReplace];
+
+        if (parent is -1) {
+            if (root is nodeToReplace) {
+                setRootNode(replacementNode);
+            }
+        } else {
+            if (leftSubTree[parent] is nodeToReplace) {
+                leftSubTree[parent] = replacementNode;
+            } else if (rightSubTree[parent] is nodeToReplace) {
+                rightSubTree[parent] = replacementNode;
+            } else if (nextUnsorted[parent] is nodeToReplace) {
+                nextUnsorted[parent] = replacementNode;
+            }
+            if (replacementNode !is -1) {
+                parentTree[replacementNode] = parent;
+            }
+        }
+    }
+
+    private void recomputeAncestorTreeSizes(int node) {
+        while (node !is -1) {
+            int oldSize = treeSize[node];
+
+            recomputeTreeSize(node);
+
+            if (treeSize[node] is oldSize) {
+                break;
+            }
+
+            node = parentTree[node];
+        }
+    }
+
+    /**
+     * Recomputes the tree size for the given node.
+     *
+     * @param node
+     * @since 3.1
+     */
+    private void recomputeTreeSize(int node) {
+        if (node is -1) {
+            return;
+        }
+        treeSize[node] = getSubtreeSize(leftSubTree[node])
+            + getSubtreeSize(rightSubTree[node])
+            + getSubtreeSize(nextUnsorted[node])
+            + (contents[node] is lazyRemovalFlag ? 0 : 1);
+    }
+
+    /**
+     *
+     * @param toRecompute
+     * @param whereToStop
+     * @since 3.1
+     */
+    private void forceRecomputeTreeSize(int toRecompute, int whereToStop) {
+        while (toRecompute !is -1 && toRecompute !is whereToStop) {
+            recomputeTreeSize(toRecompute);
+
+            toRecompute = parentTree[toRecompute];
+        }
+    }
+
+    /**
+     * Destroy the node at the given index in the tree
+     * @param nodeToDestroy
+     * @since 3.1
+     */
+    private void destroyNode(int nodeToDestroy) {
+        // If we're maintaining a map of values onto tree indices, remove this entry from
+        // the map
+        if (objectIndices !is null) {
+            Object oldContents = contents[nodeToDestroy];
+            if (oldContents !is lazyRemovalFlag) {
+                objectIndices.remove(oldContents);
+            }
+        }
+
+        contents[nodeToDestroy] = null;
+        leftSubTree[nodeToDestroy] = -1;
+        rightSubTree[nodeToDestroy] = -1;
+
+        if (firstUnusedNode is -1) {
+            treeSize[nodeToDestroy] = 1;
+        } else {
+            treeSize[nodeToDestroy] = treeSize[firstUnusedNode] + 1;
+            parentTree[firstUnusedNode] = nodeToDestroy;
+        }
+
+        nextUnsorted[nodeToDestroy] = firstUnusedNode;
+
+        firstUnusedNode = nodeToDestroy;
+    }
+
+    /**
+     * Frees up memory by clearing the list of nodes that have been freed up through removals.
+     *
+     * @since 3.1
+     */
+    private final void pack() {
+
+        // If there are no unused nodes, then there is nothing to do
+        if (firstUnusedNode is -1) {
+            return;
+        }
+
+        int reusableNodes = getSubtreeSize(firstUnusedNode);
+        int nonPackableNodes = lastNode - reusableNodes;
+
+        // Only pack the array if we're utilizing less than 1/4 of the array (note:
+        // this check is important, or it will change the time bounds for removals)
+        if (contents.length < MIN_CAPACITY || nonPackableNodes > contents.length / 4) {
+            return;
+        }
+
+        // Rather than update the entire map, just null it out. If it is needed,
+        // it will be recreated lazily later. This will save some memory if the
+        // map isn't needed, and it takes a similar amount of time to recreate the
+        // map as to update all the indices.
+        objectIndices = null;
+
+        // Maps old index -> new index
+        int[] mapNewIdxOntoOld = new int[contents.length];
+        int[] mapOldIdxOntoNew = new int[contents.length];
+
+        int nextNewIdx = 0;
+        // Compute the mapping. Determine the new index for each element
+        for (int oldIdx = 0; oldIdx < lastNode; oldIdx++) {
+            if (contents[oldIdx] !is null) {
+                mapOldIdxOntoNew[oldIdx] = nextNewIdx;
+                mapNewIdxOntoOld[nextNewIdx] = oldIdx;
+                nextNewIdx++;
+            } else {
+                mapOldIdxOntoNew[oldIdx] = -1;
+            }
+        }
+
+        // Make the actual array size double the number of nodes to allow
+        // for expansion.
+        int newNodes = nextNewIdx;
+        int newCapacity = Math.max(newNodes * 2, MIN_CAPACITY);
+
+        // Allocate new arrays
+        Object[] newContents = new Object[newCapacity];
+        int[] newTreeSize = new int[newCapacity];
+        int[] newNextUnsorted = new int[newCapacity];
+        int[] newLeftSubTree = new int[newCapacity];
+        int[] newRightSubTree = new int[newCapacity];
+        int[] newParentTree = new int[newCapacity];
+
+        for (int newIdx = 0; newIdx < newNodes; newIdx++) {
+            int oldIdx = mapNewIdxOntoOld[newIdx];
+            newContents[newIdx] = contents[oldIdx];
+            newTreeSize[newIdx] = treeSize[oldIdx];
+
+            int left = leftSubTree[oldIdx];
+            if (left is -1) {
+                newLeftSubTree[newIdx] = -1;
+            } else {
+                newLeftSubTree[newIdx] = mapOldIdxOntoNew[left];
+            }
+
+            int right = rightSubTree[oldIdx];
+            if (right is -1) {
+                newRightSubTree[newIdx] = -1;
+            } else {
+                newRightSubTree[newIdx] = mapOldIdxOntoNew[right];
+            }
+
+            int unsorted = nextUnsorted[oldIdx];
+            if (unsorted is -1) {
+                newNextUnsorted[newIdx] = -1;
+            } else {
+                newNextUnsorted[newIdx] = mapOldIdxOntoNew[unsorted];
+            }
+
+            int parent = parentTree[oldIdx];
+            if (parent is -1) {
+                newParentTree[newIdx] = -1;
+            } else {
+                newParentTree[newIdx] = mapOldIdxOntoNew[parent];
+            }
+        }
+
+        contents = newContents;
+        nextUnsorted = newNextUnsorted;
+        treeSize = newTreeSize;
+        leftSubTree = newLeftSubTree;
+        rightSubTree = newRightSubTree;
+        parentTree = newParentTree;
+
+        if (root !is -1) {
+            root = mapOldIdxOntoNew[root];
+        }
+
+        // All unused nodes have been removed
+        firstUnusedNode = -1;
+        lastNode = newNodes;
+    }
+
+    /**
+     * Adds the given object to the collection. Runs in O(1) amortized time.
+     *
+     * @param toAdd object to add
+     */
+    public final void add(Object toAdd) {
+        Assert.isNotNull(toAdd);
+        // Create the new node
+        int newIdx = createNode(toAdd);
+
+        // Insert the new node into the root tree
+        setRootNode(addUnsorted(root, newIdx));
+
+        testInvariants();
+    }
+
+    /**
+     * Adds all items from the given collection to this collection
+     *
+     * @param toAdd objects to add
+     */
+    public final void addAll( View!(Object) toAdd) {
+        Assert.isNotNull(cast(Object)toAdd);
+        foreach( o; toAdd ){
+            add( o );
+        }
+
+        testInvariants();
+    }
+
+    /**
+     * Adds all items from the given array to the collection
+     *
+     * @param toAdd objects to add
+     */
+    public final void addAll(Object[] toAdd) {
+//         Assert.isNotNull(toAdd);
+        for (int i = 0; i < toAdd.length; i++) {
+            Object object = toAdd[i];
+
+            add(object);
+        }
+
+        testInvariants();
+    }
+
+    /**
+     * Returns true iff the collection is empty
+     *
+     * @return true iff the collection contains no elements
+     */
+    public final bool isEmpty() {
+        bool result = (root is -1);
+
+        testInvariants();
+
+        return result;
+    }
+
+    /**
+     * Removes the given object from the collection. Has no effect if
+     * the element does not exist in this collection.
+     *
+     * @param toRemove element to remove
+     */
+    public final void remove(Object toRemove) {
+        internalRemove(toRemove);
+
+        pack();
+
+        testInvariants();
+    }
+
+    /**
+     * Internal implementation of remove. Removes the given element but does not
+     * pack the container after the removal.
+     *
+     * @param toRemove element to remove
+     */
+    private void internalRemove(Object toRemove) {
+        int objectIndex = getObjectIndex(toRemove);
+
+        if (objectIndex !is -1) {
+            int parent = parentTree[objectIndex];
+            lazyRemoveNode(objectIndex);
+            //Edge parentEdge = getEdgeTo(objectIndex);
+            //parentEdge.setTarget(lazyRemoveNode(objectIndex));
+            recomputeAncestorTreeSizes(parent);
+        }
+
+        //testInvariants();
+    }
+
+    /**
+     * Removes all elements in the given array from this collection.
+     *
+     * @param toRemove elements to remove
+     */
+    public final void removeAll(Object[] toRemove) {
+//         Assert.isNotNull(toRemove);
+
+        for (int i = 0; i < toRemove.length; i++) {
+            Object object = toRemove[i];
+
+            internalRemove(object);
+        }
+        pack();
+    }
+
+    /**
+     * Retains the n smallest items in the collection, removing the rest. When
+     * this method returns, the size of the collection will be n. Note that
+     * this is a no-op if n > the current size of the collection.
+     *
+     * Temporarily package visibility until the implementation of FastProgressReporter
+     * is finished.
+     *
+     * @param n number of items to retain
+     * @param mon progress monitor
+     * @throws InterruptedException if the progress monitor is cancelled in another thread
+     */
+    /* package */ final void retainFirst(int n, FastProgressReporter mon) {
+        int sz = size();
+
+        if (n >= sz) {
+            return;
+        }
+
+        removeRange(n, sz - n, mon);
+
+        testInvariants();
+    }
+
+    /**
+     * Retains the n smallest items in the collection, removing the rest. When
+     * this method returns, the size of the collection will be n. Note that
+     * this is a no-op if n > the current size of the collection.
+     *
+     * @param n number of items to retain
+     */
+    public final void retainFirst(int n) {
+        try {
+            retainFirst(n, new FastProgressReporter());
+        } catch (InterruptedException e) {
+        }
+
+        testInvariants();
+    }
+
+    /**
+     * Removes all elements in the given range from this collection.
+     * For example, removeRange(10, 3) would remove the 11th through 13th
+     * smallest items from the collection.
+     *
+     * @param first 0-based index of the smallest item to remove
+     * @param length number of items to remove
+     */
+    public final void removeRange(int first, int length) {
+        try {
+            removeRange(first, length, new FastProgressReporter());
+        } catch (InterruptedException e) {
+        }
+
+        testInvariants();
+    }
+
+    /**
+     * Removes all elements in the given range from this collection.
+     * For example, removeRange(10, 3) would remove the 11th through 13th
+     * smallest items from the collection.
+     *
+     * Temporarily package visiblity until the implementation of FastProgressReporter is
+     * finished.
+     *
+     * @param first 0-based index of the smallest item to remove
+     * @param length number of items to remove
+     * @param mon progress monitor
+     * @throws InterruptedException if the progress monitor is cancelled in another thread
+     */
+    /* package */ final void removeRange(int first, int length, FastProgressReporter mon) {
+        removeRange(root, first, length, mon);
+
+        pack();
+
+        testInvariants();
+    }
+
+    private final void removeRange(int node, int rangeStart, int rangeLength, FastProgressReporter mon) {
+        if (rangeLength is 0) {
+            return;
+        }
+
+        int size = getSubtreeSize(node);
+
+        if (size <= rangeStart) {
+            return;
+        }
+
+        // If we can chop off this entire subtree without any sorting, do so.
+        if (rangeStart is 0 && rangeLength >= size) {
+            removeSubTree(node);
+            return;
+        }
+        try {
+            // Partition any unsorted nodes
+            node = partition(node, mon);
+
+            int left = leftSubTree[node];
+            int leftSize = getSubtreeSize(left);
+
+            int toRemoveFromLeft = Math.min(leftSize - rangeStart, rangeLength);
+
+            // If we're removing anything from the left node
+            if (toRemoveFromLeft >= 0) {
+                removeRange(leftSubTree[node], rangeStart, toRemoveFromLeft, mon);
+
+                // Check if we're removing from both sides
+                int toRemoveFromRight = rangeStart + rangeLength - leftSize - 1;
+
+                if (toRemoveFromRight >= 0) {
+                    // Remove from right subtree
+                    removeRange(rightSubTree[node], 0, toRemoveFromRight, mon);
+
+                    // ... removing from both sides means we need to remove the node itself too
+                    removeNode(node);
+                    return;
+                }
+            } else {
+                // If removing from the right side only
+                removeRange(rightSubTree[node], rangeStart - leftSize - 1, rangeLength, mon);
+            }
+        } finally {
+            recomputeTreeSize(node);
+        }
+    }
+
+    /**
+     * Prunes the given subtree (and all child nodes, sorted or unsorted).
+     *
+     * @param subTree
+     * @since 3.1
+     */
+    private final void removeSubTree(int subTree) {
+        if (subTree is -1) {
+            return;
+        }
+
+        // Destroy all unsorted nodes
+        for (int next = nextUnsorted[subTree]; next !is -1;) {
+            int current = next;
+            next = nextUnsorted[next];
+
+            // Destroy this unsorted node
+            destroyNode(current);
+        }
+
+        // Destroy left subtree
+        removeSubTree(leftSubTree[subTree]);
+
+        // Destroy right subtree
+        removeSubTree(rightSubTree[subTree]);
+
+        replaceNode(subTree, -1);
+        // Destroy pivot node
+        destroyNode(subTree);
+    }
+
+    /**
+     * Schedules the node for removal. If the node can be removed in constant time,
+     * it is removed immediately.
+     *
+     * @param subTree
+     * @return the replacement node
+     * @since 3.1
+     */
+    private final int lazyRemoveNode(int subTree) {
+        int left = leftSubTree[subTree];
+        int right = rightSubTree[subTree];
+
+        // If this is a leaf node, remove it immediately
+        if (left is -1 && right is -1) {
+            int result = nextUnsorted[subTree];
+            replaceNode(subTree, result);
+            destroyNode(subTree);
+            return result;
+        }
+
+        // Otherwise, flag it for future removal
+        Object value = contents[subTree];
+        contents[subTree] = lazyRemovalFlag;
+        treeSize[subTree]--;
+        if (objectIndices !is null) {
+            objectIndices.remove(value);
+        }
+
+        return subTree;
+    }
+
+    /**
+     * Removes the given subtree, replacing it with one of its children.
+     * Returns the new root of the subtree
+     *
+     * @param subTree
+     * @return the index of the new root
+     * @since 3.1
+     */
+    private final int removeNode(int subTree) {
+        int left = leftSubTree[subTree];
+        int right = rightSubTree[subTree];
+
+        if (left is -1 || right is -1) {
+            int result = -1;
+
+            if (left is -1 && right is -1) {
+                // If this is a leaf node, replace it with its first unsorted child
+                result = nextUnsorted[subTree];
+            } else {
+                // Either the left or right child is missing -- replace with the remaining child
+                if (left is -1) {
+                    result = right;
+                } else {
+                    result = left;
+                }
+
+                try {
+                    result = partition(result, new FastProgressReporter());
+                } catch (InterruptedException e) {
+
+                }
+                if (result is -1) {
+                    result = nextUnsorted[subTree];
+                } else {
+                    int unsorted = nextUnsorted[subTree];
+                    nextUnsorted[result] = unsorted;
+                    int additionalNodes = 0;
+                    if (unsorted !is -1) {
+                        parentTree[unsorted] = result;
+                        additionalNodes = treeSize[unsorted];
+                    }
+                    treeSize[result] += additionalNodes;
+                }
+            }
+
+            replaceNode(subTree, result);
+            destroyNode(subTree);
+            return result;
+        }
+
+        // Find the edges that lead to the next-smallest and
+        // next-largest nodes
+        Edge nextSmallest = new Edge(subTree, DIR_LEFT);
+        while (!nextSmallest.isNull()) {
+            nextSmallest.advance(DIR_RIGHT);
+        }
+
+        Edge nextLargest = new Edge(subTree, DIR_RIGHT);
+        while (!nextLargest.isNull()) {
+            nextLargest.advance(DIR_LEFT);
+        }
+
+        // Index of the replacement node
+        int replacementNode = -1;
+
+        // Count of number of nodes moved to the right
+
+        int leftSize = getSubtreeSize(left);
+        int rightSize = getSubtreeSize(right);
+
+        // Swap with a child from the larger subtree
+        if (leftSize > rightSize) {
+            replacementNode = nextSmallest.getStart();
+
+            // Move any unsorted nodes that are larger than the replacement node into
+            // the left subtree of the next-largest node
+            Edge unsorted = new Edge(replacementNode, DIR_UNSORTED);
+            while (!unsorted.isNull()) {
+                int target = unsorted.getTarget();
+
+                if (!isLess(target, replacementNode)) {
+                    unsorted.setTarget(nextUnsorted[target]);
+                    nextLargest.setTarget(addUnsorted(nextLargest.getTarget(), target));
+                } else {
+                    unsorted.advance(DIR_UNSORTED);
+                }
+            }
+
+            forceRecomputeTreeSize(unsorted.getStart(), replacementNode);
+            forceRecomputeTreeSize(nextLargest.getStart(), subTree);
+        } else {
+            replacementNode = nextLargest.getStart();
+
+            // Move any unsorted nodes that are smaller than the replacement node into
+            // the right subtree of the next-smallest node
+            Edge unsorted = new Edge(replacementNode, DIR_UNSORTED);
+            while (!unsorted.isNull()) {
+                int target = unsorted.getTarget();
+
+                if (isLess(target, replacementNode)) {
+                    unsorted.setTarget(nextUnsorted[target]);
+                    nextSmallest.setTarget(addUnsorted(nextSmallest.getTarget(), target));
+                } else {
+                    unsorted.advance(DIR_UNSORTED);
+                }
+            }
+
+            forceRecomputeTreeSize(unsorted.getStart(), replacementNode);
+            forceRecomputeTreeSize(nextSmallest.getStart(), subTree);
+        }
+
+        // Now all the affected treeSize[...] elements should be updated to reflect the
+        // unsorted nodes that moved. Swap nodes.
+        Object replacementContent = contents[replacementNode];
+        contents[replacementNode] = contents[subTree];
+        contents[subTree] = replacementContent;
+
+        if (objectIndices !is null) {
+            objectIndices.put(replacementContent, subTree);
+            // Note: currently we don't bother updating the index of the replacement
+            // node since we're going to remove it immediately afterwards and there's
+            // no good reason to search for the index in a method that was given the
+            // index as a parameter...
+
+            // objectIndices.put(contents[replacementNode], replacementNode)
+        }
+
+        int replacementParent = parentTree[replacementNode];
+
+        replaceNode(replacementNode, removeNode(replacementNode));
+        //Edge parentEdge = getEdgeTo(replacementNode);
+        //parentEdge.setTarget(removeNode(replacementNode));
+
+        forceRecomputeTreeSize(replacementParent, subTree);
+        recomputeTreeSize(subTree);
+
+        //testInvariants();
+
+        return subTree;
+    }
+
+    /**
+     * Removes all elements from the collection
+     */
+    public final void clear() {
+        lastNode = 0;
+        setArraySize(MIN_CAPACITY);
+        root = -1;
+        firstUnusedNode = -1;
+        objectIndices = null;
+
+        testInvariants();
+    }
+
+    /**
+     * Returns the comparator that is determining the sort order for this collection
+     *
+     * @return comparator for this collection
+     */
+    public Comparator getComparator() {
+        return comparator;
+    }
+
+    /**
+     * Fills in an array of size n with the n smallest elements from the collection.
+     * Can compute the result in sorted or unsorted order.
+     *
+     * Currently package visible until the implementation of FastProgressReporter is finished.
+     *
+     * @param result array to be filled
+     * @param sorted if true, the result array will be sorted. If false, the result array
+     * may be unsorted. This does not affect which elements appear in the result, only their
+     * order.
+     * @param mon monitor used to report progress and check for cancellation
+     * @return the number of items inserted into the result array. This will be equal to the minimum
+     * of result.length and container.size()
+     * @throws InterruptedException if the progress monitor is cancelled
+     */
+    /* package */ final int getFirst(Object[] result, bool sorted, FastProgressReporter mon) {
+        int returnValue = getRange(result, 0, sorted, mon);
+
+        testInvariants();
+
+        return returnValue;
+    }
+
+    /**
+     * Fills in an array of size n with the n smallest elements from the collection.
+     * Can compute the result in sorted or unsorted order.
+     *
+     * @param result array to be filled
+     * @param sorted if true, the result array will be sorted. If false, the result array
+     * may be unsorted. This does not affect which elements appear in the result. It only
+     * affects their order. Computing an unsorted result is asymptotically faster.
+     * @return the number of items inserted into the result array. This will be equal to the minimum
+     * of result.length and container.size()
+     */
+    public final int getFirst(Object[] result, bool sorted) {
+        int returnValue = 0;
+
+        try {
+            returnValue = getFirst(result, sorted, new FastProgressReporter());
+        } catch (InterruptedException e) {
+        }
+
+        testInvariants();
+
+        return returnValue;
+    }
+
+    /**
+     * Given a position defined by k and an array of size n, this fills in the array with
+     * the kth smallest element through to the (k+n)th smallest element. For example,
+     * getRange(myArray, 10, false) would fill in myArray starting with the 10th smallest item
+     * in the collection. The result can be computed in sorted or unsorted order. Computing the
+     * result in unsorted order is more efficient.
+     * <p>
+     * Temporarily set to package visibility until the implementation of FastProgressReporter
+     * is finished.
+     * </p>
+     *
+     * @param result array to be filled in
+     * @param rangeStart index of the smallest element to appear in the result
+     * @param sorted true iff the result array should be sorted
+     * @param mon progress monitor used to cancel the operation
+     * @throws InterruptedException if the progress monitor was cancelled in another thread
+     */
+    /* package */ final int getRange(Object[] result, int rangeStart, bool sorted, FastProgressReporter mon) {
+        return getRange(result, 0, rangeStart, root, sorted, mon);
+    }
+
+    /**
+     * Computes the n through n+k items in this collection.
+     * Computing the result in unsorted order is more efficient. Sorting the result will
+     * not change which elements actually show up in the result. That is, even if the result is
+     * unsorted, it will still contain the same elements as would have been at that range in
+     * a fully sorted collection.
+     *
+     * @param result array containing the result
+     * @param rangeStart index of the first element to be inserted into the result array
+     * @param sorted true iff the result will be computed in sorted order
+     * @return the number of items actually inserted into the result array (will be the minimum
+     * of result.length and this.size())
+     */
+    public final int getRange(Object[] result, int rangeStart, bool sorted) {
+        int returnValue = 0;
+
+        try {
+            returnValue = getRange(result, rangeStart, sorted, new FastProgressReporter());
+        } catch (InterruptedException e) {
+        }
+
+        testInvariants();
+
+        return returnValue;
+    }
+
+    /**
+     * Returns the item at the given index. Indexes are based on sorted order.
+     *
+     * @param index index to test
+     * @return the item at the given index
+     */
+    public final Object getItem(int index) {
+        Object[] result = new Object[1];
+        try {
+            getRange(result, index, false, new FastProgressReporter());
+        } catch (InterruptedException e) {
+            // shouldn't happen
+        }
+        Object returnValue = result[0];
+
+        testInvariants();
+
+        return returnValue;
+    }
+
+    /**
+     * Returns the contents of this collection as a sorted or unsorted
+     * array. Computing an unsorted array is more efficient.
+     *
+     * @param sorted if true, the result will be in sorted order. If false,
+     * the result may be in unsorted order.
+     * @return the contents of this collection as an array.
+     */
+    public final Object[] getItems(bool sorted) {
+        Object[] result = new Object[size()];
+
+        getRange(result, 0, sorted);
+
+        return result;
+    }
+
+    private final int getRange(Object[] result, int resultIdx, int rangeStart, int node, bool sorted, FastProgressReporter mon) {
+        if (node is -1) {
+            return 0;
+        }
+
+        int availableSpace = result.length - resultIdx;
+
+        // If we're asking for all children of the current node, simply call getChildren
+        if (rangeStart is 0) {
+            if (treeSize[node] <= availableSpace) {
+                return getChildren(result, resultIdx, node, sorted, mon);
+            }
+        }
+
+        node = partition(node, mon);
+        if (node is -1) {
+            return 0;
+        }
+
+        int inserted = 0;
+
+        int numberLessThanNode = getSubtreeSize(leftSubTree[node]);
+
+        if (rangeStart < numberLessThanNode) {
+            if (inserted < availableSpace) {
+                inserted += getRange(result, resultIdx, rangeStart, leftSubTree[node], sorted, mon);
+            }
+        }
+
+        if (rangeStart <= numberLessThanNode) {
+            if (inserted < availableSpace) {
+                result[resultIdx + inserted] = contents[node];
+                inserted++;
+            }
+        }
+
+        if (inserted < availableSpace) {
+            inserted += getRange(result, resultIdx + inserted,
+                Math.max(rangeStart - numberLessThanNode - 1, 0), rightSubTree[node], sorted, mon);
+        }
+
+        return inserted;
+    }
+
+    /**
+     * Fills in the available space in the given array with all children of the given node.
+     *
+     * @param result
+     * @param resultIdx index in the result array where we will begin filling in children
+     * @param node
+     * @return the number of children added to the array
+     * @since 3.1
+     */
+    private final int getChildren(Object[] result, int resultIdx, int node, bool sorted, FastProgressReporter mon) {
+        if (node is -1) {
+            return 0;
+        }
+
+        int tempIdx = resultIdx;
+
+        if (sorted) {
+            node = partition(node, mon);
+            if (node is -1) {
+                return 0;
+            }
+        }
+
+        // Add child nodes smaller than this one
+        if (tempIdx < result.length) {
+            tempIdx += getChildren(result, tempIdx, leftSubTree[node], sorted, mon);
+        }
+
+        // Add the pivot
+        if (tempIdx < result.length) {
+            Object value = contents[node];
+            if (value !is lazyRemovalFlag) {
+                result[tempIdx++] = value;
+            }
+        }
+
+        // Add child nodes larger than this one
+        if (tempIdx < result.length) {
+            tempIdx += getChildren(result, tempIdx, rightSubTree[node], sorted, mon);
+        }
+
+        // Add unsorted children (should be empty if the sorted flag was true)
+        for (int unsortedNode = nextUnsorted[node]; unsortedNode !is -1 && tempIdx < result.length;
+            unsortedNode = nextUnsorted[unsortedNode]) {
+
+            result[tempIdx++] = contents[unsortedNode];
+        }
+
+        return tempIdx - resultIdx;
+    }
+
+    /**
+     * Returns true iff this collection contains the given item
+     *
+     * @param item item to test
+     * @return true iff this collection contains the given item
+     */
+    public bool contains(Object item) {
+        Assert.isNotNull(item);
+        bool returnValue = (getObjectIndex(item) !is -1);
+
+        testInvariants();
+
+        return returnValue;
+    }
+}