From a52c0f00d2077382c2b8c9da926fb47b81fc3392 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 19 Jan 2020 20:55:46 +0200 Subject: [PATCH 01/12] Initial version of TreeListSet and IndexedTreeList --- .../collections4/list/AbstractTreeList.java | 936 ++++++++++++++++++ .../collections4/list/IndexedTreeList.java | 239 +++++ .../collections4/list/TreeListSet.java | 179 ++++ .../list/IndexedTreeListTest.java | 328 ++++++ .../collections4/list/TreeListSetTest.java | 418 ++++++++ 5 files changed, 2100 insertions(+) create mode 100644 src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java create mode 100644 src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java create mode 100644 src/main/java/org/apache/commons/collections4/list/TreeListSet.java create mode 100644 src/test/java/org/apache/commons/collections4/list/IndexedTreeListTest.java create mode 100644 src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java b/src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java new file mode 100644 index 0000000000..7243512407 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java @@ -0,0 +1,936 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.*; + +/** + * Common class for indexable tree lists. + * + * Code is based on apache common collections TreeList. + * + * @author Aleksandr Maksymenko + */ +abstract class AbstractTreeList extends AbstractList { + + /** The root node in the AVL tree */ + protected AVLNode root; + + /** Size of a List */ + protected int size = 0; + + //----------------------------------------------------------------------- + /** + * Gets the element at the specified index. + * + * @param index the index to retrieve + * @return the element at the specified index + */ + @Override + public E get(final int index) { + return getNode(index).getValue(); + } + + /** + * Gets the current size of the list. + * + * @return the current size + */ + @Override + public int size() { + return size; + } + + /** + * Gets an iterator over the list. + * + * @return an iterator over the list + */ + @Override + public Iterator iterator() { + // override to go 75% faster + return listIterator(0); + } + + /** + * Gets a ListIterator over the list. + * + * @return the new iterator + */ + @Override + public ListIterator listIterator() { + // override to go 75% faster + return listIterator(0); + } + + /** + * Gets a ListIterator over the list. + * + * @param fromIndex the index to start from + * @return the new iterator + */ + @Override + public ListIterator listIterator(final int fromIndex) { + // override to go 75% faster + // cannot use EmptyIterator as iterator.add() must work + checkInterval(fromIndex, 0, size()); + return new TreeListIterator(this, fromIndex); + } + + /** + * Converts the list into an array. + * + * @return the list as an array + */ + @Override + public Object[] toArray() { + // override to go 20% faster + final Object[] array = new Object[size()]; + if (root != null) { + root.toArray(array, root.relativePosition); + } + return array; + } + + @Override + public boolean add(E e) { + if (!canAdd(e)) { + return false; + } + return super.add(e); + } + + /** + * Adds a new element to the list. + * + * @param index the index to add before + * @param obj the element to add + */ + @Override + public void add(final int index, final E obj) { + if (!canAdd(obj)) { + return; + } + modCount++; + checkInterval(index, 0, size()); + if (root == null) { + setRoot(new AVLNode(index, obj, null, null, null)); + } else { + setRoot(root.insert(index, obj)); + } + size++; + } + + /** + * 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 E set(final int index, final E obj) { + final AVLNode node = getNode(index); + final E 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 E remove(final int index) { + modCount++; + checkInterval(index, 0, size() - 1); + final E result = get(index); + setRoot(root.remove(index)); + size--; + return result; + } + + /** + * Removes the element at the specified index. + * + * @param o element to be removed from this list, if present + * @return true if this list contained the specified element + */ + @Override + public boolean remove(Object o) { + // Some optimization can be done here + int index = indexOf(o); + if (index < 0) { + return false; + } + remove(index); + return true; + } + + /** + * Clears the list, removing all entries. + */ + @Override + public void clear() { + modCount++; + root = null; + size = 0; + } + + + /** + * Creates a {@link Spliterator} over the elements in this list. + */ + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.ORDERED); + } + + /** + * Get node by it's index + * @param index index + * @return node + */ + private AVLNode getNode(final int index) { + checkInterval(index, 0, size() - 1); + return root.get(index); + } + + /** + * Set root node. + * @param node new root node + */ + private void setRoot(AVLNode node) { + root = node; + if (node != null) { + node.parent = null; + } + } + + /** + * Check if object can be added to list (e.g. check uniquess) + */ + abstract protected boolean canAdd(E e); + + /** + * Add node to nodeMap. + */ + abstract protected void addNode(AVLNode node); + + /** + * Remove node from nodeMap. + */ + abstract protected void removeNode(AVLNode node); + + //----------------------------------------------------------------------- + /** + * 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(final int index, final int startIndex, final int endIndex) { + if (index < startIndex || index > endIndex) { + throw new IndexOutOfBoundsException("Invalid index:" + index + ", size=" + size()); + } + } + + /** + * Used for tests. + */ + void assertConsistent() { + if (root == null) { + assert(size() == 0); + } else { + assert(size() == root.countNodes()); + } + } + + + //----------------------------------------------------------------------- + /** + * Implements an AVLNode which keeps the offset updated. + *

+ * This node contains the real work. + * TreeList is just there to implement {@link 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. + *

+ * 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). + */ + class AVLNode { + /** Parent node */ + private AVLNode parent; + /** The left child node or the predecessor if {@link #leftIsPrevious}.*/ + private AVLNode 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 AVLNode 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. */ + private E value; + + /** + * 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 AVLNode(final int relativePosition, final E obj, + final AVLNode parent, final AVLNode rightFollower, final AVLNode leftFollower) { + this.relativePosition = relativePosition; + this.rightIsNext = true; + this.leftIsPrevious = true; + this.parent = parent; + setRight(rightFollower); + setLeft(leftFollower); + setValue(obj); + } + + /** + * Gets the value. + * + * @return the value of this node + */ + E getValue() { + return value; + } + + /** + * Sets the value. + * + * @param obj the value to store + */ + void setValue(final E obj) { + if (this.value != null) { + removeNode(this); + } + this.value = obj; + addNode(this); + } + + /** + * Locate the element with the given index relative to the + * offset of the parent of this node. + */ + AVLNode get(final int index) { + final int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe == 0) { + return this; + } + + final AVLNode nextNode = indexRelativeToMe < 0 ? getLeftSubTree() : getRightSubTree(); + if (nextNode == null) { + return null; + } + return nextNode.get(indexRelativeToMe); + } + + /** + * Get position of this node. + */ + int getPosition() { + int position = 0; + AVLNode node = this; + while (node != null) { + position += node.relativePosition; + node = node.parent; + } + return position; + } + + /** + * 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(final Object[] array, final 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 + */ + AVLNode next() { + if (rightIsNext || right == null) { + return right; + } + return right.min(); + } + + /** + * Gets the node in the list before this one. + * + * @return the previous node + */ + AVLNode 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. + */ + AVLNode insert(final int index, final E obj) { + final int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe <= 0) { + return insertOnLeft(indexRelativeToMe, obj); + } + return insertOnRight(indexRelativeToMe, obj); + } + + private AVLNode insertOnLeft(final int indexRelativeToMe, final E obj) { + if (relativePosition >= 0) { + relativePosition++; + } + if (getLeftSubTree() == null) { + setLeft(new AVLNode(-1, obj, this, this, left), null); + } else { + setLeft(left.insert(indexRelativeToMe, obj), null); + } + final AVLNode ret = balance(); + recalcHeight(); + return ret; + } + + private AVLNode insertOnRight(final int indexRelativeToMe, final E obj) { + if (relativePosition < 0) { + relativePosition--; + } + if (getRightSubTree() == null) { + setRight(new AVLNode(+1, obj, this, right, this), null); + } else { + setRight(right.insert(indexRelativeToMe, obj), null); + } + final AVLNode ret = balance(); + recalcHeight(); + return ret; + } + + //----------------------------------------------------------------------- + /** + * Gets the left node, returning null if its a faedelung. + */ + private AVLNode getLeftSubTree() { + return leftIsPrevious ? null : left; + } + + /** + * Gets the right node, returning null if its a faedelung. + */ + private AVLNode getRightSubTree() { + return rightIsNext ? null : right; + } + + /** + * Gets the rightmost child of this node. + * + * @return the rightmost child (greatest index) + */ + private AVLNode max() { + return getRightSubTree() == null ? this : right.max(); + } + + /** + * Gets the leftmost child of this node. + * + * @return the leftmost child (smallest index) + */ + private AVLNode 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. + */ + AVLNode remove(final int index) { + final int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe == 0) { + return removeSelf(true); + } + 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 AVLNode removeMax() { + if (getRightSubTree() == null) { + return removeSelf(false); + } + setRight(right.removeMax(), right.right); + if (relativePosition < 0) { + relativePosition++; + } + recalcHeight(); + return balance(); + } + + private AVLNode removeMin() { + if (getLeftSubTree() == null) { + return removeSelf(false); + } + 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 AVLNode removeSelf(boolean removeValue) { + removeNode(this); + if (removeValue) { + // avoid further calling removeNode(this) when value is overwritten + value = null; + } + 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 + final AVLNode rightMin = right.min(); + if (leftIsPrevious) { + // WARN: This line is not covered by tests. I'm not sure if it's possible to reach this line somehow. + // Original TreeList has the same issue. + setLeft(rightMin.left); + } + setRight(right.removeMin()); + if (relativePosition < 0) { + relativePosition++; + } + setValue(rightMin.value); + } else { + // more on the left or equal, so delete from the left + final AVLNode leftMax = left.max(); + if (rightIsNext) { + // WARN: This line is not covered by tests. I'm not sure if it's possible to reach this line somehow. + // Original TreeList has the same issue. + setRight(leftMax.right); + } + final AVLNode leftPrevious = left.left; + setLeft(left.removeMax()); + if (left == null) { + // special case where left that was deleted was a double link + // only occurs when height difference is equal + leftIsPrevious = true; + setLeft(leftPrevious); + } + if (relativePosition > 0) { + relativePosition--; + } + setValue(leftMax.value); + } + recalcHeight(); + return this; + } + + //----------------------------------------------------------------------- + /** + * Balances according to the AVL algorithm. + */ + private AVLNode 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(final AVLNode node) { + if (node == null) { + return 0; + } + return node.relativePosition; + } + + /** + * Sets the relative position. + */ + private int setOffset(final AVLNode node, final int newOffest) { + if (node == null) { + return 0; + } + final 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(final AVLNode node) { + return node == null ? -1 : node.height; + } + + /** + * Returns the height difference right - left + */ + private int heightRightMinusLeft() { + return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); + } + + private AVLNode rotateLeft() { + final AVLNode newTop = right; // can't be faedelung! + final AVLNode movedNode = getRightSubTree().getLeftSubTree(); + + final int newTopPosition = relativePosition + getOffset(newTop); + final int myNewPosition = -newTop.relativePosition; + final int movedPosition = getOffset(newTop) + getOffset(movedNode); + + setRight(movedNode, newTop); + newTop.setLeft(this, null); + + setOffset(newTop, newTopPosition); + setOffset(this, myNewPosition); + setOffset(movedNode, movedPosition); + return newTop; + } + + private AVLNode rotateRight() { + final AVLNode newTop = left; // can't be faedelung + final AVLNode movedNode = getLeftSubTree().getRightSubTree(); + + final int newTopPosition = relativePosition + getOffset(newTop); + final int myNewPosition = -newTop.relativePosition; + final int movedPosition = getOffset(newTop) + getOffset(movedNode); + + setLeft(movedNode, newTop); + 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(final AVLNode node, final AVLNode previous) { + leftIsPrevious = node == null; + setLeft(leftIsPrevious ? previous : node); + recalcHeight(); + } + + /** + * Sets the left field to the node, or the previous node if that is null + * + * @param node the new left subtree node + */ + private void setLeft(final AVLNode node) { + left = node; + if (left != null && !leftIsPrevious) { + left.parent = this; + } + } + + /** + * 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(final AVLNode node, final AVLNode next) { + rightIsNext = node == null; + setRight(rightIsNext ? next : node); + recalcHeight(); + } + + /** + * Sets the right field to the node, or the next node if that is null + * + * @param node the new left subtree node + */ + private void setRight(final AVLNode node) { + right = node; + if (right != null && !rightIsNext) { + right.parent = this; + } + } + + /** + * Used for tests. + */ + private int countNodes() { + int c = 1; + if (!leftIsPrevious && left != null) { + assert(left.parent == this); + c += left.countNodes(); + } + if (!rightIsNext && right != null) { + assert(right.parent == this); + c += right.countNodes(); + } + return c; + } + + /** + * Used for debugging. + */ + @Override + public String toString() { + return new StringBuilder() + .append("AVLNode(") + .append(relativePosition) + .append(',') + .append(left != null) + .append(',') + .append(value) + .append(',') + .append(getRightSubTree() != null) + .append(", faedelung ") + .append(rightIsNext) + .append(" )") + .toString(); + } + } + + /** + * A list iterator over the linked list. + */ + private class TreeListIterator implements ListIterator { // TODO implements ListIterator, OrderedIterator + /** The parent list */ + private final AbstractTreeList parent; + /** + * Cache of the next node that will be returned by {@link #next()}. + */ + private AVLNode next; + /** + * The index of the next node to be returned. + */ + private int nextIndex; + /** + * Cache of the last node that was returned by {@link #next()} + * or {@link #previous()}. + */ + private AVLNode current; + /** + * The index of the last node that was returned. + */ + private int currentIndex; + /** + * The modification count that the list is expected to have. If the list + * doesn't have this count, then a + * {@link ConcurrentModificationException} may be thrown by + * the operations. + */ + private int expectedModCount; + + /** + * Create a ListIterator for a list. + * + * @param parent the parent list + * @param fromIndex the index to start at + */ + protected TreeListIterator(final AbstractTreeList parent, final int fromIndex) throws IndexOutOfBoundsException { + super(); + this.parent = parent; + this.expectedModCount = parent.modCount; + this.next = parent.root == null ? null : parent.root.get(fromIndex); + this.nextIndex = fromIndex; + this.currentIndex = -1; + } + + /** + * Checks the modification count of the list is the value that this + * object expects. + * + * @throws ConcurrentModificationException If the list's modification + * count isn't the value that was expected. + */ + protected void checkModCount() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + public boolean hasNext() { + return nextIndex < parent.size(); + } + + public E next() { + checkModCount(); + if (!hasNext()) { + throw new NoSuchElementException("No element at index " + nextIndex + "."); + } + if (next == null) { + next = parent.root.get(nextIndex); + } + final E value = next.getValue(); + current = next; + currentIndex = nextIndex++; + next = next.next(); + return value; + } + + public boolean hasPrevious() { + return nextIndex > 0; + } + + public E previous() { + checkModCount(); + if (!hasPrevious()) { + throw new NoSuchElementException("Already at start of list."); + } + if (next == null || next.previous() == null) { + next = parent.root.get(nextIndex - 1); + } else { + next = next.previous(); + } + final E value = next.getValue(); + current = next; + currentIndex = --nextIndex; + return value; + } + + public int nextIndex() { + return nextIndex; + } + + public int previousIndex() { + return nextIndex() - 1; + } + + public void remove() { + checkModCount(); + if (currentIndex == -1) { + throw new IllegalStateException(); + } + parent.remove(currentIndex); + if (nextIndex != currentIndex) { + // remove() following next() + nextIndex--; + } + // the AVL node referenced by next may have become stale after a remove + // reset it now: will be retrieved by next call to next()/previous() via nextIndex + next = null; + current = null; + currentIndex = -1; + expectedModCount++; + } + + public void set(final E obj) { + checkModCount(); + if (current == null) { + throw new IllegalStateException(); + } + current.setValue(obj); + } + + public void add(final E obj) { + checkModCount(); + parent.add(nextIndex, obj); + current = null; + currentIndex = -1; + nextIndex++; + expectedModCount++; + } + } + +} diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java new file mode 100644 index 0000000000..10ccf66d55 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.*; +import java.util.function.Function; + +/** + * As a List this data structure stores order of elements and + * provides access by index. It's is optimised for fast insertions, removals + * and searching by any index or object in the list. + * As a Set this data structure stores unique elements only. + *

