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 extends E> 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 extends E> 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 extends E> 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 extends E> 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 super Integer> 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 super Integer> 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 super Integer> 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 super Integer> 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 extends E> coll) {
+ public IndexedTreeListSet(final Collection extends E> coll) {
this(coll, new HashMap<>());
}
@@ -94,8 +97,9 @@ public TreeListSet(final Collection extends E> 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 extends E> coll, final Map map) {
+ public IndexedTreeListSet(final Collection extends E> 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 super Integer> l) {
@@ -102,8 +102,8 @@ public static void benchmark(final List super Integer> 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 extends E> 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 extends E> coll) {
*/
public IndexedTreeListSet(final Collection extends E> 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 super Integer> 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 super Integer> 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
+ *
* 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)