/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.graph.dhns.core; import java.util.AbstractList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.gephi.graph.dhns.node.AbstractNode; import org.gephi.graph.dhns.node.iterators.TreeListIterator; /* * 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. */ /** * This class is a modification of the <b><code>TreeList</code></b> from Apache Commons Collections 3.1. * <p>Basically * the <code>TreeList</code> is a <code>List</code> implementation that is optimised for fast insertions and * removals at any index in the list. * <p> * This list implementation uses a tree structure internally to ensure that * all insertions and removals are O(ln(n)). This provides much faster performance * than both an <code>ArrayList</code> and a <code>LinkedList</code> where elements * are inserted and removed repeatedly from anywhere in the list. * <p> * The class has been modified in the way any modification avoid renumbering of the <b>pre</b> order. * <ul><li>Tuned for only store {@link AbstractNode}. And <code>AbstractNode</code> knows his {@link DurableAVLNode}.</li> * <li>The class know if the <b>pre</b> number of items is synchronized with indexes or not. See * <code>preConsistent</code> integer.</li> * <li>When index are not synchronized the real index of <code>DurableAVLNode</code> has to be retrieved.</li> * <li>That's why the parent node has been added to <code>DurableAVLNode</code>. In that way retrieving a * node index can be performed in O(H) where H is the height of the tree.</li></ul> * @author Joerg Schmuecker * @author Stephen Colebourne * @author Mathieu Bastian */ public class DurableTreeList extends AbstractList<AbstractNode> implements Iterable<AbstractNode> { // add; toArray; iterator; insert; get; indexOf; remove // TreeList = 1260;7360;3080; 160; 170;3400; 170; // ArrayList = 220;1480;1760; 6870; 50;1540; 7200; // LinkedList = 270;7360;3350;55860;290720;2910;55200; /** The root node in the AVL tree */ DurableAVLNode root; /** The current size of the list */ int size = 0; private int preConsistent = 0; protected int[] levelsSize; private final GraphViewImpl view; //----------------------------------------------------------------------- /** * Constructs a new empty list. */ public DurableTreeList(GraphViewImpl view) { super(); this.view = view; levelsSize = new int[1]; } public GraphViewImpl getView() { return view; } /** * Constructs a new empty list that copies the specified list. * * @param coll the collection to copy * @throws NullPointerException if the collection is null */ /*public DurableTreeList(Collection<AbstractNode> coll) { super(); addAll(coll); }*/ public void incPreConsistent() { preConsistent++; } //----------------------------------------------------------------------- /** * Gets the element at the specified index. * * @param index the index to retrieve * @return the element at the specified index */ public AbstractNode get(int index) { checkInterval(index, 0, size() - 1); return root.get(index).getValue(); } public DurableAVLNode getNode(int index) { checkInterval(index, 0, size() - 1); return root.get(index); } /** * Gets the current size of the list. * * @return the current size */ public int size() { return size; } /** * Gets an iterator over the list. * * @return an iterator over the list */ @Override public Iterator<AbstractNode> iterator() { // override to go 75% faster return new TreeListIterator(this); } public Iterator<AbstractNode> iterator(int fromIndex) { // override to go 75% faster return new TreeListIterator(this, fromIndex); } /** * Searches for the index of an object in the list. * * @return the index of the object, -1 if not found */ public int indexOf(AbstractNode object) { // override to go 75% faster if (root == null) { return -1; } return root.indexOf(object, root.relativePosition); } /** * Searches for the presence of an object in the list. * * @return true if the object is found */ public boolean contains(AbstractNode object) { return (indexOf(object) >= 0); } /** * Converts the list into an array. * * @return the list as an array */ @Override public AbstractNode[] toArray() { // override to go 20% faster AbstractNode[] array = new AbstractNode[size()]; if (root != null) { root.toArray(array, root.relativePosition); } return array; } //----------------------------------------------------------------------- /** * Adds a new element to the list. * * @param index the index to add before * @param obj the element to add */ @Override public void add(int index, AbstractNode obj) { modCount++; checkInterval(index, 0, size()); incPreConsistent(); if (root == null) { root = new DurableAVLNode(this, index, obj, null, null, null); } else { root = root.insert(index, obj); root.parent = null; } if (obj.level >= levelsSize.length) { levelsSize = Arrays.copyOf(levelsSize, levelsSize.length + 1); } levelsSize[obj.level]++; size++; } @Override public boolean add(AbstractNode e) { add(size, e); return true; } /** * Sets the element at the specified index. * * @param index the index to set * @param obj the object to store at the specified index * @return the previous object at that index * @throws IndexOutOfBoundsException if the index is invalid */ @Override public AbstractNode set(int index, AbstractNode obj) { checkInterval(index, 0, size() - 1); DurableAVLNode node = root.get(index); AbstractNode result = node.value; node.setValue(obj); return result; } /** * Removes the element at the specified index. * * @param index the index to remove * @return the previous object at that index */ @Override public AbstractNode remove(int index) { modCount++; checkInterval(index, 0, size() - 1); AbstractNode result = get(index); levelsSize[result.level]--; result.avlNode.setIndex(index); root = root.remove(index); result.avlNode = null; result.parent = null; size--; incPreConsistent(); return result; } public AbstractNode removeAndKeepParent(int index) { checkInterval(index, 0, size() - 1); //Remove without setting null parent AbstractNode result = get(index); levelsSize[result.level]--; root = root.remove(index); result.avlNode = null; result.size = 0; size--; incPreConsistent(); return result; } public void move(int index, int destination) { checkInterval(index, 0, size() - 1); AbstractNode node = get(index); AbstractNode parent = get(destination); int destinationPre = parent.pre + parent.size + 1; int nodeLimit = node.pre + node.size; boolean forward = destinationPre > node.pre; int difflevel = 0; //Move descendant & self int count = 0; for (int i = node.pre; i <= nodeLimit; i++) { int sourcePre = i; int destPre = destinationPre + count; if (forward) { sourcePre -= count; destPre -= count + 1; } AbstractNode sourceNode = get(sourcePre); levelsSize[sourceNode.level]--; root = root.remove(sourcePre); //Remove sourceNode.avlNode = null; //Remove size--; //Remove //System.out.println("add "+(destPre)+" remove "+sourceNode.getId()); if (count == 0) { sourceNode.parent = parent; difflevel = node.parent.level - node.level + 1; } sourceNode.level += difflevel; add(destPre, sourceNode); count++; } incPreConsistent(); } /** * Clears the list, removing all entries. */ @Override public void clear() { modCount++; root = null; size = 0; levelsSize = new int[1]; } //----------------------------------------------------------------------- /** * Checks whether the index is valid. * * @param index the index to check * @param startIndex the first allowed index * @param endIndex the last allowed index * @throws IndexOutOfBoundsException if the index is invalid */ private void checkInterval(int index, int startIndex, int endIndex) { if (index < startIndex || index > endIndex) { throw new IndexOutOfBoundsException("Invalid index:" + index + ", size=" + size()); } } //----------------------------------------------------------------------- /** * Implements an DurableAVLNode which keeps the offset updated. * <p> * This node contains the real work. * TreeList is just there to implement {@link java.util.List}. * The nodes don't know the index of the object they are holding. They * do know however their position relative to their parent node. * This allows to calculate the index of a node while traversing the tree. * <p> * The Faedelung calculation stores a flag for both the left and right child * to indicate if they are a child (false) or a link as in linked list (true). */ public static class DurableAVLNode { /** The left child node or the predecessor if {@link #leftIsPrevious}.*/ private DurableAVLNode left; /** Flag indicating that left reference is not a subtree but the predecessor. */ private boolean leftIsPrevious; /** The right child node or the successor if {@link #rightIsNext}. */ private DurableAVLNode right; /** Flag indicating that right reference is not a subtree but the successor. */ private boolean rightIsNext; /** How many levels of left/right are below this one. */ private int height; /** The relative position, root holds absolute position. */ private int relativePosition; /** The stored element. */ AbstractNode value; private DurableAVLNode parent; private int preConsistent; private DurableTreeList tree; /** * Constructs a new node with a relative position. * * @param relativePosition the relative position of the node * @param obj the value for the node * @param rightFollower the node with the value following this one * @param leftFollower the node with the value leading this one */ private DurableAVLNode(DurableTreeList treeParent, int relativePosition, AbstractNode obj, DurableAVLNode rightFollower, DurableAVLNode leftFollower, DurableAVLNode parentNode) { this.relativePosition = relativePosition; value = obj; obj.avlNode = this; tree = treeParent; rightIsNext = true; leftIsPrevious = true; right = rightFollower; left = leftFollower; parent = parentNode; preConsistent = tree.preConsistent; } public DurableTreeList getList() { return tree; } public int getIndex() { if (preConsistent != tree.preConsistent) { //The Pre is not consistent DurableAVLNode currentParent = parent; int index = relativePosition; while (currentParent != null) { index += currentParent.relativePosition; currentParent = currentParent.parent; } value.pre = index; value.getPost(); preConsistent = tree.preConsistent; } return value.pre; } public void setIndex(int index) { value.pre = index; value.getPost(); preConsistent = tree.preConsistent; } public boolean isConsistent() { return preConsistent == tree.preConsistent; } /** * Gets the value. * * @return the value of this node */ public AbstractNode getValue() { return value; } /** * Sets the value. * * @param obj the value to store */ void setValue(AbstractNode obj) { this.value = obj; obj.avlNode = this; } /** * Locate the element with the given index relative to the * offset of the parent of this node. */ DurableAVLNode get(int index) { int indexRelativeToMe = index - relativePosition; if (indexRelativeToMe == 0) { //value.setPre(index); return this; } DurableAVLNode nextNode = ((indexRelativeToMe < 0) ? getLeftSubTree() : getRightSubTree()); if (nextNode == null) { return null; } return nextNode.get(indexRelativeToMe); } /** * Locate the index that contains the specified object. */ int indexOf(AbstractNode object, int index) { //value.setPre(index); if (getLeftSubTree() != null) { int result = left.indexOf(object, index + left.relativePosition); if (result != -1) { return result; } } if (value == null ? value == object : value.equals(object)) { return index; } if (getRightSubTree() != null) { return right.indexOf(object, index + right.relativePosition); } return -1; } /** * Stores the node and its children into the array specified. * * @param array the array to be filled * @param index the index of this node */ void toArray(AbstractNode[] array, int index) { array[index] = value; if (getLeftSubTree() != null) { left.toArray(array, index + left.relativePosition); } if (getRightSubTree() != null) { right.toArray(array, index + right.relativePosition); } } /** * Gets the next node in the list after this one. * * @return the next node */ public DurableAVLNode next() { if (rightIsNext || right == null) { return right; } return right.min(); } /** * Gets the node in the list before this one. * * @return the previous node */ DurableAVLNode previous() { if (leftIsPrevious || left == null) { return left; } return left.max(); } /** * Inserts a node at the position index. * * @param index is the index of the position relative to the position of * the parent node. * @param obj is the object to be stored in the position. */ DurableAVLNode insert(int index, AbstractNode obj) { int indexRelativeToMe = index - relativePosition; if (indexRelativeToMe <= 0) { return insertOnLeft(indexRelativeToMe, obj); } else { return insertOnRight(indexRelativeToMe, obj); } } private DurableAVLNode insertOnLeft(int indexRelativeToMe, AbstractNode obj) { DurableAVLNode ret = this; if (getLeftSubTree() == null) { setLeft(new DurableAVLNode(tree, -1, obj, this, left, this), null); } else { setLeft(left.insert(indexRelativeToMe, obj), null); } if (relativePosition >= 0) { relativePosition++; } ret = balance(); recalcHeight(); return ret; } private DurableAVLNode insertOnRight(int indexRelativeToMe, AbstractNode obj) { DurableAVLNode ret = this; if (getRightSubTree() == null) { setRight(new DurableAVLNode(tree, +1, obj, right, this, this), null); } else { setRight(right.insert(indexRelativeToMe, obj), null); } if (relativePosition < 0) { relativePosition--; } ret = balance(); recalcHeight(); return ret; } //----------------------------------------------------------------------- /** * Gets the left node, returning null if its a faedelung. */ private DurableAVLNode getLeftSubTree() { return (leftIsPrevious ? null : left); } /** * Gets the right node, returning null if its a faedelung. */ private DurableAVLNode getRightSubTree() { return (rightIsNext ? null : right); } /** * Gets the rightmost child of this node. * * @return the rightmost child (greatest index) */ private DurableAVLNode max() { return (getRightSubTree() == null) ? this : right.max(); } /** * Gets the leftmost child of this node. * * @return the leftmost child (smallest index) */ private DurableAVLNode min() { return (getLeftSubTree() == null) ? this : left.min(); } /** * Removes the node at a given position. * * @param index is the index of the element to be removed relative to the position of * the parent node of the current node. */ DurableAVLNode remove(int index) { int indexRelativeToMe = index - relativePosition; if (indexRelativeToMe == 0) { return removeSelf(); } if (indexRelativeToMe > 0) { setRight(right.remove(indexRelativeToMe), right.right); if (relativePosition < 0) { relativePosition++; } } else { setLeft(left.remove(indexRelativeToMe), left.left); if (relativePosition > 0) { relativePosition--; } } recalcHeight(); return balance(); } private DurableAVLNode removeMax() { if (getRightSubTree() == null) { return removeSelf(); } setRight(right.removeMax(), right.right); if (relativePosition < 0) { relativePosition++; } recalcHeight(); return balance(); } private DurableAVLNode removeMin() { if (getLeftSubTree() == null) { return removeSelf(); } setLeft(left.removeMin(), left.left); if (relativePosition > 0) { relativePosition--; } recalcHeight(); return balance(); } /** * Removes this node from the tree. * * @return the node that replaces this one in the parent */ private DurableAVLNode removeSelf() { if (getRightSubTree() == null && getLeftSubTree() == null) { return null; } if (getRightSubTree() == null) { if (relativePosition > 0) { left.relativePosition += relativePosition + (relativePosition > 0 ? 0 : 1); } left.max().setRight(null, right); return left; } if (getLeftSubTree() == null) { right.relativePosition += relativePosition - (relativePosition < 0 ? 0 : 1); right.min().setLeft(null, left); return right; } if (heightRightMinusLeft() > 0) { // more on the right, so delete from the right DurableAVLNode rightMin = right.min(); value = rightMin.value; value.avlNode = this; if (leftIsPrevious) { left = rightMin.left; } right = right.removeMin(); right.parent = this; if (relativePosition < 0) { relativePosition++; } } else { // more on the left or equal, so delete from the left DurableAVLNode leftMax = left.max(); value = leftMax.value; value.avlNode = this; if (rightIsNext) { right = leftMax.right; } DurableAVLNode leftPrevious = left.left; left = left.removeMax(); if (left == null) { // special case where left that was deleted was a double link // only occurs when height difference is equal left = leftPrevious; leftIsPrevious = true; } else { left.parent = this; } if (relativePosition > 0) { relativePosition--; } } recalcHeight(); return this; } //----------------------------------------------------------------------- /** * Balances according to the AVL algorithm. */ private DurableAVLNode balance() { switch (heightRightMinusLeft()) { case 1: case 0: case -1: return this; case -2: if (left.heightRightMinusLeft() > 0) { setLeft(left.rotateLeft(), null); } return rotateRight(); case 2: if (right.heightRightMinusLeft() < 0) { setRight(right.rotateRight(), null); } return rotateLeft(); default: throw new RuntimeException("tree inconsistent!"); } } /** * Gets the relative position. */ private int getOffset(DurableAVLNode node) { if (node == null) { return 0; } return node.relativePosition; } /** * Sets the relative position. */ private int setOffset(DurableAVLNode node, int newOffest) { if (node == null) { return 0; } int oldOffset = getOffset(node); node.relativePosition = newOffest; return oldOffset; } /** * Sets the height by calculation. */ private void recalcHeight() { height = Math.max( getLeftSubTree() == null ? -1 : getLeftSubTree().height, getRightSubTree() == null ? -1 : getRightSubTree().height) + 1; } /** * Returns the height of the node or -1 if the node is null. */ private int getHeight(DurableAVLNode node) { return (node == null ? -1 : node.height); } /** * Returns the height difference right - left */ private int heightRightMinusLeft() { return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); } private DurableAVLNode rotateLeft() { DurableAVLNode newTop = right; // can't be faedelung! DurableAVLNode movedNode = getRightSubTree().getLeftSubTree(); int newTopPosition = relativePosition + getOffset(newTop); int myNewPosition = -newTop.relativePosition; int movedPosition = getOffset(newTop) + getOffset(movedNode); setRight(movedNode, newTop); newTop.parent = parent; newTop.setLeft(this, null); setOffset(newTop, newTopPosition); setOffset(this, myNewPosition); setOffset(movedNode, movedPosition); return newTop; } private DurableAVLNode rotateRight() { DurableAVLNode newTop = left; // can't be faedelung DurableAVLNode movedNode = getLeftSubTree().getRightSubTree(); int newTopPosition = relativePosition + getOffset(newTop); int myNewPosition = -newTop.relativePosition; int movedPosition = getOffset(newTop) + getOffset(movedNode); setLeft(movedNode, newTop); newTop.parent = parent; newTop.setRight(this, null); setOffset(newTop, newTopPosition); setOffset(this, myNewPosition); setOffset(movedNode, movedPosition); return newTop; } /** * Sets the left field to the node, or the previous node if that is null * * @param node the new left subtree node * @param previous the previous node in the linked list */ private void setLeft(DurableAVLNode node, DurableAVLNode previous) { leftIsPrevious = (node == null); if (leftIsPrevious) { left = previous; } else { left = node; left.parent = this; } //left = (leftIsPrevious ? previous : node); recalcHeight(); } /** * Sets the right field to the node, or the next node if that is null * * @param node the new left subtree node * @param next the next node in the linked list */ private void setRight(DurableAVLNode node, DurableAVLNode next) { rightIsNext = (node == null); if (rightIsNext) { right = next; } else { right = node; right.parent = this; } //right = (rightIsNext ? next : node); recalcHeight(); } // private void checkFaedelung() { // DurableAVLNode maxNode = left.max(); // if (!maxNode.rightIsFaedelung || maxNode.right != this) { // throw new RuntimeException(maxNode + " should right-faedel to " + this); // } // DurableAVLNode minNode = right.min(); // if (!minNode.leftIsFaedelung || minNode.left != this) { // throw new RuntimeException(maxNode + " should left-faedel to " + this); // } // } // // private int checkTreeDepth() { // int hright = (getRightSubTree() == null ? -1 : getRightSubTree().checkTreeDepth()); // // System.out.print("checkTreeDepth"); // // System.out.print(this); // // System.out.print(" left: "); // // System.out.print(_left); // // System.out.print(" right: "); // // System.out.println(_right); // // int hleft = (left == null ? -1 : left.checkTreeDepth()); // if (height != Math.max(hright, hleft) + 1) { // throw new RuntimeException( // "height should be max" + hleft + "," + hright + " but is " + height); // } // return height; // } // // private int checkLeftSubNode() { // if (getLeftSubTree() == null) { // return 0; // } // int count = 1 + left.checkRightSubNode(); // if (left.relativePosition != -count) { // throw new RuntimeException(); // } // return count + left.checkLeftSubNode(); // } // // private int checkRightSubNode() { // DurableAVLNode right = getRightSubTree(); // if (right == null) { // return 0; // } // int count = 1; // count += right.checkLeftSubNode(); // if (right.relativePosition != count) { // throw new RuntimeException(); // } // return count + right.checkRightSubNode(); // } /** * Used for debugging. */ public String toString() { return "AVLNode(" + relativePosition + "," + (left != null) + "," + value + "," + (getRightSubTree() != null) + ", faedelung " + rightIsNext + " )"; } } public DurableAVLNode getRoot() { return root; } }