+ * TreeListSet can be suitable for tasks which requires fast modification in the + * middle of a list and provides fast contains and indexOf. + *

+ * Get by index is O(log n). + * Insert (head, tail, middle) and remove(by index or by value) are O(log n) + * in most cases but can be up to O((log n) ^ 2) in cases when List contains big + * amount of elements equals to each other. Actual complexity is + * O((log n) * (1 + log m)) where m is amount of elements equal to inserted/removed. + * indexOf is O(log n). + * Contains is O(1) or O(log n) depending on Map implementation. + *

+ * Internally it uses Map (HashMap by default) and AVL tree. + * HashMap can be replaced to TreeMap, this will slightly reduce overall performance + * but will eliminate problems with hash collisions and hash table resizing. + * Using TreeMap with custom Comparator will provide indexOf by custom criteria. + * Using IdentityHashMap will provide indexOf by object's identity. + *

+ * Objects equality is checked by Map, so objects should be immutable for Map + * consistency. + *

+ * Code is based on apache common collections TreeList. + * Comparing to TreeList this data structure: + *

    + *
  • Has slightly slower insertion/removing operations, O(log n) in most cases, O((log n) ^ 2) in + * worst cases (if TreeMap is used).
  • + *
  • Requires more memory, however it's still O(n).
  • + *
  • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
  • + *
+ * + * As this implementation is slightly slower, anr require more memory it's recommended to use + * TreeList in cases when no searching is required or TreeListSet in + * cases where unique elements should be stored. + * + * @author Aleksandr Maksymenko + */ +public class IndexedTreeList extends AbstractTreeList { + + private final Comparator NODE_COMPARATOR = Comparator.comparingInt(AVLNode::getPosition); + private final Function> NEW_NODE_TREE_SET = k -> new TreeSet(NODE_COMPARATOR); + + /** Map from element to it's node or nodes */ + protected final Map> nodeMap; + + //----------------------------------------------------------------------- + /** + * Constructs a new empty list. + */ + public IndexedTreeList() { + this(new HashMap<>()); + } + + /** + * Constructs a new empty list. + * @param map Map implementation. It defines how elements would be compared. For example HashMap (by hashcode/equals), + * TreeMap (by compareTo or Comparator), IdentityHashMap (by identity). Specified map should be empty. + */ + public IndexedTreeList(final Map map) { + this.nodeMap = map; + } + + /** + * Constructs a new list that copies the specified collection. + * + * @param coll The collection to copy + * @throws NullPointerException if the collection is null + */ + public IndexedTreeList(final Collection coll) { + this(coll, new HashMap<>()); + } + + /** + * Constructs a new list that copies the specified collection. + * + * @param coll The collection to copy + * @param map Map implementation. It defines how elements would be compared. For example HashMap (by hashcode/equals), + * TreeMap (by compareTo or Comparator), IdentityHashMap (by identity). Specified map should be empty. + * @throws NullPointerException if the collection is null + */ + public IndexedTreeList(final Collection coll, final Map map) { + this.nodeMap = map; + for (E e : coll) { + add(e); + } + } + + //----------------------------------------------------------------------- + + /** + * Searches for the index of an object in the list. + * + * @param object the object to search + * @return the index of the object, -1 if not found + */ + @Override + public int indexOf(final Object object) { + TreeSet nodes = nodeMap.get(object); + if (nodes == null || nodes.isEmpty()) { + return -1; + } + return nodes.first().getPosition(); + } + + /** + * Searches for the last index of an object in the list. + * + * @param object the object to search + * @return the index of the object, -1 if not found + */ + @Override + public int lastIndexOf(final Object object) { + TreeSet nodes = nodeMap.get(object); + if (nodes == null || nodes.isEmpty()) { + return -1; + } + return nodes.last().getPosition(); + } + + /** + * Searches for all indexes of an objects in the list equals to specified object. + * + * @param object the object to search + * @return array of indexes of the objects + */ + public int[] indexes(final Object object) { + TreeSet nodes = nodeMap.get(object); + if (nodes == null || nodes.isEmpty()) { + return new int[0]; + } + int[] indexes = new int[nodes.size()]; + int i = 0; + for (AVLNode node : nodes) { + indexes[i++] = node.getPosition(); + } + return indexes; + } + + /** + * Get amount of objects in the list equals to specified object. + * + * @param object the object to search + * @return amount of objects + */ + public int count(final Object object) { + TreeSet nodes = nodeMap.get(object); + if (nodes == null || nodes.isEmpty()) { + return 0; + } + return nodes.size(); + } + + /** + * Searches for the presence of an object in the list. + * + * @param object the object to check + * @return true if the object is found + */ + @Override + public boolean contains(final Object object) { + return nodeMap.containsKey(object); + } + + /** + * Clears the list, removing all entries. + */ + @Override + public void clear() { + super.clear(); + nodeMap.clear(); + } + + /** + * Get unordered Set o unique values. + */ + public Set uniqueValues() { + return nodeMap.keySet(); + } + + /** + * Check if set does not contains an object. + */ + @Override + protected boolean canAdd(E e) { + if (e == null) { + throw new NullPointerException("Null elements are not allowed"); + } + return true; + } + + /** + * Add node to nodeMap. + */ + @Override + protected void addNode(AVLNode node) { + nodeMap.computeIfAbsent(node.getValue(), NEW_NODE_TREE_SET).add(node); + } + + /** + * Remove node from nodeMap. + */ + @Override + protected void removeNode(AVLNode node) { + TreeSet nodes = nodeMap.remove(node.getValue()); + if (nodes == null) { + return; + } + nodes.remove(node); + if (!nodes.isEmpty()) { + nodeMap.put(nodes.first().getValue(), nodes); + } + } + +} diff --git a/src/main/java/org/apache/commons/collections4/list/TreeListSet.java b/src/main/java/org/apache/commons/collections4/list/TreeListSet.java new file mode 100644 index 0000000000..0a49122bfc --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/list/TreeListSet.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Data structure which implements both List and Set. + *

+ * As a List this data structure stores order of elements and + * provides access by index. It's is optimised for fast insertions, removals + * and searching by any index or object in the list. + * As a Set this data structure stores unique elements only. + *

+ * TreeListSet can be suitable for tasks which requires fast modification in the + * middle of a list and provides fast contains and indexOf. + *

+ * Get by index, insert (head, tail, middle), remove(by index or by value) + * and indexOf are all O(log n). Contains is O(1) or O(log n) depending on Map + * implementation. + *

+ * Internally it uses Map (HashMap by default) and AVL tree. + * HashMap can be replaced to TreeMap, this will slightly reduce overall performance + * but will eliminate problems with hash collisions and hash table resizing. + *

+ * Objects equality is checked by Map, so objects should be immutable for Map + * consistency. + *

+ * Code is based on apache common collections TreeList. + * Comparing to TreeList this data structure: + *

    + *
  • Contains unique elements
  • + *
  • Has almost the same or slightly slower insertion/removing operations, O(log n)
  • + *
  • Requires more memory, however it's still O(n).
  • + *
  • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
  • + *
+ * + * @author Aleksandr Maksymenko + */ +public class TreeListSet extends AbstractTreeList implements Set { + + /** Map from element to it's node or nodes */ + protected final Map nodeMap; + + //----------------------------------------------------------------------- + /** + * Constructs a new empty list. + */ + public TreeListSet() { + this(new HashMap<>()); + } + + /** + * Constructs a new empty list. + * @param map Map implementation. It defines how elements would be compared. For example HashMap (by hashcode/equals), + * TreeMap (by compareTo or Comparator), IdentityHashMap (by identity). Specified map should be empty. + */ + public TreeListSet(final Map map) { + this.nodeMap = map; + } + + /** + * Constructs a new list that copies the specified collection. + * + * @param coll The collection to copy + * @throws NullPointerException if the collection is null + */ + public TreeListSet(final Collection coll) { + this(coll, new HashMap<>()); + } + + /** + * Constructs a new list that copies the specified collection. + * + * @param coll The collection to copy + * @param map Map implementation. It defines how elements would be compared. For example HashMap (by hashcode/equals), + * TreeMap (by compareTo or Comparator), IdentityHashMap (by identity). Specified map should be empty. + * @throws NullPointerException if the collection is null + */ + public TreeListSet(final Collection coll, final Map map) { + this.nodeMap = map; + for (E e : coll) { + add(e); + } + } + + //----------------------------------------------------------------------- + + /** + * Searches for the index of an object in the list. + * + * @param object the object to search + * @return the index of the object, -1 if not found + */ + @Override + public int indexOf(final Object object) { + AVLNode node = nodeMap.get(object); + if (node == null) { + return -1; + } + return node.getPosition(); + } + + /** + * Searches for the last index of an object in the list. + * + * @param object the object to search + * @return the index of the object, -1 if not found + */ + @Override + public int lastIndexOf(final Object object) { + return indexOf(object); + } + + /** + * Searches for the presence of an object in the list. + * + * @param object the object to check + * @return true if the object is found + */ + @Override + public boolean contains(final Object object) { + return nodeMap.containsKey(object); + } + + /** + * Clears the list, removing all entries. + */ + @Override + public void clear() { + super.clear(); + nodeMap.clear(); + } + + /** + * Check if set does not contains an object. + */ + @Override + protected boolean canAdd(E e) { + if (e == null) { + throw new NullPointerException("Null elements are not allowed"); + } + return !nodeMap.containsKey(e); + } + + /** + * Add node to nodeMap. + */ + @Override + protected void addNode(AVLNode node) { + nodeMap.put(node.getValue(), node); + } + + /** + * Remove node from nodeMap. + */ + @Override + protected void removeNode(AVLNode node) { + nodeMap.remove(node.getValue()); + } + +} diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListTest.java new file mode 100644 index 0000000000..4a622e2e13 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListTest.java @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import junit.framework.Test; +import org.apache.commons.collections4.BulkTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * JUnit tests + * + * @since 3.1 + */ +public class IndexedTreeListTest extends AbstractListTest { + + public IndexedTreeListTest(final String name) { + super(name); + } + +// public static void main(String[] args) { +// junit.textui.TestRunner.run(suite()); +// System.out.println(" add; toArray; iterator; insert; get; indexOf; remove"); +// System.out.print(" IndexedTreeList = "); +// benchmark(new IndexedTreeList()); +// System.out.print("\n ArrayList = "); +// benchmark(new java.util.ArrayList()); +// System.out.print("\n LinkedList = "); +// benchmark(new java.util.LinkedList()); +// System.out.print("\n NodeCachingLinkedList = "); +// benchmark(new NodeCachingLinkedList()); +// } + + public static Test suite() { + return BulkTest.makeSuite(IndexedTreeListTest.class); + } + + public static void benchmark(final List l) { + long start = System.currentTimeMillis(); + for (int i = 0; i < 100000; i++) { + l.add(Integer.valueOf(i)); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 200; i++) { + l.toArray(); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 100; i++) { + final java.util.Iterator it = l.iterator(); + while (it.hasNext()) { + it.next(); + } + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + final int j = (int) (Math.random() * 100000); + l.add(j, Integer.valueOf(-j)); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 50000; i++) { + final int j = (int) (Math.random() * 110000); + l.get(j); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 200; i++) { + final int j = (int) (Math.random() * 100000); + l.indexOf(Integer.valueOf(j)); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + final int j = (int) (Math.random() * 100000); + l.remove(j); + } + System.out.print(System.currentTimeMillis() - start + ";"); + } + + //----------------------------------------------------------------------- + @Override + public IndexedTreeList makeObject() { + return new IndexedTreeList<>(); + } + + @Override + public boolean isNullSupported() { + return false; + } + + //----------------------------------------------------------------------- + @SuppressWarnings("unchecked") + public void testAddMultiple() { + final List l = makeObject(); + l.add((E) "hugo"); + l.add((E) "erna"); + l.add((E) "daniel"); + l.add((E) "andres"); + l.add(0, (E) "harald"); + assertEquals("harald", l.get(0)); + assertEquals("hugo", l.get(1)); + assertEquals("erna", l.get(2)); + assertEquals("daniel", l.get(3)); + assertEquals("andres", l.get(4)); + } + + @SuppressWarnings("unchecked") + public void testRemove() { + final List l = makeObject(); + l.add((E) "hugo"); + l.add((E) "erna"); + l.add((E) "daniel"); + l.add((E) "andres"); + l.add(0, (E) "harald"); + int i = 0; + assertEquals("harald", l.get(i++)); + assertEquals("hugo", l.get(i++)); + assertEquals("erna", l.get(i++)); + assertEquals("daniel", l.get(i++)); + assertEquals("andres", l.get(i++)); + + l.remove(0); + i = 0; + assertEquals("hugo", l.get(i++)); + assertEquals("erna", l.get(i++)); + assertEquals("daniel", l.get(i++)); + assertEquals("andres", l.get(i++)); + + i = 0; + l.remove(1); + assertEquals("hugo", l.get(i++)); + assertEquals("daniel", l.get(i++)); + assertEquals("andres", l.get(i++)); + + i = 0; + l.remove(2); + assertEquals("hugo", l.get(i++)); + assertEquals("daniel", l.get(i++)); + } + + @SuppressWarnings("unchecked") + public void testInsertBefore() { + final List l = makeObject(); + l.add((E) "erna"); + l.add(0, (E) "hugo"); + assertEquals("hugo", l.get(0)); + assertEquals("erna", l.get(1)); + } + + @SuppressWarnings("unchecked") + public void testIndexOf() { + final List l = makeObject(); + l.add((E) "0"); + l.add((E) "1"); + l.add((E) "2"); + l.add((E) "3"); + l.add((E) "4"); + l.add((E) "5"); + l.add((E) "6"); + assertEquals(0, l.indexOf("0")); + assertEquals(1, l.indexOf("1")); + assertEquals(2, l.indexOf("2")); + assertEquals(3, l.indexOf("3")); + assertEquals(4, l.indexOf("4")); + assertEquals(5, l.indexOf("5")); + assertEquals(6, l.indexOf("6")); + + l.set(1, (E) "0"); + assertEquals(0, l.indexOf("0")); + + l.set(3, (E) "3"); + assertEquals(3, l.indexOf("3")); + l.set(2, (E) "3"); + assertEquals(2, l.indexOf("3")); + l.set(1, (E) "3"); + assertEquals(1, l.indexOf("3")); + l.set(0, (E) "3"); + assertEquals(0, l.indexOf("3")); + } + +// public void testCheck() { +// List l = makeEmptyList(); +// l.add("A1"); +// l.add("A2"); +// l.add("A3"); +// l.add("A4"); +// l.add("A5"); +// l.add("A6"); +// } + + public void testBug35258() { + final Object objectToRemove = Integer.valueOf(3); + + final List IndexedTreeList = new IndexedTreeList<>(); + IndexedTreeList.add(Integer.valueOf(0)); + IndexedTreeList.add(Integer.valueOf(1)); + IndexedTreeList.add(Integer.valueOf(2)); + IndexedTreeList.add(Integer.valueOf(3)); + IndexedTreeList.add(Integer.valueOf(4)); + + // this cause inconsistence of ListIterator() + IndexedTreeList.remove(objectToRemove); + + final ListIterator li = IndexedTreeList.listIterator(); + assertEquals(Integer.valueOf(0), li.next()); + assertEquals(Integer.valueOf(0), li.previous()); + assertEquals(Integer.valueOf(0), li.next()); + assertEquals(Integer.valueOf(1), li.next()); + // this caused error in bug 35258 + assertEquals(Integer.valueOf(1), li.previous()); + assertEquals(Integer.valueOf(1), li.next()); + assertEquals(Integer.valueOf(2), li.next()); + assertEquals(Integer.valueOf(2), li.previous()); + assertEquals(Integer.valueOf(2), li.next()); + assertEquals(Integer.valueOf(4), li.next()); + assertEquals(Integer.valueOf(4), li.previous()); + assertEquals(Integer.valueOf(4), li.next()); + assertEquals(false, li.hasNext()); + } + + public void testBugCollections447() { + final List IndexedTreeList = new IndexedTreeList<>(); + IndexedTreeList.add("A"); + IndexedTreeList.add("B"); + IndexedTreeList.add("C"); + IndexedTreeList.add("D"); + + final ListIterator li = IndexedTreeList.listIterator(); + assertEquals("A", li.next()); + assertEquals("B", li.next()); + + assertEquals("B", li.previous()); + + li.remove(); // Deletes "B" + + // previous() after remove() should move to + // the element before the one just removed + assertEquals("A", li.previous()); + } + + @SuppressWarnings("boxing") // OK in test code + public void testIterationOrder() { + // COLLECTIONS-433: + // ensure that the iteration order of elements is correct + // when initializing the IndexedTreeList with another collection + + for (int size = 1; size < 1000; size++) { + final List other = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + other.add(i); + } + final IndexedTreeList l = new IndexedTreeList<>(other); + final ListIterator it = l.listIterator(); + int i = 0; + while (it.hasNext()) { + final Integer val = it.next(); + assertEquals(i++, val.intValue()); + } + + while (it.hasPrevious()) { + final Integer val = it.previous(); + assertEquals(--i, val.intValue()); + } + } + } + + @SuppressWarnings("boxing") // OK in test code + public void testIterationOrderAfterAddAll() { + // COLLECTIONS-433: + // ensure that the iteration order of elements is correct + // when calling addAll on the IndexedTreeList + + // to simulate different cases in addAll, do different runs where + // the number of elements already in the list and being added by addAll differ + + final int size = 1000; + for (int i = 0; i < 100; i++) { + final List other = new ArrayList<>(size); + for (int j = i; j < size; j++) { + other.add(j); + } + final IndexedTreeList l = new IndexedTreeList<>(); + for (int j = 0; j < i; j++) { + l.add(j); + } + + l.addAll(other); + + final ListIterator it = l.listIterator(); + int cnt = 0; + while (it.hasNext()) { + final Integer val = it.next(); + assertEquals(cnt++, val.intValue()); + } + + while (it.hasPrevious()) { + final Integer val = it.previous(); + assertEquals(--cnt, val.intValue()); + } + } + } + +} diff --git a/src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java b/src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java new file mode 100644 index 0000000000..66388f1e7b --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java @@ -0,0 +1,418 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.collections4.list; + +import junit.framework.Test; +import org.apache.commons.collections4.BulkTest; + +import java.util.*; + +/** + * JUnit tests + * + * @since 3.1 + */ +public class TreeListSetTest extends AbstractListTest { + + public TreeListSetTest(final String name) { + super(name); + } + +// public static void main(String[] args) { +// junit.textui.TestRunner.run(suite()); +// System.out.println(" add; toArray; iterator; insert; get; indexOf; remove"); +// System.out.print(" TreeListSet = "); +// benchmark(new TreeListSet()); +// System.out.print("\n ArrayList = "); +// benchmark(new java.util.ArrayList()); +// System.out.print("\n LinkedList = "); +// benchmark(new java.util.LinkedList()); +// System.out.print("\n NodeCachingLinkedList = "); +// benchmark(new NodeCachingLinkedList()); +// } + + public static Test suite() { + return BulkTest.makeSuite(TreeListSetTest.class); + } + + public static void benchmark(final List l) { + long start = System.currentTimeMillis(); + for (int i = 0; i < 100000; i++) { + l.add(Integer.valueOf(i)); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 200; i++) { + l.toArray(); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 100; i++) { + final java.util.Iterator it = l.iterator(); + while (it.hasNext()) { + it.next(); + } + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + final int j = (int) (Math.random() * 100000); + l.add(j, Integer.valueOf(-j)); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 50000; i++) { + final int j = (int) (Math.random() * 110000); + l.get(j); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 200; i++) { + final int j = (int) (Math.random() * 100000); + l.indexOf(Integer.valueOf(j)); + } + System.out.print(System.currentTimeMillis() - start + ";"); + + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + final int j = (int) (Math.random() * 100000); + l.remove(j); + } + System.out.print(System.currentTimeMillis() - start + ";"); + } + + //----------------------------------------------------------------------- + @Override + public TreeListSet makeObject() { + return new TreeListSet<>(); + } + + @Override + public boolean isNullSupported() { + return false; + } + + @Override + @SuppressWarnings("unchecked") + public E[] getFullNonNullElements() { + // override to avoid duplicate "One" + return (E[]) new Object[] { + new String(""), + new String("One"), + Integer.valueOf(2), + "Three", + Integer.valueOf(4), + new Double(5), + new Float(6), + "Seven", + "Eight", + new String("Nine"), + Integer.valueOf(10), + new Short((short)11), + new Long(12), + "Thirteen", + "14", + "15", + new Byte((byte)16) + }; + } + + @Override + public void testListIteratorAdd() { + // override to cope with Set behaviour + resetEmpty(); + final List list1 = getCollection(); + final List list2 = getConfirmed(); + + final E[] elements = getOtherElements(); // changed here + ListIterator iter1 = list1.listIterator(); + ListIterator iter2 = list2.listIterator(); + + for (final E element : elements) { + iter1.add(element); + iter2.add(element); + super.verify(); // changed here + } + + resetFull(); + iter1 = getCollection().listIterator(); + iter2 = getConfirmed().listIterator(); + for (final E element : elements) { + iter1.next(); + iter2.next(); + iter1.add(element); + iter2.add(element); + super.verify(); // changed here + } + } + + //----------------------------------------------------------------------- + @SuppressWarnings("unchecked") + public void testAddMultiple() { + final List l = makeObject(); + l.add((E) "hugo"); + l.add((E) "erna"); + l.add((E) "daniel"); + l.add((E) "andres"); + l.add(0, (E) "harald"); + assertEquals("harald", l.get(0)); + assertEquals("hugo", l.get(1)); + assertEquals("erna", l.get(2)); + assertEquals("daniel", l.get(3)); + assertEquals("andres", l.get(4)); + } + + @SuppressWarnings("unchecked") + public void testRemove() { + final List l = makeObject(); + l.add((E) "hugo"); + l.add((E) "erna"); + l.add((E) "daniel"); + l.add((E) "andres"); + l.add(0, (E) "harald"); + int i = 0; + assertEquals("harald", l.get(i++)); + assertEquals("hugo", l.get(i++)); + assertEquals("erna", l.get(i++)); + assertEquals("daniel", l.get(i++)); + assertEquals("andres", l.get(i++)); + + l.remove(0); + i = 0; + assertEquals("hugo", l.get(i++)); + assertEquals("erna", l.get(i++)); + assertEquals("daniel", l.get(i++)); + assertEquals("andres", l.get(i++)); + + i = 0; + l.remove(1); + assertEquals("hugo", l.get(i++)); + assertEquals("daniel", l.get(i++)); + assertEquals("andres", l.get(i++)); + + i = 0; + l.remove(2); + assertEquals("hugo", l.get(i++)); + assertEquals("daniel", l.get(i++)); + } + + @SuppressWarnings("unchecked") + public void testInsertBefore() { + final List l = makeObject(); + l.add((E) "erna"); + l.add(0, (E) "hugo"); + assertEquals("hugo", l.get(0)); + assertEquals("erna", l.get(1)); + } + + @SuppressWarnings("unchecked") + public void testIndexOf() { + final List l = makeObject(); + l.add((E) "0"); + l.add((E) "1"); + l.add((E) "2"); + l.add((E) "3"); + l.add((E) "4"); + l.add((E) "5"); + l.add((E) "6"); + assertEquals(0, l.indexOf("0")); + assertEquals(1, l.indexOf("1")); + assertEquals(2, l.indexOf("2")); + assertEquals(3, l.indexOf("3")); + assertEquals(4, l.indexOf("4")); + assertEquals(5, l.indexOf("5")); + assertEquals(6, l.indexOf("6")); + + l.set(1, (E) "0"); + assertEquals(0, l.indexOf("0")); + + l.set(3, (E) "3"); + assertEquals(3, l.indexOf("3")); + l.set(2, (E) "3"); + assertEquals(2, l.indexOf("3")); + l.set(1, (E) "3"); + assertEquals(1, l.indexOf("3")); + l.set(0, (E) "3"); + assertEquals(0, l.indexOf("3")); + } + + @SuppressWarnings("unchecked") + public void testAddAll() { + final TreeListSet lset = new TreeListSet<>(); + + lset.addAll(Arrays.asList((E[]) new Integer[] { Integer.valueOf(1), Integer.valueOf(1)})); + + assertEquals("Duplicate element was added.", 1, lset.size()); + } + + @Override + public void testCollectionAddAll() { + // override for set behaviour + resetEmpty(); + E[] elements = getFullElements(); + boolean r = getCollection().addAll(Arrays.asList(elements)); + getConfirmed().addAll(Arrays.asList(elements)); + verify(); + assertTrue("Empty collection should change after addAll", r); + for (final E element : elements) { + assertTrue("Collection should contain added element", + getCollection().contains(element)); + } + + resetFull(); + final int size = getCollection().size(); + elements = getOtherElements(); + r = getCollection().addAll(Arrays.asList(elements)); + getConfirmed().addAll(Arrays.asList(elements)); + verify(); + assertTrue("Full collection should change after addAll", r); + for (int i = 0; i < elements.length; i++) { + assertTrue("Full collection should contain added element " + i, + getCollection().contains(elements[i])); + } + assertEquals("Size should increase after addAll", + size + elements.length, getCollection().size()); + } + +// public void testCheck() { +// List l = makeEmptyList(); +// l.add("A1"); +// l.add("A2"); +// l.add("A3"); +// l.add("A4"); +// l.add("A5"); +// l.add("A6"); +// } + + public void testBug35258() { + final Object objectToRemove = Integer.valueOf(3); + + final List TreeListSet = new TreeListSet<>(); + TreeListSet.add(Integer.valueOf(0)); + TreeListSet.add(Integer.valueOf(1)); + TreeListSet.add(Integer.valueOf(2)); + TreeListSet.add(Integer.valueOf(3)); + TreeListSet.add(Integer.valueOf(4)); + + // this cause inconsistence of ListIterator() + TreeListSet.remove(objectToRemove); + + final ListIterator li = TreeListSet.listIterator(); + assertEquals(Integer.valueOf(0), li.next()); + assertEquals(Integer.valueOf(0), li.previous()); + assertEquals(Integer.valueOf(0), li.next()); + assertEquals(Integer.valueOf(1), li.next()); + // this caused error in bug 35258 + assertEquals(Integer.valueOf(1), li.previous()); + assertEquals(Integer.valueOf(1), li.next()); + assertEquals(Integer.valueOf(2), li.next()); + assertEquals(Integer.valueOf(2), li.previous()); + assertEquals(Integer.valueOf(2), li.next()); + assertEquals(Integer.valueOf(4), li.next()); + assertEquals(Integer.valueOf(4), li.previous()); + assertEquals(Integer.valueOf(4), li.next()); + assertEquals(false, li.hasNext()); + } + + public void testBugCollections447() { + final List TreeListSet = new TreeListSet<>(); + TreeListSet.add("A"); + TreeListSet.add("B"); + TreeListSet.add("C"); + TreeListSet.add("D"); + + final ListIterator li = TreeListSet.listIterator(); + assertEquals("A", li.next()); + assertEquals("B", li.next()); + + assertEquals("B", li.previous()); + + li.remove(); // Deletes "B" + + // previous() after remove() should move to + // the element before the one just removed + assertEquals("A", li.previous()); + } + + @SuppressWarnings("boxing") // OK in test code + public void testIterationOrder() { + // COLLECTIONS-433: + // ensure that the iteration order of elements is correct + // when initializing the TreeListSet with another collection + + for (int size = 1; size < 1000; size++) { + final List other = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + other.add(i); + } + final TreeListSet l = new TreeListSet<>(other); + final ListIterator it = l.listIterator(); + int i = 0; + while (it.hasNext()) { + final Integer val = it.next(); + assertEquals(i++, val.intValue()); + } + + while (it.hasPrevious()) { + final Integer val = it.previous(); + assertEquals(--i, val.intValue()); + } + } + } + + @SuppressWarnings("boxing") // OK in test code + public void testIterationOrderAfterAddAll() { + // COLLECTIONS-433: + // ensure that the iteration order of elements is correct + // when calling addAll on the TreeListSet + + // to simulate different cases in addAll, do different runs where + // the number of elements already in the list and being added by addAll differ + + final int size = 1000; + for (int i = 0; i < 100; i++) { + final List other = new ArrayList<>(size); + for (int j = i; j < size; j++) { + other.add(j); + } + final TreeListSet l = new TreeListSet<>(); + for (int j = 0; j < i; j++) { + l.add(j); + } + + l.addAll(other); + + final ListIterator it = l.listIterator(); + int cnt = 0; + while (it.hasNext()) { + final Integer val = it.next(); + assertEquals(cnt++, val.intValue()); + } + + while (it.hasPrevious()) { + final Integer val = it.previous(); + assertEquals(--cnt, val.intValue()); + } + } + } + +} From f6830f38b3add233b31d87867ab7ad84f32f7dfe Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 00:10:28 +0200 Subject: [PATCH 02/12] Collections renamed --- ...List.java => AbstractIndexedTreeList.java} | 28 +++- .../collections4/list/IndexedTreeList.java | 11 +- ...eeListSet.java => IndexedTreeListSet.java} | 39 ++++- ...tTest.java => IndexedTreeListSetTest.java} | 133 ++++++++++++------ 4 files changed, 149 insertions(+), 62 deletions(-) rename src/main/java/org/apache/commons/collections4/list/{AbstractTreeList.java => AbstractIndexedTreeList.java} (95%) rename src/main/java/org/apache/commons/collections4/list/{TreeListSet.java => IndexedTreeListSet.java} (80%) rename src/test/java/org/apache/commons/collections4/list/{TreeListSetTest.java => IndexedTreeListSetTest.java} (80%) diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java similarity index 95% rename from src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java rename to src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java index 7243512407..4d14597f63 100644 --- a/src/main/java/org/apache/commons/collections4/list/AbstractTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java @@ -19,13 +19,13 @@ import java.util.*; /** - * Common class for indexable tree lists. + * Common class for indexed tree lists. * * Code is based on apache common collections TreeList. * * @author Aleksandr Maksymenko */ -abstract class AbstractTreeList extends AbstractList { +abstract class AbstractIndexedTreeList extends AbstractList { /** The root node in the AVL tree */ protected AVLNode root; @@ -33,6 +33,14 @@ abstract class AbstractTreeList extends AbstractList { /** Size of a List */ protected int size = 0; + /** + * Methods set(obj) and add(obj) in ListIterator can't be implemented to satisfy specification in IndexedTreeListSet. + * So these methods are disabled by default and throws UnsupportedOperationException. + * It's possible to enable this feature but in this case exception may be thrown if someone tries to add/set + * an element which is already in a collection. + */ + protected boolean supportAddSetInIterator = true; + //----------------------------------------------------------------------- /** * Gets the element at the specified index. @@ -796,7 +804,7 @@ public String toString() { */ private class TreeListIterator implements ListIterator { // TODO implements ListIterator, OrderedIterator /** The parent list */ - private final AbstractTreeList parent; + private final AbstractIndexedTreeList parent; /** * Cache of the next node that will be returned by {@link #next()}. */ @@ -828,7 +836,7 @@ private class TreeListIterator implements ListIterator { // TODO implements L * @param parent the parent list * @param fromIndex the index to start at */ - protected TreeListIterator(final AbstractTreeList parent, final int fromIndex) throws IndexOutOfBoundsException { + protected TreeListIterator(final AbstractIndexedTreeList parent, final int fromIndex) throws IndexOutOfBoundsException { super(); this.parent = parent; this.expectedModCount = parent.modCount; @@ -916,15 +924,27 @@ public void remove() { } public void set(final E obj) { + if (!supportAddSetInIterator) { + throw new UnsupportedOperationException("Set operation is not supported"); + } checkModCount(); if (current == null) { throw new IllegalStateException(); } + if (!canAdd(obj)) { + throw new IllegalArgumentException("Unable to set specified element"); + } current.setValue(obj); } public void add(final E obj) { + if (!supportAddSetInIterator) { + throw new UnsupportedOperationException("Add operation is not supported"); + } checkModCount(); + if (!canAdd(obj)) { + throw new IllegalArgumentException("Unable to add specified element"); + } parent.add(nextIndex, obj); current = null; currentIndex = -1; diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java index 10ccf66d55..b0afb60150 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java @@ -23,9 +23,8 @@ * As a List this data structure stores order of elements and * provides access by index. It's is optimised for fast insertions, removals * and searching by any index or object in the list. - * As a Set this data structure stores unique elements only. *

- * TreeListSet can be suitable for tasks which requires fast modification in the + * IndexedTreeList can be suitable for tasks which requires fast modification in the * middle of a list and provides fast contains and indexOf. *

* Get by index is O(log n). @@ -54,13 +53,13 @@ *

  • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
  • * * - * As this implementation is slightly slower, anr require more memory it's recommended to use - * TreeList in cases when no searching is required or TreeListSet in - * cases where unique elements should be stored. + * As this implementation is slightly slower and require more memory it's recommended to use + * TreeList in cases when no searching is required or IndexedTreeListSet + * in cases where unique elements should be stored. * * @author Aleksandr Maksymenko */ -public class IndexedTreeList extends AbstractTreeList { +public class IndexedTreeList extends AbstractIndexedTreeList { private final Comparator NODE_COMPARATOR = Comparator.comparingInt(AVLNode::getPosition); private final Function> NEW_NODE_TREE_SET = k -> new TreeSet(NODE_COMPARATOR); diff --git a/src/main/java/org/apache/commons/collections4/list/TreeListSet.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java similarity index 80% rename from src/main/java/org/apache/commons/collections4/list/TreeListSet.java rename to src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java index 0a49122bfc..497934d99e 100644 --- a/src/main/java/org/apache/commons/collections4/list/TreeListSet.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java @@ -29,8 +29,8 @@ * and searching by any index or object in the list. * As a Set this data structure stores unique elements only. *

    - * TreeListSet can be suitable for tasks which requires fast modification in the - * middle of a list and provides fast contains and indexOf. + * IndexedTreeListSet can be suitable for tasks which requires fast modification + * in the middle of a list and provides fast contains and indexOf operations. *

    * Get by index, insert (head, tail, middle), remove(by index or by value) * and indexOf are all O(log n). Contains is O(1) or O(log n) depending on Map @@ -54,7 +54,9 @@ * * @author Aleksandr Maksymenko */ -public class TreeListSet extends AbstractTreeList implements Set { +public class IndexedTreeListSet extends AbstractIndexedTreeList implements Set { + + private static final boolean SUPPORT_ADD_SET_IN_ITERATOR = false; /** Map from element to it's node or nodes */ protected final Map nodeMap; @@ -63,7 +65,7 @@ public class TreeListSet extends AbstractTreeList implements Set { /** * Constructs a new empty list. */ - public TreeListSet() { + public IndexedTreeListSet() { this(new HashMap<>()); } @@ -72,8 +74,9 @@ public TreeListSet() { * @param map Map implementation. It defines how elements would be compared. For example HashMap (by hashcode/equals), * TreeMap (by compareTo or Comparator), IdentityHashMap (by identity). Specified map should be empty. */ - public TreeListSet(final Map map) { + public IndexedTreeListSet(final Map map) { this.nodeMap = map; + this.supportAddSetInIterator = SUPPORT_ADD_SET_IN_ITERATOR; } /** @@ -82,7 +85,7 @@ public TreeListSet(final Map map) { * @param coll The collection to copy * @throws NullPointerException if the collection is null */ - public TreeListSet(final Collection coll) { + public IndexedTreeListSet(final Collection coll) { this(coll, new HashMap<>()); } @@ -94,8 +97,9 @@ public TreeListSet(final Collection coll) { * TreeMap (by compareTo or Comparator), IdentityHashMap (by identity). Specified map should be empty. * @throws NullPointerException if the collection is null */ - public TreeListSet(final Collection coll, final Map map) { + public IndexedTreeListSet(final Collection coll, final Map map) { this.nodeMap = map; + this.supportAddSetInIterator = SUPPORT_ADD_SET_IN_ITERATOR; for (E e : coll) { add(e); } @@ -140,6 +144,27 @@ public boolean contains(final Object object) { return nodeMap.containsKey(object); } + /** + * Sets the element at the specified index. + * If specified value already exist in Set, it will be removed at old position. + * E.g. if TreeListSet contains ["A", "B", "C"] and set(2, "A") is invoked, then result will be ["B", "A"]. + * + * @param index the index to set + * @param obj the object to store at the specified index + * @return + */ + @Override + public E set(int index, final E obj) { + final int pos = indexOf(obj); + if (pos >= 0 && pos != index) { + remove(pos); + if (pos < index) { + index--; + } + } + return super.set(index, obj); + } + /** * Clears the list, removing all entries. */ diff --git a/src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java similarity index 80% rename from src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java rename to src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java index 66388f1e7b..9e4d12c9bc 100644 --- a/src/test/java/org/apache/commons/collections4/list/TreeListSetTest.java +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java @@ -26,9 +26,9 @@ * * @since 3.1 */ -public class TreeListSetTest extends AbstractListTest { +public class IndexedTreeListSetTest extends AbstractListTest { - public TreeListSetTest(final String name) { + public IndexedTreeListSetTest(final String name) { super(name); } @@ -46,7 +46,7 @@ public TreeListSetTest(final String name) { // } public static Test suite() { - return BulkTest.makeSuite(TreeListSetTest.class); + return BulkTest.makeSuite(IndexedTreeListSetTest.class); } public static void benchmark(final List l) { @@ -102,8 +102,8 @@ public static void benchmark(final List l) { //----------------------------------------------------------------------- @Override - public TreeListSet makeObject() { - return new TreeListSet<>(); + public IndexedTreeListSet makeObject() { + return new IndexedTreeListSet<>(); } @Override @@ -138,31 +138,7 @@ public E[] getFullNonNullElements() { @Override public void testListIteratorAdd() { - // override to cope with Set behaviour - resetEmpty(); - final List list1 = getCollection(); - final List list2 = getConfirmed(); - - final E[] elements = getOtherElements(); // changed here - ListIterator iter1 = list1.listIterator(); - ListIterator iter2 = list2.listIterator(); - - for (final E element : elements) { - iter1.add(element); - iter2.add(element); - super.verify(); // changed here - } - - resetFull(); - iter1 = getCollection().listIterator(); - iter2 = getConfirmed().listIterator(); - for (final E element : elements) { - iter1.next(); - iter2.next(); - iter1.add(element); - iter2.add(element); - super.verify(); // changed here - } + // Does not support ListIterator.add(obj) } //----------------------------------------------------------------------- @@ -241,23 +217,55 @@ public void testIndexOf() { assertEquals(4, l.indexOf("4")); assertEquals(5, l.indexOf("5")); assertEquals(6, l.indexOf("6")); + assertEquals(7, l.size()); - l.set(1, (E) "0"); - assertEquals(0, l.indexOf("0")); + l.set(2, (E) "0"); // Previous "0" at index 0 was removed + assertEquals(1, l.indexOf("0")); + assertEquals(-1, l.indexOf("2")); + assertEquals(0, l.indexOf("1")); + assertEquals(6, l.size()); - l.set(3, (E) "3"); - assertEquals(3, l.indexOf("3")); - l.set(2, (E) "3"); - assertEquals(2, l.indexOf("3")); - l.set(1, (E) "3"); - assertEquals(1, l.indexOf("3")); - l.set(0, (E) "3"); - assertEquals(0, l.indexOf("3")); + l.set(2, (E) "7"); + assertEquals(-1, l.indexOf("3")); + assertEquals(2, l.indexOf("7")); + assertEquals(6, l.size()); + + l.set(2, (E) "5"); // Previous "5" at index 4 was removed + assertEquals(2, l.indexOf("5")); + assertEquals(4, l.indexOf("6")); + assertEquals(5, l.size()); + + l.remove(4); + assertEquals(2, l.indexOf("5")); + + l.remove(1); + assertEquals(1, l.indexOf("5")); + } + + /** + * Test {@link List#set(int,Object)}. + */ + public void testListSetByIndex() { + if (!isSetSupported()) { + return; + } + + resetFull(); + final E[] elements = getFullElements(); + final E[] other = getOtherElements(); + + for (int i = 0; i < other.length; i++) { + final E n = other[i % other.length]; + final E v = getCollection().set(i % elements.length, n); + assertEquals("Set should return correct element", elements[i], v); + getConfirmed().set(i % elements.length, n); + verify(); + } } @SuppressWarnings("unchecked") public void testAddAll() { - final TreeListSet lset = new TreeListSet<>(); + final IndexedTreeListSet lset = new IndexedTreeListSet<>(); lset.addAll(Arrays.asList((E[]) new Integer[] { Integer.valueOf(1), Integer.valueOf(1)})); @@ -306,7 +314,7 @@ public void testCollectionAddAll() { public void testBug35258() { final Object objectToRemove = Integer.valueOf(3); - final List TreeListSet = new TreeListSet<>(); + final List TreeListSet = new IndexedTreeListSet<>(); TreeListSet.add(Integer.valueOf(0)); TreeListSet.add(Integer.valueOf(1)); TreeListSet.add(Integer.valueOf(2)); @@ -334,7 +342,7 @@ public void testBug35258() { } public void testBugCollections447() { - final List TreeListSet = new TreeListSet<>(); + final List TreeListSet = new IndexedTreeListSet<>(); TreeListSet.add("A"); TreeListSet.add("B"); TreeListSet.add("C"); @@ -364,7 +372,7 @@ public void testIterationOrder() { for (int i = 0; i < size; i++) { other.add(i); } - final TreeListSet l = new TreeListSet<>(other); + final IndexedTreeListSet l = new IndexedTreeListSet<>(other); final ListIterator it = l.listIterator(); int i = 0; while (it.hasNext()) { @@ -394,7 +402,7 @@ public void testIterationOrderAfterAddAll() { for (int j = i; j < size; j++) { other.add(j); } - final TreeListSet l = new TreeListSet<>(); + final IndexedTreeListSet l = new IndexedTreeListSet<>(); for (int j = 0; j < i; j++) { l.add(j); } @@ -414,5 +422,40 @@ public void testIterationOrderAfterAddAll() { } } } +// +// /** +// * Tests the {@link ListIterator#set(Object)} method of the list +// * iterator. +// */ +// public void testListIteratorSet() { +// final E[] elements = getOtherElements(); +// +// resetFull(); +// final ListIterator iter1 = getCollection().listIterator(); +// final ListIterator iter2 = getConfirmed().listIterator(); +// for (final E element : elements) { +// iter1.next(); +// iter2.next(); +// iter1.set(element); +// iter2.set(element); +// verify(); +// } +// } + + //----------------------------------------------------------------------- + public BulkTest bulkTestListIterator() { + return new TestListIteratorNoAddSet(); + } + public class TestListIteratorNoAddSet extends TestListIterator { + @Override + public boolean supportsAdd() { + return false; + } + + @Override + public boolean supportsSet() { + return false; + } + } } From fe91c3f097e1ae1afea248b3bb2484a68d101d40 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 13:54:21 +0200 Subject: [PATCH 03/12] Tests fixed --- .../list/AbstractIndexedTreeList.java | 68 +- .../collections4/list/IndexedTreeListSet.java | 22 +- .../list/IndexedTreeListSetTest.java | 641 ++++++++++++------ 3 files changed, 488 insertions(+), 243 deletions(-) diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java index 4d14597f63..89ac3226cc 100644 --- a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java @@ -34,12 +34,12 @@ abstract class AbstractIndexedTreeList extends AbstractList { protected int size = 0; /** - * Methods set(obj) and add(obj) in ListIterator can't be implemented to satisfy specification in IndexedTreeListSet. + * Methods set(obj) in ListIterator can't be implemented to satisfy specification in IndexedTreeListSet. * So these methods are disabled by default and throws UnsupportedOperationException. * It's possible to enable this feature but in this case exception may be thrown if someone tries to add/set * an element which is already in a collection. */ - protected boolean supportAddSetInIterator = true; + boolean supportSetInIterator = true; //----------------------------------------------------------------------- /** @@ -143,6 +143,37 @@ public void add(final int index, final E obj) { size++; } + /** + * Inserts all of the elements in the specified collection into this + * list at the specified position. + * + * @param index index at which to insert the first element from the + * specified collection + * @param collection collection containing elements to be added to this list + * @return true if this list changed as a result of the call + */ + @Override + public boolean addAll(final int index, final Collection collection) { + modCount++; + checkInterval(index, 0, size()); + + int currentIndex = index; + + for (E obj : collection) { + if (canAdd(obj)) { + if (root == null) { + setRoot(new AVLNode(currentIndex, obj, null, null, null)); + } else { + setRoot(root.insert(currentIndex, obj)); + } + size++; + currentIndex++; + } + } + + return currentIndex > index; + } + /** * Sets the element at the specified index. * @@ -924,32 +955,27 @@ public void remove() { } public void set(final E obj) { - if (!supportAddSetInIterator) { + if (!supportSetInIterator) { throw new UnsupportedOperationException("Set operation is not supported"); } - checkModCount(); - if (current == null) { - throw new IllegalStateException(); - } - if (!canAdd(obj)) { - throw new IllegalArgumentException("Unable to set specified element"); + if (canAdd(obj)) { + checkModCount(); + if (current == null) { + throw new IllegalStateException(); + } + current.setValue(obj); } - current.setValue(obj); } public void add(final E obj) { - if (!supportAddSetInIterator) { - throw new UnsupportedOperationException("Add operation is not supported"); + if (canAdd(obj)) { + checkModCount(); + parent.add(nextIndex, obj); + current = null; + currentIndex = -1; + nextIndex++; + expectedModCount++; } - checkModCount(); - if (!canAdd(obj)) { - throw new IllegalArgumentException("Unable to add specified element"); - } - parent.add(nextIndex, obj); - current = null; - currentIndex = -1; - nextIndex++; - expectedModCount++; } } diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java index 497934d99e..e02fe19b16 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java @@ -16,10 +16,7 @@ */ package org.apache.commons.collections4.list; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Data structure which implements both List and Set. @@ -56,8 +53,6 @@ */ public class IndexedTreeListSet extends AbstractIndexedTreeList implements Set { - private static final boolean SUPPORT_ADD_SET_IN_ITERATOR = false; - /** Map from element to it's node or nodes */ protected final Map nodeMap; @@ -76,7 +71,6 @@ public IndexedTreeListSet() { */ public IndexedTreeListSet(final Map map) { this.nodeMap = map; - this.supportAddSetInIterator = SUPPORT_ADD_SET_IN_ITERATOR; } /** @@ -99,7 +93,6 @@ public IndexedTreeListSet(final Collection coll) { */ public IndexedTreeListSet(final Collection coll, final Map map) { this.nodeMap = map; - this.supportAddSetInIterator = SUPPORT_ADD_SET_IN_ITERATOR; for (E e : coll) { add(e); } @@ -133,6 +126,19 @@ public int lastIndexOf(final Object object) { return indexOf(object); } + /** + * Returns an unmodifiable view of the portion of this list between the specified + * fromIndex, inclusive, and toIndex, exclusive. + * + * @param fromIndex low endpoint (inclusive) of the subList + * @param toIndex high endpoint (exclusive) of the subList + * @return an unmodifiable view of the specified range within this list + */ + @Override + public List subList(int fromIndex, int toIndex) { + return Collections.unmodifiableList(super.subList(fromIndex, toIndex)); + } + /** * Searches for the presence of an object in the list. * diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java index 9e4d12c9bc..b72c23cd39 100644 --- a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetTest.java @@ -16,99 +16,25 @@ */ package org.apache.commons.collections4.list; -import junit.framework.Test; -import org.apache.commons.collections4.BulkTest; - import java.util.*; /** - * JUnit tests + * JUnit tests. * - * @since 3.1 + * @since 3.0 */ public class IndexedTreeListSetTest extends AbstractListTest { - public IndexedTreeListSetTest(final String name) { - super(name); - } - -// public static void main(String[] args) { -// junit.textui.TestRunner.run(suite()); -// System.out.println(" add; toArray; iterator; insert; get; indexOf; remove"); -// System.out.print(" TreeListSet = "); -// benchmark(new TreeListSet()); -// System.out.print("\n ArrayList = "); -// benchmark(new java.util.ArrayList()); -// System.out.print("\n LinkedList = "); -// benchmark(new java.util.LinkedList()); -// System.out.print("\n NodeCachingLinkedList = "); -// benchmark(new NodeCachingLinkedList()); -// } - - public static Test suite() { - return BulkTest.makeSuite(IndexedTreeListSetTest.class); - } - - public static void benchmark(final List l) { - long start = System.currentTimeMillis(); - for (int i = 0; i < 100000; i++) { - l.add(Integer.valueOf(i)); - } - System.out.print(System.currentTimeMillis() - start + ";"); - - start = System.currentTimeMillis(); - for (int i = 0; i < 200; i++) { - l.toArray(); - } - System.out.print(System.currentTimeMillis() - start + ";"); - - start = System.currentTimeMillis(); - for (int i = 0; i < 100; i++) { - final java.util.Iterator it = l.iterator(); - while (it.hasNext()) { - it.next(); - } - } - System.out.print(System.currentTimeMillis() - start + ";"); - - start = System.currentTimeMillis(); - for (int i = 0; i < 10000; i++) { - final int j = (int) (Math.random() * 100000); - l.add(j, Integer.valueOf(-j)); - } - System.out.print(System.currentTimeMillis() - start + ";"); - - start = System.currentTimeMillis(); - for (int i = 0; i < 50000; i++) { - final int j = (int) (Math.random() * 110000); - l.get(j); - } - System.out.print(System.currentTimeMillis() - start + ";"); - - start = System.currentTimeMillis(); - for (int i = 0; i < 200; i++) { - final int j = (int) (Math.random() * 100000); - l.indexOf(Integer.valueOf(j)); - } - System.out.print(System.currentTimeMillis() - start + ";"); + boolean extraVerify = true; - start = System.currentTimeMillis(); - for (int i = 0; i < 10000; i++) { - final int j = (int) (Math.random() * 100000); - l.remove(j); - } - System.out.print(System.currentTimeMillis() - start + ";"); + public IndexedTreeListSetTest(final String testName) { + super(testName); } //----------------------------------------------------------------------- @Override - public IndexedTreeListSet makeObject() { - return new IndexedTreeListSet<>(); - } - - @Override - public boolean isNullSupported() { - return false; + public String getCompatibilityVersion() { + return "4"; } @Override @@ -136,68 +62,207 @@ public E[] getFullNonNullElements() { }; } + //----------------------------------------------------------------------- @Override - public void testListIteratorAdd() { - // Does not support ListIterator.add(obj) + public List makeObject() { + IndexedTreeListSet list = new IndexedTreeListSet<>(); + list.supportSetInIterator = false; + return list; + } + + @Override + public boolean isNullSupported() { + return false; } - //----------------------------------------------------------------------- @SuppressWarnings("unchecked") - public void testAddMultiple() { - final List l = makeObject(); - l.add((E) "hugo"); - l.add((E) "erna"); - l.add((E) "daniel"); - l.add((E) "andres"); - l.add(0, (E) "harald"); - assertEquals("harald", l.get(0)); - assertEquals("hugo", l.get(1)); - assertEquals("erna", l.get(2)); - assertEquals("daniel", l.get(3)); - assertEquals("andres", l.get(4)); + public void testAdd() { + final IndexedTreeListSet lset = new IndexedTreeListSet<>(new ArrayList()); + + // Duplicate element + final E obj = (E) Integer.valueOf(1); + lset.add(obj); + lset.add(obj); + assertEquals("Duplicate element was added.", 1, lset.size()); + + // Unique element + lset.add((E) Integer.valueOf(2)); + assertEquals("Unique element was not added.", 2, lset.size()); } @SuppressWarnings("unchecked") - public void testRemove() { - final List l = makeObject(); - l.add((E) "hugo"); - l.add((E) "erna"); - l.add((E) "daniel"); - l.add((E) "andres"); - l.add(0, (E) "harald"); - int i = 0; - assertEquals("harald", l.get(i++)); - assertEquals("hugo", l.get(i++)); - assertEquals("erna", l.get(i++)); - assertEquals("daniel", l.get(i++)); - assertEquals("andres", l.get(i++)); - - l.remove(0); - i = 0; - assertEquals("hugo", l.get(i++)); - assertEquals("erna", l.get(i++)); - assertEquals("daniel", l.get(i++)); - assertEquals("andres", l.get(i++)); - - i = 0; - l.remove(1); - assertEquals("hugo", l.get(i++)); - assertEquals("daniel", l.get(i++)); - assertEquals("andres", l.get(i++)); - - i = 0; - l.remove(2); - assertEquals("hugo", l.get(i++)); - assertEquals("daniel", l.get(i++)); + public void testAddAll() { + final IndexedTreeListSet lset = new IndexedTreeListSet<>(new ArrayList()); + + lset.addAll( + Arrays.asList((E[]) new Integer[] { Integer.valueOf(1), Integer.valueOf(1)})); + + assertEquals("Duplicate element was added.", 1, lset.size()); + } + + @Override + public void testCollectionAddAll() { + // override for set behaviour + resetEmpty(); + E[] elements = getFullElements(); + boolean r = getCollection().addAll(Arrays.asList(elements)); + getConfirmed().addAll(Arrays.asList(elements)); + verify(); + assertTrue("Empty collection should change after addAll", r); + for (final E element : elements) { + assertTrue("Collection should contain added element", + getCollection().contains(element)); + } + + resetFull(); + final int size = getCollection().size(); + elements = getOtherElements(); + r = getCollection().addAll(Arrays.asList(elements)); + getConfirmed().addAll(Arrays.asList(elements)); + verify(); + assertTrue("Full collection should change after addAll", r); + for (int i = 0; i < elements.length; i++) { + assertTrue("Full collection should contain added element " + i, + getCollection().contains(elements[i])); + } + assertEquals("Size should increase after addAll", + size + elements.length, getCollection().size()); + } + + @Override + public void testCollectionIteratorRemove() { + try { + extraVerify = false; + super.testCollectionIteratorRemove(); + } finally { + extraVerify = true; + } + } + public void testCollections304() { + final List list = new LinkedList<>(); + final IndexedTreeListSet decoratedList = new IndexedTreeListSet(list); + final String s1 = "Apple"; + final String s2 = "Lemon"; + final String s3 = "Orange"; + final String s4 = "Strawberry"; + + decoratedList.add(s1); + decoratedList.add(s2); + decoratedList.add(s3); + assertEquals(3, decoratedList.size()); + + decoratedList.set(1, s4); + assertEquals(3, decoratedList.size()); + + decoratedList.add(1, s4); + assertEquals(3, decoratedList.size()); + + decoratedList.add(1, s2); + assertEquals(4, decoratedList.size()); } @SuppressWarnings("unchecked") - public void testInsertBefore() { - final List l = makeObject(); - l.add((E) "erna"); - l.add(0, (E) "hugo"); - assertEquals("hugo", l.get(0)); - assertEquals("erna", l.get(1)); + public void testCollections307() { + List list = new ArrayList<>(); + List uniqueList = new IndexedTreeListSet(list); + + final String hello = "Hello"; + final String world = "World"; + uniqueList.add((E) hello); + uniqueList.add((E) world); + + List subList = list.subList(0, 0); + List subUniqueList = uniqueList.subList(0, 0); + + assertFalse(subList.contains(world)); // passes + assertFalse(subUniqueList.contains(world)); // fails + + List worldList = new ArrayList<>(); + worldList.add((E) world); + assertFalse(subList.contains("World")); // passes + assertFalse(subUniqueList.contains("World")); // fails + + // repeat the test with a different class than HashSet; + // which means subclassing IndexedTreeListSet below + list = new ArrayList<>(); + uniqueList = new IndexedTreeListSet(list, new java.util.TreeMap()); + + uniqueList.add((E) hello); + uniqueList.add((E) world); + + subList = list.subList(0, 0); + subUniqueList = uniqueList.subList(0, 0); + + assertFalse(subList.contains(world)); // passes + assertFalse(subUniqueList.contains(world)); // fails + + worldList = new ArrayList<>(); + worldList.add((E) world); + assertFalse(subList.contains("World")); // passes + assertFalse(subUniqueList.contains("World")); // fails + } + + public void testCollections701() { + final IndexedTreeListSet uniqueList = new IndexedTreeListSet<>(new ArrayList<>()); + final Integer obj1 = Integer.valueOf(1); + final Integer obj2 = Integer.valueOf(2); + uniqueList.add(obj1); + uniqueList.add(obj2); + assertEquals(2, uniqueList.size()); + uniqueList.add(uniqueList); + assertEquals(3, uniqueList.size()); + final List list = new LinkedList<>(); + final IndexedTreeListSet decoratedList = new IndexedTreeListSet(list); + final String s1 = "Apple"; + final String s2 = "Lemon"; + final String s3 = "Orange"; + final String s4 = "Strawberry"; + decoratedList.add(s1); + decoratedList.add(s2); + decoratedList.add(s3); + assertEquals(3, decoratedList.size()); + decoratedList.set(1, s4); + assertEquals(3, decoratedList.size()); + decoratedList.add(decoratedList); + assertEquals(4, decoratedList.size()); + } + + //----------------------------------------------------------------------- + public void testFactory() { + final Integer[] array = new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(1) }; + final ArrayList list = new ArrayList<>(Arrays.asList(array)); + final IndexedTreeListSet lset = new IndexedTreeListSet(list); + + assertEquals("Duplicate element was added.", 2, lset.size()); + assertEquals(Integer.valueOf(1), lset.get(0)); + assertEquals(Integer.valueOf(2), lset.get(1)); + assertEquals(Integer.valueOf(1), list.get(0)); + assertEquals(Integer.valueOf(2), list.get(1)); + } + + public void testIntCollectionAddAll() { + // make a IndexedTreeListSet with one element + final List list = new IndexedTreeListSet<>(new ArrayList()); + final Integer existingElement = Integer.valueOf(1); + list.add(existingElement); + + // add two new unique elements at index 0 + final Integer firstNewElement = Integer.valueOf(2); + final Integer secondNewElement = Integer.valueOf(3); + Collection collection = Arrays.asList(firstNewElement, secondNewElement); + list.addAll(0, collection); + assertEquals("Unique elements should be added.", 3, list.size()); + assertEquals("First new element should be at index 0", firstNewElement, list.get(0)); + assertEquals("Second new element should be at index 1", secondNewElement, list.get(1)); + assertEquals("Existing element should shift to index 2", existingElement, list.get(2)); + + // add a duplicate element and a unique element at index 0 + final Integer thirdNewElement = Integer.valueOf(4); + collection = Arrays.asList(existingElement, thirdNewElement); + list.addAll(0, collection); + assertEquals("Duplicate element should not be added, unique element should be added.", + 4, list.size()); + assertEquals("Third new element should be at index 0", thirdNewElement, list.get(0)); } @SuppressWarnings("unchecked") @@ -242,74 +307,200 @@ public void testIndexOf() { assertEquals(1, l.indexOf("5")); } - /** - * Test {@link List#set(int,Object)}. - */ - public void testListSetByIndex() { - if (!isSetSupported()) { - return; + @SuppressWarnings("unchecked") + public void testListIterator() { + final IndexedTreeListSet lset = new IndexedTreeListSet<>(new ArrayList()); + + final E obj1 = (E) Integer.valueOf(1); + final E obj2 = (E) Integer.valueOf(2); + lset.add(obj1); + lset.add(obj2); + + // Attempts to add a duplicate object + for (final ListIterator it = lset.listIterator(); it.hasNext();) { + it.next(); + + if (!it.hasNext()) { + it.add(obj1); + break; + } + } + + assertEquals("Duplicate element was added", 2, lset.size()); + } + + @Override + public void testListIteratorAdd() { + // override to cope with Set behaviour + resetEmpty(); + final List list1 = getCollection(); + final List list2 = getConfirmed(); + + final E[] elements = getOtherElements(); // changed here + ListIterator iter1 = list1.listIterator(); + ListIterator iter2 = list2.listIterator(); + + for (final E element : elements) { + iter1.add(element); + iter2.add(element); + super.verify(); // changed here } resetFull(); - final E[] elements = getFullElements(); - final E[] other = getOtherElements(); - - for (int i = 0; i < other.length; i++) { - final E n = other[i % other.length]; - final E v = getCollection().set(i % elements.length, n); - assertEquals("Set should return correct element", elements[i], v); - getConfirmed().set(i % elements.length, n); - verify(); + iter1 = getCollection().listIterator(); + iter2 = getConfirmed().listIterator(); + for (final E element : elements) { + iter1.next(); + iter2.next(); + iter1.add(element); + iter2.add(element); + super.verify(); // changed here } } + //----------------------------------------------------------------------- + @Override + public void testListIteratorSet() { + // override to block + resetFull(); + final ListIterator it = getCollection().listIterator(); + it.next(); + try { + it.set(null); + fail(); + } catch (final UnsupportedOperationException ex) {} + } + + @Override @SuppressWarnings("unchecked") - public void testAddAll() { - final IndexedTreeListSet lset = new IndexedTreeListSet<>(); + public void testListSetByIndex() { + // override for set behaviour + resetFull(); + final int size = getCollection().size(); + getCollection().set(0, (E) new Long(1000)); + assertEquals(size, getCollection().size()); - lset.addAll(Arrays.asList((E[]) new Integer[] { Integer.valueOf(1), Integer.valueOf(1)})); + getCollection().set(2, (E) new Long(1000)); + assertEquals(size - 1, getCollection().size()); + assertEquals(new Long(1000), getCollection().get(1)); // set into 2, but shifted down to 1 + } - assertEquals("Duplicate element was added.", 1, lset.size()); + @SuppressWarnings("unchecked") + public void testRetainAll() { + final List list = new ArrayList<>(10); + final IndexedTreeListSet uniqueList = new IndexedTreeListSet(list); + for (int i = 0; i < 10; ++i) { + uniqueList.add((E)Integer.valueOf(i)); + } + + final Collection retained = new ArrayList<>(5); + for (int i = 0; i < 5; ++i) { + retained.add((E)Integer.valueOf(i * 2)); + } + + assertTrue(uniqueList.retainAll(retained)); + assertEquals(5, uniqueList.size()); + assertTrue(uniqueList.contains(Integer.valueOf(0))); + assertTrue(uniqueList.contains(Integer.valueOf(2))); + assertTrue(uniqueList.contains(Integer.valueOf(4))); + assertTrue(uniqueList.contains(Integer.valueOf(6))); + assertTrue(uniqueList.contains(Integer.valueOf(8))); } - @Override - public void testCollectionAddAll() { - // override for set behaviour - resetEmpty(); - E[] elements = getFullElements(); - boolean r = getCollection().addAll(Arrays.asList(elements)); - getConfirmed().addAll(Arrays.asList(elements)); - verify(); - assertTrue("Empty collection should change after addAll", r); - for (final E element : elements) { - assertTrue("Collection should contain added element", - getCollection().contains(element)); + @SuppressWarnings("unchecked") + public void testRetainAllWithInitialList() { + // initialized with empty list + final List list = new ArrayList<>(10); + for (int i = 0; i < 5; ++i) { + list.add((E)Integer.valueOf(i)); + } + final IndexedTreeListSet uniqueList = new IndexedTreeListSet(list); + for (int i = 5; i < 10; ++i) { + uniqueList.add((E)Integer.valueOf(i)); + } + + final Collection retained = new ArrayList<>(5); + for (int i = 0; i < 5; ++i) { + retained.add((E)Integer.valueOf(i * 2)); } + assertTrue(uniqueList.retainAll(retained)); + assertEquals(5, uniqueList.size()); + assertTrue(uniqueList.contains(Integer.valueOf(0))); + assertTrue(uniqueList.contains(Integer.valueOf(2))); + assertTrue(uniqueList.contains(Integer.valueOf(4))); + assertTrue(uniqueList.contains(Integer.valueOf(6))); + assertTrue(uniqueList.contains(Integer.valueOf(8))); + } + + @SuppressWarnings("unchecked") + public void testSet() { + final IndexedTreeListSet lset = new IndexedTreeListSet<>(new ArrayList()); + + // Duplicate element + final E obj1 = (E) Integer.valueOf(1); + final E obj2 = (E) Integer.valueOf(2); + final E obj3 = (E) Integer.valueOf(3); + + lset.add(obj1); + lset.add(obj2); + lset.set(0, obj1); + assertEquals(2, lset.size()); + assertSame(obj1, lset.get(0)); + assertSame(obj2, lset.get(1)); + + lset.clear(); + lset.add(obj1); + lset.add(obj2); + lset.set(0, obj2); + assertEquals(1, lset.size()); + assertSame(obj2, lset.get(0)); + + lset.clear(); + lset.add(obj1); + lset.add(obj2); + lset.set(0, obj3); + assertEquals(2, lset.size()); + assertSame(obj3, lset.get(0)); + assertSame(obj2, lset.get(1)); + + lset.clear(); + lset.add(obj1); + lset.add(obj2); + lset.set(1, obj1); + assertEquals(1, lset.size()); + assertSame(obj1, lset.get(0)); + } + + public void testSetCollections444() { + final IndexedTreeListSet lset = new IndexedTreeListSet<>(new ArrayList()); + + // Duplicate element + final Integer obj1 = Integer.valueOf(1); + final Integer obj2 = Integer.valueOf(2); + + lset.add(obj1); + lset.add(obj2); + lset.set(0, obj1); + assertEquals(2, lset.size()); + assertSame(obj1, lset.get(0)); + assertSame(obj2, lset.get(1)); + + assertTrue(lset.contains(obj1)); + assertTrue(lset.contains(obj2)); + } + + public void testSubListIsUnmodifiable() { resetFull(); - final int size = getCollection().size(); - elements = getOtherElements(); - r = getCollection().addAll(Arrays.asList(elements)); - getConfirmed().addAll(Arrays.asList(elements)); - verify(); - assertTrue("Full collection should change after addAll", r); - for (int i = 0; i < elements.length; i++) { - assertTrue("Full collection should contain added element " + i, - getCollection().contains(elements[i])); + final List subList = getCollection().subList(1, 3); + try { + subList.remove(0); + fail("subList should be unmodifiable"); + } catch (final UnsupportedOperationException e) { + // expected } - assertEquals("Size should increase after addAll", - size + elements.length, getCollection().size()); } -// public void testCheck() { -// List l = makeEmptyList(); -// l.add("A1"); -// l.add("A2"); -// l.add("A3"); -// l.add("A4"); -// l.add("A5"); -// l.add("A6"); -// } public void testBug35258() { final Object objectToRemove = Integer.valueOf(3); @@ -422,40 +613,62 @@ public void testIterationOrderAfterAddAll() { } } } -// -// /** -// * Tests the {@link ListIterator#set(Object)} method of the list -// * iterator. -// */ -// public void testListIteratorSet() { -// final E[] elements = getOtherElements(); -// -// resetFull(); -// final ListIterator iter1 = getCollection().listIterator(); -// final ListIterator iter2 = getConfirmed().listIterator(); -// for (final E element : elements) { -// iter1.next(); -// iter2.next(); -// iter1.set(element); -// iter2.set(element); -// verify(); -// } -// } - //----------------------------------------------------------------------- - public BulkTest bulkTestListIterator() { - return new TestListIteratorNoAddSet(); + @SuppressWarnings("unchecked") + public void testUniqueListDoubleInsert() { + final List l = new IndexedTreeListSet(new LinkedList()); + l.add((E) new Object()); + l.add((E) new Object()); + + // duplicate is removed + l.set(0, l.get(1)); + assertEquals(1, l.size()); + + // duplicate should be removed again + l.add(1, l.get(0)); + assertEquals(1, l.size()); } - public class TestListIteratorNoAddSet extends TestListIterator { - @Override - public boolean supportsAdd() { - return false; - } + @SuppressWarnings("unchecked") + public void testUniqueListReInsert() { + final List l = new IndexedTreeListSet(new LinkedList()); + l.add((E) new Object()); + l.add((E) new Object()); + + final E a = l.get(0); + + // duplicate is removed + l.set(0, l.get(1)); + assertEquals(1, l.size()); - @Override - public boolean supportsSet() { - return false; + // old object is added back in + l.add(1, a); + assertEquals(2, l.size()); + } + + @Override + @SuppressWarnings("unchecked") + public void verify() { + super.verify(); + + if (extraVerify) { + final int size = getCollection().size(); + getCollection().add((E) new Long(1000)); + assertEquals(size + 1, getCollection().size()); + + getCollection().add((E) new Long(1000)); + assertEquals(size + 1, getCollection().size()); + assertEquals(new Long(1000), getCollection().get(size)); + + getCollection().remove(size); } } + +// public void testCreate() throws Exception { +// resetEmpty(); +// writeExternalFormToDisk((java.io.Serializable) getCollection(), "src/test/resources/data/test/IndexedTreeListSet.emptyCollection.version4.obj"); +// resetFull(); +// writeExternalFormToDisk((java.io.Serializable) getCollection(), "src/test/resources/data/test/IndexedTreeListSet.fullCollection.version4.obj"); +// } + } From 4d8845ec265d34fb5322f63cf919a8ec1cfad048 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 14:39:26 +0200 Subject: [PATCH 04/12] Added randomized tests --- ...IndexedTreeListIteratorRandomizedTest.java | 323 ++++++++++++ .../list/IndexedTreeListRandomizedTest.java | 464 ++++++++++++++++++ .../IndexedTreeListSetRandomizedTest.java | 377 ++++++++++++++ 3 files changed, 1164 insertions(+) create mode 100644 src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java create mode 100644 src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java create mode 100644 src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java new file mode 100644 index 0000000000..a5b82fce6f --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java @@ -0,0 +1,323 @@ +package org.apache.commons.collections4.list; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.*; + +import static org.junit.Assert.*; + +@RunWith(Parameterized.class) +public class IndexedTreeListIteratorRandomizedTest { + + private Random random; + private Set elementsSet; + private List elementsList; + private List removedList; + + private Class listClass; + private List testList; + + private int seed; + private int iterations; + + public IndexedTreeListIteratorRandomizedTest(Class listClass, int seed, int iterations) { + this.listClass = listClass; + this.seed = seed; + this.iterations = iterations; + } + + @Before + public void setUp() throws Exception { + random = new Random(seed); + elementsSet = new HashSet<>(); + elementsList = new ArrayList<>(); + removedList = new ArrayList<>(); + testList = (List) listClass.newInstance(); + } + + @Parameterized.Parameters(name = "{0} {1} {2}") + public static Collection parameters() { + return Arrays.asList(new Object[][] { + { IndexedTreeListSet.class, 9999, 1}, + { IndexedTreeListSet.class, 9999, 2}, + { IndexedTreeListSet.class, 9999, 3}, + { IndexedTreeListSet.class, 9999, 4}, + { IndexedTreeListSet.class, 9999, 5}, + { IndexedTreeListSet.class, 9999, 10}, + { IndexedTreeListSet.class, 9999, 100}, + { IndexedTreeListSet.class, 9999, 1000}, +// {IndexedTreeListSet.class, 9999, 10000}, + + {IndexedTreeList.class, 9999, 1}, + {IndexedTreeList.class, 9999, 2}, + {IndexedTreeList.class, 9999, 3}, + {IndexedTreeList.class, 9999, 4}, + {IndexedTreeList.class, 9999, 5}, + {IndexedTreeList.class, 9999, 10}, + {IndexedTreeList.class, 9999, 100}, + {IndexedTreeList.class, 9999, 1000}, +// {IndexedTreeList.class, 9999, 10000}, + +// {TreeList.class, 9999, 1}, +// {TreeList.class, 9999, 2}, +// {TreeList.class, 9999, 3}, +// {TreeList.class, 9999, 4}, +// {TreeList.class, 9999, 5}, +// {TreeList.class, 9999, 10}, +// {TreeList.class, 9999, 100}, +// {TreeList.class, 9999, 1000}, +//// {TreeList.class, 9999, 10000}, + }); + } + +// @Parameterized.Parameters(name = "{0} {1} {2}") +// public static Collection parameters() { +// ArrayList params = new ArrayList(); +// Random r = new Random(); +// for (int i = 0; i < 1000; i++) { +// params.add(new Object[] {IndexedTreeListSet.class, r.nextInt(), r.nextInt(1 << (r.nextInt(12))) + 1}); +// params.add(new Object[] {IndexedTreeList.class, r.nextInt(), r.nextInt(1 << (r.nextInt(12))) + 1}); +// params.add(new Object[] {TreeList.class, r.nextInt(), r.nextInt(1 << (r.nextInt(12))) + 1}); +// } +// return params; +// } + + @Test + public void iteratorTest() throws Exception { + init(); + Iterator arrayListIterator = elementsList.iterator(); + Iterator testListIterator = testList.iterator(); + assertReference(); + while (arrayListIterator.hasNext()) { + Long element = arrayListIterator.next(); + assertEquals(element, testListIterator.next()); + if (random.nextBoolean()) { + arrayListIterator.remove(); + testListIterator.remove(); + removedList.add(element); + } + } + elementsSet.removeAll(removedList); + assertReference(); + } + + @Test + public void listIteratorForwardTest() throws Exception { + init(); + ListIterator arrayListIterator = elementsList.listIterator(); + ListIterator testListIterator = testList.listIterator(); + assertReference(); + while (arrayListIterator.hasNext()) { + Long element = arrayListIterator.next(); + assertEquals(element, testListIterator.next()); + assertEquals(arrayListIterator.nextIndex(), testListIterator.nextIndex()); + assertEquals(arrayListIterator.previousIndex(), testListIterator.previousIndex()); + if (random.nextBoolean()) { + arrayListIterator.remove(); + testListIterator.remove(); + removedList.add(element); + } else if (random.nextBoolean()) { + Long val = getRandomNotExisting(); + arrayListIterator.set(val); + testListIterator.set(val); + removedList.add(element); + elementsSet.add(val); + } + if (random.nextBoolean()) { + Long val = getRandomNotExisting(); + arrayListIterator.add(val); + testListIterator.add(val); + elementsSet.add(val); + } + } + elementsSet.removeAll(removedList); + assertReference(); + } + + @Test + public void listIteratorBackwardTest() throws Exception { + init(); + ListIterator arrayListIterator = elementsList.listIterator(elementsList.size() - 1); + ListIterator testListIterator = testList.listIterator(elementsList.size() - 1); + assertReference(); + while (arrayListIterator.hasPrevious()) { + Long element = arrayListIterator.previous(); + assertEquals(element, testListIterator.previous()); + assertEquals(arrayListIterator.nextIndex(), testListIterator.nextIndex()); + assertEquals(arrayListIterator.previousIndex(), testListIterator.previousIndex()); + if (random.nextBoolean()) { + arrayListIterator.remove(); + testListIterator.remove(); + removedList.add(element); + } else if (random.nextBoolean()) { + Long val = getRandomNotExisting(); + arrayListIterator.set(val); + testListIterator.set(val); + removedList.add(element); + elementsSet.add(val); + } + if (random.nextBoolean()) { + Long val = getRandomNotExisting(); + arrayListIterator.add(val); + testListIterator.add(val); + elementsSet.add(val); + } + } + elementsSet.removeAll(removedList); + assertReference(); + } + + @Test + public void listIteratorRandomizedTest() throws Exception { + init(); + int initialPosition = random.nextInt(elementsList.size()); + ListIterator arrayListIterator = elementsList.listIterator(initialPosition); + ListIterator testListIterator = testList.listIterator(initialPosition); + assertReference(); + + Long element = null; + for (int i = 0; i < iterations; i++) { + assertEquals(arrayListIterator.hasNext(), testListIterator.hasNext()); + assertEquals(arrayListIterator.hasPrevious(), testListIterator.hasPrevious()); + assertEquals(arrayListIterator.nextIndex(), testListIterator.nextIndex()); + assertEquals(arrayListIterator.previousIndex(), testListIterator.previousIndex()); + + int operation = random.nextInt(5); + //System.out.println("" + i + " : " + arrayListIterator.previousIndex() + ", " + arrayListIterator.nextIndex() + " : " + operation); + + switch (operation) { + case 0: + if (arrayListIterator.hasNext()) { + element = arrayListIterator.next(); + assertEquals(element, testListIterator.next()); + } + break; + case 1: + if (arrayListIterator.hasPrevious()) { + element = arrayListIterator.previous(); + assertEquals(element, testListIterator.previous()); + } + break; + case 2: + if (element != null) { + arrayListIterator.remove(); + testListIterator.remove(); + removedList.add(element); + element = null; + } + break; + case 3: + if (element != null) { + Long val = getRandomNotExisting(); + arrayListIterator.set(val); + testListIterator.set(val); + removedList.add(element); + elementsSet.add(val); + element = val; + } + break; + case 4: + Long val = getRandomNotExisting(); + arrayListIterator.add(val); + testListIterator.add(val); + elementsSet.add(val); + element = null; + break; + } + assertEquals(elementsList, testList); + } + elementsSet.removeAll(removedList); + assertReference(); + } + + private void init() { + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size() + 1); + testList.add(index, addRandom(index)); // can be optimized + } + assertReference(); + } + + private Long addRandom() { + while (true) { + Long value = random.nextLong(); + if (elementsSet.add(value)) { + elementsList.add(value); + return value; + } + } + } + + private Long addRandom(int index) { + while (true) { + Long value = random.nextLong(); + if (elementsSet.add(value)) { + elementsList.add(index, value); + return value; + } + } + } + + private Long getRandomExisting() { + if (elementsList.isEmpty()) { + return null; + } + return elementsList.get(random.nextInt(elementsList.size())); + } + + private Long getRandomNotExisting() { + while (true) { + Long value = random.nextLong(); + if (!elementsSet.contains(value)) { + return value; + } + } + } + + private Long getRandomRemoved() { + if (removedList.isEmpty()) { + return null; + } + return removedList.get(random.nextInt(removedList.size())); + } + + private Long removeRandomValue() { + if (elementsList.isEmpty()) { + return null; + } + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + elementsSet.remove(value); + elementsList.remove(index); + removedList.add(value); + return value; + } + + private int removeRandomIndex() { + if (elementsList.isEmpty()) { + return -1; + } + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + elementsSet.remove(value); + elementsList.remove(index); + removedList.add(value); + return index; + } + + private void assertReference() { + if (testList instanceof Set) { + assertEquals(elementsSet, testList); + } else { + assertEquals(elementsSet, new HashSet<>(testList)); + } + assertEquals(elementsList, testList); + + if (testList instanceof AbstractIndexedTreeList) { + ((AbstractIndexedTreeList) testList).assertConsistent(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java new file mode 100644 index 0000000000..5ef6e2dad7 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java @@ -0,0 +1,464 @@ +package org.apache.commons.collections4.list; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.Assert.*; + +@RunWith(Parameterized.class) +public class IndexedTreeListRandomizedTest { + + private Random random; + private Set elementsSet; + private List elementsList; + private List removedList; + + private IndexedTreeList testList; + + private int seed; + private int iterations; + + public IndexedTreeListRandomizedTest(int seed, int iterations) { + this.seed = seed; + this.iterations = iterations; + } + + @Before + public void setUp() throws Exception { + random = new Random(seed); + elementsSet = new HashSet<>(); + elementsList = new ArrayList<>(); + removedList = new ArrayList<>(); + testList = new IndexedTreeList<>(); + } + + @Parameterized.Parameters(name = "{0} {1}") + public static Collection parameters() { + return Arrays.asList(new Object[][] { + {9999, 1}, + {9999, 2}, + {9999, 3}, + {9999, 4}, + {9999, 5}, + {9999, 10}, + {9999, 100}, + {9999, 1000}, +// {9999, 10000}, + }); + } + +// @Parameterized.Parameters(name = "{0} {1}") +// public static Collection parameters() { +// ArrayList params = new ArrayList(); +// Random r = new Random(); +// for (int i = 0; i < 1000; i++) { +// params.add(new Object[] {r.nextInt(), r.nextInt(1 << (r.nextInt(12))) + 1}); +// } +// return params; +// } + + @Test + public void addToTail() throws Exception { + for (int i = 0; i < iterations; i++) { + testList.add(addRandom()); + assertReference(); + } + } + + @Test + public void addToHead() throws Exception { + for (int i = 0; i < iterations; i++) { + testList.add(0, addRandom(0)); + assertReference(); + } + } + + @Test + public void addToMiddle() throws Exception { + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size() + 1); + testList.add(index, addRandom(index)); + assertReference(); + } + } + + @Test + public void setNewValue() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size()); + final Long value = elementsList.get(index); + elementsSet.remove(value); + final long newValue = random.nextLong(); + elementsSet.add(newValue); + elementsList.set(index, newValue); + testList.set(index, newValue); + assertReference(); + } + } + + @Test + public void setExistingValue() throws Exception { + init(); + final List indexes = IntStream.range(0, iterations) + .mapToObj(Integer::valueOf) + .collect(Collectors.toList()); + Collections.shuffle(indexes, random); + for (int i = 0; i < indexes.size() - 1; i += 2) { + int indexFrom = indexes.get(i); + int indexTo = indexes.get(i + 1); + final Long value = elementsList.get(indexFrom); + elementsSet.remove(elementsList.get(indexTo)); + elementsList.set(indexTo, value); + testList.set(indexTo, value); + assertReference(); + } + } + + @Test + public void removeByIndex() throws Exception { + init(); + while (!testList.isEmpty()) { + int index = removeRandomIndex(); + testList.remove(index); + assertReference(); + } + } + + @Test + public void removeByValue() throws Exception { + init(); + while (!testList.isEmpty()) { + Long value = removeRandomValue(); + assertTrue(testList.contains(value)); + testList.remove(value); + assertFalse(testList.contains(value)); + assertReference(); + } + } + + @Test + public void removeFirstLast() throws Exception { + init(); + while (!testList.isEmpty()) { + removeByIndex(0); + testList.remove(0); + if (testList.size() > 0) { + removeByIndex(testList.size() - 1); + testList.remove(testList.size() - 1); + } + assertReference(); + } + } + + @Test + public void removeMiddle() throws Exception { + init(); + while (!testList.isEmpty()) { + removeByIndex(testList.size() / 2); + testList.remove(testList.size() / 2); + assertReference(); + } + } + + @Test + public void contains() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + assertTrue(testList.contains(getRandomExisting())); + assertFalse(testList.contains(getRandomNotExisting())); + } + assertReference(); + } + + @Test + public void get() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + assertEquals(value, testList.get(index)); + } + assertReference(); + } + + @Test + public void indexOf() throws Exception { + init(); + int initialSize = elementsList.size(); + for (int i = 0; i < initialSize; i++) { + elementsList.add(elementsList.get(i)); + testList.add(elementsList.get(i)); + } + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(initialSize); + Long value = elementsList.get(index); + assertEquals(index, testList.indexOf(value)); + } + assertReference(); + } + + @Test + public void lastIndexOf() throws Exception { + init(); + int initialSize = elementsList.size(); + for (int i = 0; i < initialSize; i++) { + elementsList.add(elementsList.get(i)); + testList.add(elementsList.get(i)); + } + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(initialSize); + Long value = elementsList.get(index); + assertEquals(index + initialSize, testList.lastIndexOf(value)); + } + assertReference(); + } + + @Test + public void indexes() throws Exception { + init(); + int initialSize = elementsList.size(); + for (int i = 0; i < initialSize; i++) { + elementsList.add(elementsList.get(i)); + testList.add(elementsList.get(i)); + } + for (int i = 0; i < initialSize; i++) { + elementsList.add(elementsList.get(i)); + testList.add(i, elementsList.get(i)); + } + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(initialSize); + Long value = elementsList.get(index); + assertArrayEquals(new int[] {index, index + initialSize, index + initialSize * 2}, testList.indexes(value)); + assertEquals(3, testList.count(value)); + } + assertReference(); + } + + @Test + public void indexesWithRemove() throws Exception { + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size() + 1); + testList.add(index, addRandom(index)); + + index = random.nextInt(elementsList.size() + 1); + Long existingValue = getRandomExisting(); + testList.add(index, existingValue); + elementsList.add(index, existingValue); + + index = random.nextInt(elementsList.size()); + existingValue = getRandomExisting(); + testList.set(index, existingValue); + elementsList.set(index, existingValue); + + index = random.nextInt(elementsList.size()); + testList.remove(index); + elementsList.remove(index); + } + + assertEquals(elementsList, testList); + + Map> indexes = new HashMap<>(); + for (int i = 0; i < elementsList.size(); i++) { + indexes.computeIfAbsent(elementsList.get(i), k -> new ArrayList()).add(i); + } + + assertEquals(indexes.keySet(), testList.nodeMap.keySet()); + + for (Entry> entry : indexes.entrySet()) { + assertArrayEquals(entry.getValue().stream().mapToInt(i->i).toArray(), + testList.indexes(entry.getKey())); + assertEquals(entry.getValue().size(), testList.count(entry.getKey())); + } + + assertArrayEquals(elementsList.toArray(), testList.toArray()); + assertArrayEquals(elementsList.toArray(new Long[0]), testList.toArray(new Long[0])); + } + + @Test + public void addRandomExisting() throws Exception { + for (int i = 0; i < iterations; i++) { + testList.add(addRandom()); + Long randomExisting = getRandomExisting(); + testList.add(randomExisting); + elementsList.add(randomExisting); + assertReference(); + } + + assertArrayEquals(elementsList.toArray(), testList.toArray()); + assertArrayEquals(elementsList.toArray(new Long[0]), testList.toArray(new Long[0])); + } + + @Test + public void addSameExisting() throws Exception { + for (int i = 0; i < iterations; i++) { + testList.add(addRandom()); + testList.add(elementsList.get(0)); + elementsList.add(elementsList.get(0)); + assertReference(); + } + + assertArrayEquals(elementsList.toArray(), testList.toArray()); + assertArrayEquals(elementsList.toArray(new Long[0]), testList.toArray(new Long[0])); + } + + @Test + public void addContainsIndexOfRemove() throws Exception { + init(); + while (!testList.isEmpty()) { + // add + int index = random.nextInt(elementsList.size() + 1); + testList.add(index, addRandom(index)); + + // contains + assertTrue(testList.contains(getRandomExisting())); + assertFalse(testList.contains(getRandomNotExisting())); + + // indexOf + index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + assertEquals(value, testList.get(index)); + assertEquals(index, testList.indexOf(value)); + + // remove + final Long valueToRemove = removeRandomValue(); + assertTrue(testList.remove(valueToRemove)); + final int indexToRemove = removeRandomIndex(); + final Long removedValue = testList.get(indexToRemove); + assertEquals(removedValue, testList.remove(indexToRemove)); + + // check add nulls + try { + testList.add(null); + fail("No exception on adding null"); + } catch (NullPointerException e) {} + try { + testList.add(random.nextInt(elementsList.size() + 1), null); + fail("No exception on adding null"); + } catch (NullPointerException e) {} + // check not existing indexOf + assertEquals(-1, testList.indexOf(random.nextLong())); + assertEquals(-1, testList.lastIndexOf(random.nextLong())); + assertEquals(0, testList.indexes(random.nextLong()).length); + assertEquals(0, testList.count(random.nextLong())); + assertFalse(testList.remove(random.nextLong())); + + assertReference(); + } + } + + @Test + public void clear() { + init(); + testList.clear(); + elementsList.clear(); + elementsSet.clear(); + assertReference(); + } + + @Test + public void constructorWithCollection() { + init(); + testList = new IndexedTreeList<>(elementsList); + assertReference(); + } + + @Test + public void constructorWithTreeMap() { + init(); + testList = new IndexedTreeList<>(elementsList, new TreeMap()); + assertReference(); + } + + private void init() { + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size() + 1); + testList.add(index, addRandom(index)); // can be optimized + } + assertReference(); + } + + private Long addRandom() { + while (true) { + Long value = random.nextLong(); + if (elementsSet.add(value)) { + elementsList.add(value); + return value; + } + } + } + + private Long addRandom(int index) { + while (true) { + Long value = random.nextLong(); + if (elementsSet.add(value)) { + elementsList.add(index, value); + return value; + } + } + } + + private Long getRandomExisting() { + if (elementsList.isEmpty()) { + return null; + } + return elementsList.get(random.nextInt(elementsList.size())); + } + + private Long getRandomNotExisting() { + while (true) { + Long value = random.nextLong(); + if (!elementsSet.contains(value)) { + return value; + } + } + } + + private Long getRandomRemoved() { + if (removedList.isEmpty()) { + return null; + } + return removedList.get(random.nextInt(removedList.size())); + } + + private Long removeRandomValue() { + if (elementsList.isEmpty()) { + return null; + } + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + elementsSet.remove(value); + elementsList.remove(index); + removedList.add(value); + return value; + } + + private int removeRandomIndex() { + if (elementsList.isEmpty()) { + return -1; + } + int index = random.nextInt(elementsList.size()); + removeByIndex(index); + return index; + } + + private void removeByIndex(final int index) { + Long value = elementsList.get(index); + elementsSet.remove(value); + elementsList.remove(index); + removedList.add(value); + } + + private void assertReference() { + assertEquals(elementsList, testList); + assertEquals(elementsSet, testList.uniqueValues()); + testList.assertConsistent(); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java new file mode 100644 index 0000000000..26afb9cfe1 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java @@ -0,0 +1,377 @@ +package org.apache.commons.collections4.list; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.*; + +import static org.junit.Assert.*; + +@RunWith(Parameterized.class) +public class IndexedTreeListSetRandomizedTest { + + private Random random; + private Set elementsSet; + private List elementsList; + private List removedList; + + private IndexedTreeListSet testListSet; + + private int seed; + private int iterations; + + public IndexedTreeListSetRandomizedTest(int seed, int iterations) { + this.seed = seed; + this.iterations = iterations; + } + + @Before + public void setUp() throws Exception { + random = new Random(seed); + elementsSet = new HashSet<>(); + elementsList = new ArrayList<>(); + removedList = new ArrayList<>(); + testListSet = new IndexedTreeListSet<>(); + } + + @Parameterized.Parameters(name = "{0} {1}") + public static Collection parameters() { + return Arrays.asList(new Object[][] { + {9999, 1}, + {9999, 2}, + {9999, 3}, + {9999, 4}, + {9999, 5}, + {9999, 10}, + {9999, 100}, + {9999, 1000}, +// {9999, 10000}, + }); + } + +// @Parameterized.Parameters(name = "{0} {1}") +// public static Collection parameters() { +// ArrayList params = new ArrayList(); +// Random r = new Random(); +// for (int i = 0; i < 1000; i++) { +// params.add(new Object[] {r.nextInt(), r.nextInt(1 << (r.nextInt(12))) + 1}); +// } +// return params; +// } + + @Test + public void addToTail() throws Exception { + for (int i = 0; i < iterations; i++) { + testListSet.add(addRandom()); + assertReference(); + } + } + + @Test + public void addToHead() throws Exception { + for (int i = 0; i < iterations; i++) { + testListSet.add(0, addRandom(0)); + assertReference(); + } + } + + @Test + public void addToMiddle() throws Exception { + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size() + 1); + testListSet.add(index, addRandom(index)); + assertReference(); + } + } + + @Test + public void setNewValue() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size()); + final Long value = elementsList.get(index); + elementsSet.remove(value); + final long newValue = random.nextLong(); + elementsSet.add(newValue); + elementsList.set(index, newValue); + final Long oldValue = testListSet.set(index, newValue); + assertEquals(value, oldValue); + assertReference(); + } + } + + @Test + public void exceptionOnSetNewValue() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int indexFrom = random.nextInt(elementsList.size()); + int indexTo = random.nextInt(elementsList.size()); + final Long value = elementsList.get(indexFrom); + final Long oldValue = testListSet.set(indexTo, value); + assertEquals(elementsList.get(indexTo), oldValue); + if (indexFrom != indexTo) { + elementsSet.remove(elementsList.get(indexTo)); + elementsList.set(indexTo, elementsList.get(indexFrom)); + elementsList.remove(indexFrom); + } + assertReference(); + } + } + + @Test + public void removeByIndex() throws Exception { + init(); + while (!testListSet.isEmpty()) { + int index = removeRandomIndex(); + testListSet.remove(index); + assertReference(); + } + } + + @Test + public void removeByValue() throws Exception { + init(); + while (!testListSet.isEmpty()) { + Long value = removeRandomValue(); + assertTrue(testListSet.contains(value)); + testListSet.remove(value); + assertFalse(testListSet.contains(value)); + assertReference(); + } + } + + @Test + public void removeFirstLast() throws Exception { + init(); + while (!testListSet.isEmpty()) { + removeByIndex(0); + testListSet.remove(0); + if (testListSet.size() > 0) { + removeByIndex(testListSet.size() - 1); + testListSet.remove(testListSet.size() - 1); + } + assertReference(); + } + } + + @Test + public void contains() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + assertTrue(testListSet.contains(getRandomExisting())); + assertFalse(testListSet.contains(getRandomNotExisting())); + } + assertReference(); + } + + @Test + public void get() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + assertEquals(value, testListSet.get(index)); + } + assertReference(); + } + + @Test + public void indexOf() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + assertEquals(index, testListSet.indexOf(value)); + } + assertReference(); + } + + @Test + public void lastIndexOf() throws Exception { + init(); + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + assertEquals(index, testListSet.lastIndexOf(value)); + } + assertReference(); + } + + @Test + public void addExisting() throws Exception { + for (int i = 0; i < iterations; i++) { + testListSet.add(addRandom()); + testListSet.add(getRandomExisting()); + testListSet.add(random.nextInt(elementsList.size() + 1), getRandomExisting()); + assertReference(); + } + } + + @Test + public void addContainsIndexOfRemove() throws Exception { + init(); + while (!testListSet.isEmpty()) { + // add + int index = random.nextInt(elementsList.size() + 1); + testListSet.add(index, addRandom(index)); + + // contains + assertTrue(testListSet.contains(getRandomExisting())); + assertFalse(testListSet.contains(getRandomNotExisting())); + + // indexOf + index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + assertEquals(value, testListSet.get(index)); + assertEquals(index, testListSet.indexOf(value)); + + // remove + final Long valueToRemove = removeRandomValue(); + assertTrue(testListSet.remove(valueToRemove)); + final int indexToRemove = removeRandomIndex(); + final Long removedValue = testListSet.get(indexToRemove); + assertEquals(removedValue, testListSet.remove(indexToRemove)); + + // check add nulls + try { + testListSet.add(null); + fail("No exception on adding null"); + } catch (NullPointerException e) {} + try { + testListSet.add(random.nextInt(elementsList.size() + 1), null); + fail("No exception on adding null"); + } catch (NullPointerException e) {} + // check not existing indexOf + assertEquals(-1, testListSet.indexOf(random.nextLong())); + assertEquals(-1, testListSet.lastIndexOf(random.nextLong())); + assertFalse(testListSet.remove(random.nextLong())); + + + assertReference(); + } + } + + @Test + public void toArray() { + init(); + + for (int i = 0; i < iterations; i++) { + testListSet.add(addRandom()); + testListSet.remove(removeRandomValue()); + assertReference(); + } + + assertArrayEquals(elementsList.toArray(), testListSet.toArray()); + assertArrayEquals(elementsList.toArray(new Long[0]), testListSet.toArray(new Long[0])); + } + + @Test + public void clear() { + init(); + testListSet.clear(); + elementsList.clear(); + elementsSet.clear(); + assertReference(); + } + + @Test + public void constructorWithColection() { + init(); + testListSet = new IndexedTreeListSet<>(elementsList); + assertReference(); + } + + @Test + public void constructorWithTreeMap() { + init(); + testListSet = new IndexedTreeListSet<>(elementsList, new TreeMap()); + assertReference(); + } + + private void init() { + for (int i = 0; i < iterations; i++) { + int index = random.nextInt(elementsList.size() + 1); + testListSet.add(index, addRandom(index)); // can be optimized + } + assertReference(); + } + + private Long addRandom() { + while (true) { + Long value = random.nextLong(); + if (elementsSet.add(value)) { + elementsList.add(value); + return value; + } + } + } + + private Long addRandom(int index) { + while (true) { + Long value = random.nextLong(); + if (elementsSet.add(value)) { + elementsList.add(index, value); + return value; + } + } + } + + private Long getRandomExisting() { + if (elementsList.isEmpty()) { + return null; + } + return elementsList.get(random.nextInt(elementsList.size())); + } + + private Long getRandomNotExisting() { + while (true) { + Long value = random.nextLong(); + if (!elementsSet.contains(value)) { + return value; + } + } + } + + private Long getRandomRemoved() { + if (removedList.isEmpty()) { + return null; + } + return removedList.get(random.nextInt(removedList.size())); + } + + private Long removeRandomValue() { + if (elementsList.isEmpty()) { + return null; + } + int index = random.nextInt(elementsList.size()); + Long value = elementsList.get(index); + elementsSet.remove(value); + elementsList.remove(index); + removedList.add(value); + return value; + } + + private int removeRandomIndex() { + if (elementsList.isEmpty()) { + return -1; + } + int index = random.nextInt(elementsList.size()); + removeByIndex(index); + return index; + } + + private void removeByIndex(final int index) { + Long value = elementsList.get(index); + elementsSet.remove(value); + elementsList.remove(index); + removedList.add(value); + } + + private void assertReference() { + assertEquals(elementsSet, testListSet); + assertEquals(elementsList, testListSet); + testListSet.assertConsistent(); + } +} \ No newline at end of file From ef93b34cd7deb25d6817675fa45d738e42bf7ba9 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 16:46:56 +0200 Subject: [PATCH 05/12] Implements OrderedIterator --- .../commons/collections4/list/AbstractIndexedTreeList.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java index 89ac3226cc..466bb64f20 100644 --- a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java @@ -16,6 +16,8 @@ */ package org.apache.commons.collections4.list; +import org.apache.commons.collections4.OrderedIterator; + import java.util.*; /** @@ -833,7 +835,7 @@ public String toString() { /** * A list iterator over the linked list. */ - private class TreeListIterator implements ListIterator { // TODO implements ListIterator, OrderedIterator + private class TreeListIterator implements ListIterator, OrderedIterator { /** The parent list */ private final AbstractIndexedTreeList parent; /** From a5a97e7b13b439e51ec348a35ea5c6d90ba4a960 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 16:48:43 +0200 Subject: [PATCH 06/12] Added license --- .../IndexedTreeListIteratorRandomizedTest.java | 16 ++++++++++++++++ .../list/IndexedTreeListRandomizedTest.java | 16 ++++++++++++++++ .../list/IndexedTreeListSetRandomizedTest.java | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java index a5b82fce6f..b1f4dfde3d 100644 --- a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListIteratorRandomizedTest.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.commons.collections4.list; import org.junit.Before; diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java index 5ef6e2dad7..bef847cea5 100644 --- a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListRandomizedTest.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.commons.collections4.list; import org.junit.Before; diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java index 26afb9cfe1..fede4c6dbc 100644 --- a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.commons.collections4.list; import org.junit.Before; From 0af1afe5c1a95d851446c73c5e13c757dc175085 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 16:53:02 +0200 Subject: [PATCH 07/12] Test renamed --- .../collections4/list/IndexedTreeListSetRandomizedTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java index fede4c6dbc..f65086d5e1 100644 --- a/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java +++ b/src/test/java/org/apache/commons/collections4/list/IndexedTreeListSetRandomizedTest.java @@ -119,7 +119,7 @@ public void setNewValue() throws Exception { } @Test - public void exceptionOnSetNewValue() throws Exception { + public void removeElementOnSetExistingValue() throws Exception { init(); for (int i = 0; i < iterations; i++) { int indexFrom = random.nextInt(elementsList.size()); From ff3f14853eca263f92c2d5fbc78261b3aeff8674 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 17:07:20 +0200 Subject: [PATCH 08/12] Added myself into contributor list --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 72973b6028..52ef2cc60e 100644 --- a/pom.xml +++ b/pom.xml @@ -441,6 +441,9 @@ Claude Warren + + Oleksandr Maksymenko + From 8d2cb1e471422055512ee7c99af740d6b52325d2 Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 18:02:41 +0200 Subject: [PATCH 09/12] Improved Javadocs --- .../list/AbstractIndexedTreeList.java | 15 +++++++++--- .../collections4/list/IndexedTreeList.java | 24 ++++++++++++++----- .../collections4/list/IndexedTreeListSet.java | 24 ++++++++++++------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java index 466bb64f20..be1738336a 100644 --- a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java @@ -152,7 +152,7 @@ public void add(final int index, final E obj) { * @param index index at which to insert the first element from the * specified collection * @param collection collection containing elements to be added to this list - * @return true if this list changed as a result of the call + * @return true if this list changed as a result of the call */ @Override public boolean addAll(final int index, final Collection collection) { @@ -212,7 +212,7 @@ public E remove(final int index) { * Removes the element at the specified index. * * @param o element to be removed from this list, if present - * @return true if this list contained the specified element + * @return true if this list contained the specified element */ @Override public boolean remove(Object o) { @@ -238,6 +238,8 @@ public void clear() { /** * Creates a {@link Spliterator} over the elements in this list. + * + * @return spliterator */ @Override public Spliterator spliterator() { @@ -266,17 +268,24 @@ private void setRoot(AVLNode node) { } /** - * Check if object can be added to list (e.g. check uniquess) + * Check if object can be added to list (e.g. check uniqueness) + * + * @param e element to check if it can be added to collection + * @return true if specified element can be added to collection */ abstract protected boolean canAdd(E e); /** * Add node to nodeMap. + * + * @param node node to add */ abstract protected void addNode(AVLNode node); /** * Remove node from nodeMap. + * + * @param node node to remove */ abstract protected void removeNode(AVLNode node); diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java index b0afb60150..60370466d1 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java @@ -20,13 +20,16 @@ import java.util.function.Function; /** + *

    * As a List this data structure stores order of elements and * provides access by index. It's is optimised for fast insertions, removals * and searching by any index or object in the list. - *

    + *

    + *

    * IndexedTreeList can be suitable for tasks which requires fast modification in the * middle of a list and provides fast contains and indexOf. - *

    + *

    + *

    * Get by index is O(log n). * Insert (head, tail, middle) and remove(by index or by value) are O(log n) * in most cases but can be up to O((log n) ^ 2) in cases when List contains big @@ -34,16 +37,19 @@ * O((log n) * (1 + log m)) where m is amount of elements equal to inserted/removed. * indexOf is O(log n). * Contains is O(1) or O(log n) depending on Map implementation. - *

    + *

    + *

    * Internally it uses Map (HashMap by default) and AVL tree. * HashMap can be replaced to TreeMap, this will slightly reduce overall performance * but will eliminate problems with hash collisions and hash table resizing. * Using TreeMap with custom Comparator will provide indexOf by custom criteria. * Using IdentityHashMap will provide indexOf by object's identity. - *

    + *

    + *

    * Objects equality is checked by Map, so objects should be immutable for Map * consistency. - *

    + *

    + *

    * Code is based on apache common collections TreeList. * Comparing to TreeList this data structure: *

      @@ -56,6 +62,7 @@ * As this implementation is slightly slower and require more memory it's recommended to use * TreeList in cases when no searching is required or IndexedTreeListSet * in cases where unique elements should be stored. + *

      * * @author Aleksandr Maksymenko */ @@ -195,7 +202,9 @@ public void clear() { } /** - * Get unordered Set o unique values. + * Get unordered Set of unique values. + * + * @return unordered Set of unique values */ public Set uniqueValues() { return nodeMap.keySet(); @@ -203,6 +212,9 @@ public Set uniqueValues() { /** * Check if set does not contains an object. + * + * @param e element to check if it can be added to collection + * @return true if specified element can be added to collection */ @Override protected boolean canAdd(E e) { diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java index e02fe19b16..0df3c32e41 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java @@ -19,27 +19,34 @@ import java.util.*; /** + *

      * Data structure which implements both List and Set. - *

      + *

      + *

      * As a List this data structure stores order of elements and * provides access by index. It's is optimised for fast insertions, removals * and searching by any index or object in the list. * As a Set this data structure stores unique elements only. - *

      + *

      + *

      * IndexedTreeListSet can be suitable for tasks which requires fast modification * in the middle of a list and provides fast contains and indexOf operations. - *

      + *

      + *

      * Get by index, insert (head, tail, middle), remove(by index or by value) * and indexOf are all O(log n). Contains is O(1) or O(log n) depending on Map * implementation. - *

      + *

      + *

      * Internally it uses Map (HashMap by default) and AVL tree. * HashMap can be replaced to TreeMap, this will slightly reduce overall performance * but will eliminate problems with hash collisions and hash table resizing. - *

      + *

      + *

      * Objects equality is checked by Map, so objects should be immutable for Map * consistency. - *

      + *

      + *

      * Code is based on apache common collections TreeList. * Comparing to TreeList this data structure: *

        @@ -48,6 +55,7 @@ *
      • Requires more memory, however it's still O(n).
      • *
      • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
      • *
      + *

      * * @author Aleksandr Maksymenko */ @@ -128,7 +136,7 @@ public int lastIndexOf(final Object object) { /** * Returns an unmodifiable view of the portion of this list between the specified - * fromIndex, inclusive, and toIndex, exclusive. + * fromIndex, inclusive, and toIndex, exclusive. * * @param fromIndex low endpoint (inclusive) of the subList * @param toIndex high endpoint (exclusive) of the subList @@ -157,7 +165,7 @@ public boolean contains(final Object object) { * * @param index the index to set * @param obj the object to store at the specified index - * @return + * @return previous value */ @Override public E set(int index, final E obj) { From 3d8787f3c24bb21e032081eb308e1f8c2383ea3c Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 18:18:02 +0200 Subject: [PATCH 10/12] Improved Javadocs --- .../org/apache/commons/collections4/list/IndexedTreeList.java | 4 +++- .../apache/commons/collections4/list/IndexedTreeListSet.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java index 60370466d1..82dac4aeb5 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java @@ -24,8 +24,8 @@ * As a List this data structure stores order of elements and * provides access by index. It's is optimised for fast insertions, removals * and searching by any index or object in the list. - *

      *

      + *

      * IndexedTreeList can be suitable for tasks which requires fast modification in the * middle of a list and provides fast contains and indexOf. *

      @@ -51,6 +51,7 @@ *

      *

      * Code is based on apache common collections TreeList. + *

      * Comparing to TreeList this data structure: *
        *
      • Has slightly slower insertion/removing operations, O(log n) in most cases, O((log n) ^ 2) in @@ -59,6 +60,7 @@ *
      • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
      • *
      * + *

      * As this implementation is slightly slower and require more memory it's recommended to use * TreeList in cases when no searching is required or IndexedTreeListSet * in cases where unique elements should be stored. diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java index 0df3c32e41..e7b2684649 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java @@ -48,6 +48,7 @@ *

      *

      * Code is based on apache common collections TreeList. + *

      * Comparing to TreeList this data structure: *
        *
      • Contains unique elements
      • @@ -55,7 +56,6 @@ *
      • Requires more memory, however it's still O(n).
      • *
      • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
      • *
      - *

      * * @author Aleksandr Maksymenko */ From b6f3201825069140e4afc2ef43b761aa10d9df3b Mon Sep 17 00:00:00 2001 From: Aleksandr Maksymenko Date: Sun, 26 Jan 2020 18:30:33 +0200 Subject: [PATCH 11/12] Improved Javadocs --- .../commons/collections4/list/AbstractIndexedTreeList.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java index be1738336a..fd19935d0f 100644 --- a/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/AbstractIndexedTreeList.java @@ -78,6 +78,8 @@ public Iterator iterator() { /** * Gets a ListIterator over the list. + * WARN: ListIterator may not update list on set(obj) and add(obj) methods if collection is + * IndexedTreeListSet and obj is already in collection. * * @return the new iterator */ @@ -89,6 +91,8 @@ public ListIterator listIterator() { /** * Gets a ListIterator over the list. + * WARN: ListIterator may not update list on set(obj) and add(obj) methods if collection is + * IndexedTreeListSet and obj is already in collection. * * @param fromIndex the index to start from * @return the new iterator From dcd438e453a446897deaaa4ebc6ac3fb82149af5 Mon Sep 17 00:00:00 2001 From: Oleksandr Maksymenko Date: Sat, 8 Feb 2020 21:01:51 +0200 Subject: [PATCH 12/12] Improved Javadocs --- .../org/apache/commons/collections4/list/IndexedTreeList.java | 4 ++-- .../apache/commons/collections4/list/IndexedTreeListSet.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java index 82dac4aeb5..572d9d109f 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeList.java @@ -55,8 +55,8 @@ * Comparing to TreeList this data structure: *
        *
      • Has slightly slower insertion/removing operations, O(log n) in most cases, O((log n) ^ 2) in - * worst cases (if TreeMap is used).
      • - *
      • Requires more memory, however it's still O(n).
      • + * worst cases (if TreeMap is used) + *
      • Requires more memory, however it's still O(n)
      • *
      • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
      • *
      * diff --git a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java index e7b2684649..374046fa18 100644 --- a/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java +++ b/src/main/java/org/apache/commons/collections4/list/IndexedTreeListSet.java @@ -53,8 +53,10 @@ *
        *
      • Contains unique elements
      • *
      • Has almost the same or slightly slower insertion/removing operations, O(log n)
      • - *
      • Requires more memory, however it's still O(n).
      • + *
      • Requires more memory, however it's still O(n)
      • *
      • Has greatly improved contains and indexOf operations, O(log n) while TreeList has O(n)
      • + *
      • Has limited usage of set(index, object) method due to requirement of object uniqueness
      • + *
      • Sorting may not work correctly as same object could be set in 2 places during objects swapping
      • *
      * * @author Aleksandr Maksymenko