diff --git a/src/java/org/apache/commons/collections/list/AbstractLinkedList.java b/src/java/org/apache/commons/collections/list/AbstractLinkedList.java index 1828c223a..88b743edd 100644 --- a/src/java/org/apache/commons/collections/list/AbstractLinkedList.java +++ b/src/java/org/apache/commons/collections/list/AbstractLinkedList.java @@ -1,5 +1,5 @@ /* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/list/AbstractLinkedList.java,v 1.1 2003/12/11 00:18:06 scolebourne Exp $ + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/list/AbstractLinkedList.java,v 1.2 2003/12/24 01:15:40 scolebourne Exp $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -75,20 +75,18 @@ import org.apache.commons.collections.OrderedIterator; * An abstract implementation of a linked list which provides numerous points for * subclasses to override. *

- * Overridable methods are provided to change the storage node, and to change how - * entries are added to and removed from the map. Hopefully, all you need for - * unusual subclasses is here. - *

- * This class currently extends AbstractList, but do not rely on that. It may change. + * Overridable methods are provided to change the storage node and to change how + * nodes are added to and removed. Hopefully, all you need for unusual subclasses + * is here. * * @since Commons Collections 3.0 - * @version $Revision: 1.1 $ $Date: 2003/12/11 00:18:06 $ + * @version $Revision: 1.2 $ $Date: 2003/12/24 01:15:40 $ * * @author Rich Dougherty * @author Phil Steitz * @author Stephen Colebourne */ -public abstract class AbstractLinkedList extends AbstractList implements List { +public abstract class AbstractLinkedList implements List { /* * Implementation notes: @@ -99,7 +97,6 @@ public abstract class AbstractLinkedList extends AbstractList implements List { * - a modification count is kept, with the same semantics as * {@link java.util.LinkedList}. * - respects {@link AbstractList#modCount} - * - only extends AbstractList for subList() - TODO */ /** @@ -110,8 +107,8 @@ public abstract class AbstractLinkedList extends AbstractList implements List { protected transient Node header; /** The size of the list */ protected transient int size; -// /** Modification count for iterators */ -// protected transient int modCount; + /** Modification count for iterators */ + protected transient int modCount; /** * Constructor that does nothing intended for deserialization. @@ -164,11 +161,11 @@ public abstract class AbstractLinkedList extends AbstractList implements List { } public ListIterator listIterator() { - return new LinkedListIterator(); + return new LinkedListIterator(this, 0); } - public ListIterator listIterator(int startIndex) { - return new LinkedListIterator(startIndex); + public ListIterator listIterator(int fromIndex) { + return new LinkedListIterator(this, fromIndex); } //----------------------------------------------------------------------- @@ -208,6 +205,40 @@ public abstract class AbstractLinkedList extends AbstractList implements List { return true; } + //----------------------------------------------------------------------- + public Object[] toArray() { + return toArray(new Object[size]); + } + + public Object[] toArray(Object[] array) { + // Extend the array if needed + if (array.length < size) { + Class componentType = array.getClass().getComponentType(); + array = (Object[]) Array.newInstance(componentType, size); + } + // Copy the values into the array + int i = 0; + for (Node node = header.next; node != header; node = node.next, i++) { + array[i] = node.value; + } + // Set the value after the last value to null + if (array.length > size) { + array[size] = null; + } + return array; + } + + /** + * Gets a sublist of the main list. + * + * @param fromIndexInclusive the index to start from + * @param toIndexExclusive the index to end at + * @return the new sublist + */ + public List subList(int fromIndexInclusive, int toIndexExclusive) { + return new LinkedSubList(this, fromIndexInclusive, toIndexExclusive); + } + //----------------------------------------------------------------------- public boolean add(Object value) { addLast(value); @@ -278,7 +309,7 @@ public abstract class AbstractLinkedList extends AbstractList implements List { public Object set(int index, Object value) { Node node = getNode(index, false); Object oldValue = node.value; - node.value = value; + updateNode(node, value); return oldValue; } @@ -286,30 +317,6 @@ public abstract class AbstractLinkedList extends AbstractList implements List { removeAllNodes(); } - //----------------------------------------------------------------------- - public Object[] toArray() { - return toArray(new Object[size]); - } - - public Object[] toArray(Object[] array) { - // Extend the array if needed - if (array.length < size) { - Class componentType = array.getClass().getComponentType(); - array = (Object[]) Array.newInstance(componentType, size); - } - // Copy the values into the array - Node node = header.next; - for (int i = 0; i < size; i++) { - array[i] = node.value; - node = node.next; - } - // Set the value after the last value to null - if (array.length > size) { - array[size] = null; - } - return array; - } - //----------------------------------------------------------------------- public Object getFirst() { Node node = header.next; @@ -327,12 +334,14 @@ public abstract class AbstractLinkedList extends AbstractList implements List { return node.value; } - public void addFirst(Object o) { + public boolean addFirst(Object o) { addNodeAfter(header, o); + return true; } - public void addLast(Object o) { + public boolean addLast(Object o) { addNodeBefore(header, o); + return true; } public Object removeFirst() { @@ -423,6 +432,18 @@ public abstract class AbstractLinkedList extends AbstractList implements List { return (value1 == value2 || (value1 == null ? false : value1.equals(value2))); } + /** + * Updates the node with a new value. + * This implementation sets the value on the node. + * Subclasses can override this to record the change. + * + * @param node node to update + * @param value new value of the node + */ + protected void updateNode(Node node, Object value) { + node.value = value; + } + /** * Creates a new node with previous, next and element all set to null. * This implementation creates a new empty Node. @@ -439,42 +460,54 @@ public abstract class AbstractLinkedList extends AbstractList implements List { * This implementation creates a new Node with data. * Subclasses can override this to create a different class. * - * @param previous node to precede the new node - * @param next node to follow the new node * @param value value of the new node */ - protected Node createNode(Node previous, Node next, Object value) { - return new Node(previous, next, value); + protected Node createNode(Object value) { + return new Node(value); } /** * Creates a new node with the specified object as its * value and inserts it before node. + *

+ * This implementation uses {@link #createNode(Object)} and {@link #addNode(Node, Node)}. * * @param node node to insert before * @param value value of the newly added node * @throws NullPointerException if node is null */ protected void addNodeBefore(Node node, Object value) { - Node newNode = createNode(node.previous, node, value); - node.previous.next = newNode; - node.previous = newNode; - size++; - modCount++; + Node newNode = createNode(value); + addNode(newNode, node); } /** * Creates a new node with the specified object as its * value and inserts it after node. + *

+ * This implementation uses {@link #createNode(Object)} and {@link #addNode(Node, Node)}. * * @param node node to insert after * @param value value of the newly added node * @throws NullPointerException if node is null */ protected void addNodeAfter(Node node, Object value) { - Node newNode = createNode(node, node.next, value); - node.next.previous = newNode; - node.next = newNode; + Node newNode = createNode(value); + addNode(newNode, node.next); + } + + /** + * Inserts a new node into the list. + * + * @param nodeToInsert new node to insert + * @param insertBeforeNode node to insert before + * @throws NullPointerException if either node is null + */ + protected void addNode(Node nodeToInsert, Node insertBeforeNode) { + nodeToInsert.next = insertBeforeNode; + nodeToInsert.previous = insertBeforeNode.previous; + insertBeforeNode.previous.next = nodeToInsert; + insertBeforeNode.previous = nodeToInsert; size++; modCount++; } @@ -545,6 +578,26 @@ public abstract class AbstractLinkedList extends AbstractList implements List { return node; } + //----------------------------------------------------------------------- + /** + * Creates an iterator for the sublist. + * + * @param subList the sublist to get an iterator for + */ + protected Iterator createSubListIterator(LinkedSubList subList) { + return createSubListListIterator(subList, 0); + } + + /** + * Creates a list iterator for the sublist. + * + * @param subList the sublist to get an iterator for + * @param fromIndex the index to start from, relative to the sublist + */ + protected ListIterator createSubListListIterator(LinkedSubList subList, int fromIndex) { + return new LinkedSubListIterator(subList, fromIndex); + } + //----------------------------------------------------------------------- /** * Serializes the data held in this object to the stream specified. @@ -577,28 +630,35 @@ public abstract class AbstractLinkedList extends AbstractList implements List { //----------------------------------------------------------------------- /** * A node within the linked list. - * - * @author Rich Dougherty - * @author Stephen Colebourne */ protected static class Node { /** A pointer to the node before this node */ - public Node previous; + protected Node previous; /** A pointer to the node after this node */ - public Node next; + protected Node next; /** The object contained within this node */ - public Object value; + protected Object value; /** * Constructs a new header node. */ - public Node() { + protected Node() { super(); previous = this; next = this; } + /** + * Constructs a new node. + * + * @param value the value to store + */ + protected Node(Object value) { + super(); + this.value = value; + } + /** * Constructs a new node. * @@ -606,7 +666,7 @@ public abstract class AbstractLinkedList extends AbstractList implements List { * @param next the next node in the list * @param value the value to store */ - public Node(Node previous, Node next, Object value) { + protected Node(Node previous, Node next, Object value) { super(); this.previous = previous; this.next = next; @@ -617,16 +677,17 @@ public abstract class AbstractLinkedList extends AbstractList implements List { //----------------------------------------------------------------------- /** * A list iterator over the linked list. - * - * @author Rich Dougherty */ - protected class LinkedListIterator implements ListIterator, OrderedIterator { + protected static class LinkedListIterator implements ListIterator, OrderedIterator { + + /** The parent list */ + protected final AbstractLinkedList list; /** * The node that will be returned by {@link #next()}. If this is equal * to {@link #marker} then there are no more values to return. */ - protected Node nextNode; + protected Node next; /** * The index of {@link #nextNode}. @@ -641,7 +702,7 @@ public abstract class AbstractLinkedList extends AbstractList implements List { * Should be accesed through {@link #getLastNodeReturned()} to enforce * this behaviour. */ - protected Node lastNodeReturned; + protected Node current; /** * The modification count that the list is expected to have. If the list @@ -651,24 +712,18 @@ public abstract class AbstractLinkedList extends AbstractList implements List { */ protected int expectedModCount; - /** - * Create a ListIterator for a list, starting at the first value in - * the list. - */ - public LinkedListIterator() throws IndexOutOfBoundsException { - this(0); - } - /** * Create a ListIterator for a list. * - * @param startIndex The index to start at. + * @param parent the parent list + * @param fromIndex the index to start at */ - public LinkedListIterator(int startIndex) throws IndexOutOfBoundsException { + public LinkedListIterator(AbstractLinkedList parent, int fromIndex) throws IndexOutOfBoundsException { super(); - expectedModCount = modCount; - nextNode = getNode(startIndex, true); - nextIndex = startIndex; + this.list = parent; + this.expectedModCount = list.modCount; + this.next = list.getNode(fromIndex, true); + this.nextIndex = fromIndex; } /** @@ -678,9 +733,8 @@ public abstract class AbstractLinkedList extends AbstractList implements List { * @throws ConcurrentModificationException If the list's modification * count isn't the value that was expected. */ - protected void checkModCount() - throws ConcurrentModificationException { - if (modCount != expectedModCount) { + protected void checkModCount() { + if (list.modCount != expectedModCount) { throw new ConcurrentModificationException(); } } @@ -693,14 +747,14 @@ public abstract class AbstractLinkedList extends AbstractList implements List { * with {@link #remove()} or a new node added with {@link #add(Object)}. */ protected Node getLastNodeReturned() throws IllegalStateException { - if (lastNodeReturned == null) { + if (current == null) { throw new IllegalStateException(); } - return lastNodeReturned; + return current; } public boolean hasNext() { - return nextNode != header; + return next != list.header; } public Object next() { @@ -709,15 +763,15 @@ public abstract class AbstractLinkedList extends AbstractList implements List { throw new NoSuchElementException("No element at index " + nextIndex + "."); } - Object value = nextNode.value; - lastNodeReturned = nextNode; - nextNode = nextNode.next; + Object value = next.value; + current = next; + next = next.next; nextIndex++; return value; } public boolean hasPrevious() { - return nextNode.previous != header; + return next.previous != list.header; } public Object previous() { @@ -725,9 +779,9 @@ public abstract class AbstractLinkedList extends AbstractList implements List { if (!hasPrevious()) { throw new NoSuchElementException("Already at start of list."); } - nextNode = nextNode.previous; - Object value = nextNode.value; - lastNodeReturned = nextNode; + next = next.previous; + Object value = next.value; + current = next; nextIndex--; return value; } @@ -737,30 +791,192 @@ public abstract class AbstractLinkedList extends AbstractList implements List { } public int previousIndex() { - return nextIndex - 1; + // not normally overridden, as relative to nextIndex() + return nextIndex() - 1; } public void remove() { checkModCount(); - removeNode(getLastNodeReturned()); - lastNodeReturned = null; + list.removeNode(getLastNodeReturned()); + current = null; nextIndex--; expectedModCount++; } - public void set(Object o) { + public void set(Object obj) { checkModCount(); - getLastNodeReturned().value = o; + getLastNodeReturned().value = obj; } - public void add(Object o) { + public void add(Object obj) { checkModCount(); - addNodeBefore(nextNode, o); - lastNodeReturned = null; + list.addNodeBefore(next, obj); + current = null; nextIndex++; expectedModCount++; } } + //----------------------------------------------------------------------- + /** + * A list iterator over the linked sub list. + */ + protected static class LinkedSubListIterator extends LinkedListIterator { + + /** The parent list */ + protected final LinkedSubList sub; + + protected LinkedSubListIterator(LinkedSubList sub, int startIndex) { + super(sub.list, startIndex + sub.offset); + this.sub = sub; + } + + public boolean hasNext() { + return (nextIndex() < sub.size); + } + + public boolean hasPrevious() { + return (previousIndex() >= 0); + } + + public int nextIndex() { + return (super.nextIndex() - sub.offset); + } + + public void add(Object obj) { + super.add(obj); + sub.expectedModCount = list.modCount; + sub.size++; + } + + public void remove() { + super.remove(); + sub.expectedModCount = list.modCount; + sub.size--; + } + } + + //----------------------------------------------------------------------- + /** + * The sublist implementation for AbstractLinkedList. + */ + protected static class LinkedSubList extends AbstractList { + /** The main list */ + private AbstractLinkedList list; + /** Offset from the main list */ + private int offset; + /** Sublist size */ + private int size; + /** Sublist modCount */ + private int expectedModCount; + + protected LinkedSubList(AbstractLinkedList list, int fromIndex, int toIndex) { + if (fromIndex < 0) { + throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); + } + if (toIndex > list.size()) { + throw new IndexOutOfBoundsException("toIndex = " + toIndex); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + this.list = list; + this.offset = fromIndex; + this.size = toIndex - fromIndex; + this.expectedModCount = list.modCount; + } + + public int size() { + checkModCount(); + return size; + } + + public Object get(int index) { + rangeCheck(index, size); + checkModCount(); + return list.get(index + offset); + } + + public void add(int index, Object obj) { + rangeCheck(index, size + 1); + checkModCount(); + list.add(index + offset, obj); + expectedModCount = list.modCount; + size++; + LinkedSubList.this.modCount++; + } + + public Object remove(int index) { + rangeCheck(index, size); + checkModCount(); + Object result = list.remove(index + offset); + expectedModCount = list.modCount; + size--; + LinkedSubList.this.modCount++; + return result; + } + + public boolean addAll(Collection coll) { + return addAll(size, coll); + } + + public boolean addAll(int index, Collection coll) { + rangeCheck(index, size + 1); + int cSize = coll.size(); + if (cSize == 0) { + return false; + } + + checkModCount(); + list.addAll(offset + index, coll); + expectedModCount = list.modCount; + size += cSize; + LinkedSubList.this.modCount++; + return true; + } + + public Object set(int index, Object obj) { + rangeCheck(index, size); + checkModCount(); + return list.set(index + offset, obj); + } + + public void clear() { + checkModCount(); + Iterator it = iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + } + + public Iterator iterator() { + checkModCount(); + return list.createSubListIterator(this); + } + + public ListIterator listIterator(final int index) { + rangeCheck(index, size + 1); + checkModCount(); + return list.createSubListListIterator(this, index); + } + + public List subList(int fromIndexInclusive, int toIndexExclusive) { + return new LinkedSubList(list, fromIndexInclusive + offset, toIndexExclusive + offset); + } + + protected void rangeCheck(int index, int beyond) { + if (index < 0 || index >= beyond) { + throw new IndexOutOfBoundsException("Index '" + index + "' out of bounds for size '" + size + "'"); + } + } + + protected void checkModCount() { + if (list.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + } + } diff --git a/src/java/org/apache/commons/collections/list/CursorableLinkedList.java b/src/java/org/apache/commons/collections/list/CursorableLinkedList.java new file mode 100644 index 000000000..5f488edb2 --- /dev/null +++ b/src/java/org/apache/commons/collections/list/CursorableLinkedList.java @@ -0,0 +1,807 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/list/CursorableLinkedList.java,v 1.1 2003/12/24 01:15:40 scolebourne Exp $ + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.collections.list; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * A List implementation with a ListIterator that + * allows concurrent modifications to the underlying list. + *

+ * This implementation supports all of the optional {@link List} operations. + * It extends AbstractLinkedList and thus provides the + * stack/queue/dequeue operations available in {@link java.util.LinkedList}. + *

+ * The main feature of this class is the ability to modify the list and the + * iterator at the same time. Both the {@link #listIterator()} and {@link #cursor()} + * methods provides access to a Cursor instance which extends + * ListIterator. The cursor allows changes to the list concurrent + * with changes to the iterator. Note that the {@link #iterator()} method and + * sublists do not provide this cursor behaviour. + *

+ * The Cursor class is provided partly for backwards compatability + * and partly because it allows the cursor to be directly closed. Closing the + * cursor is optional because references are held via a WeakReference. + * For most purposes, simply modify the iterator and list at will, and then let + * the garbage collector to the rest. + *

+ * Note that this implementation is not synchronized. + * + * @see java.util.LinkedList + * @since Commons Collections 1.0 + * @version $Revision: 1.1 $ $Date: 2003/12/24 01:15:40 $ + * + * @author Rodney Waldhoff + * @author Janek Bogucki + * @author Simon Kitching + * @author Stephen Colebourne + */ +public class CursorableLinkedList extends AbstractLinkedList implements Serializable { + + /** Ensure serialization compatability */ + private static final long serialVersionUID = 8836393098519411393L; + + /** A list of the cursor currently open on this list */ + protected transient List cursors = new ArrayList(); + + //----------------------------------------------------------------------- + /** + * Constructor that creates. + */ + public CursorableLinkedList() { + super(); + init(); // must call init() as use super(); + } + + /** + * Constructor that copies the specified collection + * + * @param coll the collection to copy + */ + public CursorableLinkedList(Collection coll) { + super(coll); + } + + /** + * The equivalent of a default constructor called + * by any constructor and by readObject. + */ + protected void init() { + super.init(); + cursors = new ArrayList(); + } + + //----------------------------------------------------------------------- + /** + * Returns an iterator that does not support concurrent modification. + *

+ * If the underlying list is modified while iterating using this iterator + * a ConcurrentModificationException will occur. + * The cursor behaviour is available via {@link #listIterator()}. + * + * @return a new iterator that does not support concurrent modification + */ + public Iterator iterator() { + return super.listIterator(0); + } + + /** + * Returns a cursor iterator that allows changes to the underlying list in parallel. + *

+ * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

+ * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + * + * @return a new cursor iterator + */ + public ListIterator listIterator() { + return cursor(0); + } + + /** + * Returns a cursor iterator that allows changes to the underlying list in parallel. + *

+ * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

+ * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + * + * @param fromIndex the index to start from + * @return a new cursor iterator + */ + public ListIterator listIterator(int fromIndex) { + return cursor(fromIndex); + } + + /** + * Returns a {@link Cursor} for iterating through the elements of this list. + *

+ * A Cursor is a ListIterator with an additional + * close() method. Calling this method immediately discards the + * references to the cursor. If it is not called, then the garbage collector + * will still remove the reference as it is held via a WeakReference. + *

+ * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

+ * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + *

+ * The {@link #listIterator()} method returns the same as this method, and can + * be cast to a Cursor if the close method is required. + * + * @return a new cursor iterator + */ + public CursorableLinkedList.Cursor cursor() { + return cursor(0); + } + + /** + * Returns a {@link Cursor} for iterating through the elements of this list + * starting from a specified index. + *

+ * A Cursor is a ListIterator with an additional + * close() method. Calling this method immediately discards the + * references to the cursor. If it is not called, then the garbage collector + * will still remove the reference as it is held via a WeakReference. + *

+ * The cursor enables iteration and list changes to occur in any order without + * invalidating the iterator (from one thread). When elements are added to the + * list, an event is fired to all active cursors enabling them to adjust to the + * change in the list. + *

+ * When the "current" (i.e., last returned by {@link ListIterator#next} + * or {@link ListIterator#previous}) element of the list is removed, + * the cursor automatically adjusts to the change (invalidating the + * last returned value such that it cannot be removed). + *

+ * The {@link #listIterator(int)} method returns the same as this method, and can + * be cast to a Cursor if the close method is required. + * + * @param fromIndex the index to start from + * @return a new cursor iterator + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > size()). + */ + public CursorableLinkedList.Cursor cursor(int fromIndex) { + Cursor cursor = new Cursor(this, fromIndex); + registerCursor(cursor); + return cursor; + } + + //----------------------------------------------------------------------- + /** + * Updates the node with a new value. + * This implementation sets the value on the node. + * Subclasses can override this to record the change. + * + * @param node node to update + * @param value new value of the node + */ + protected void updateNode(Node node, Object value) { + super.updateNode(node, value); + broadcastNodeChanged(node); + } + + /** + * Inserts a new node into the list. + * + * @param nodeToInsert new node to insert + * @param insertBeforeNode node to insert before + * @throws NullPointerException if either node is null + */ + protected void addNode(Node nodeToInsert, Node insertBeforeNode) { + super.addNode(nodeToInsert, insertBeforeNode); + broadcastNodeInserted(nodeToInsert); + } + + /** + * Removes the specified node from the list. + * + * @param node the node to remove + * @throws NullPointerException if node is null + */ + protected void removeNode(Node node) { + super.removeNode(node); + broadcastNodeRemoved(node); + } + + /** + * Removes all nodes by iteration. + */ + protected void removeAllNodes() { + if (size() > 0) { + // superclass implementation would break all the iterators + Iterator it = iterator(); + while (it.hasNext()) { + it.next(); + it.remove(); + } + } + } + + //----------------------------------------------------------------------- + /** + * Registers a cursor to be notified of changes to this list. + * + * @param cursor the cursor to register + */ + protected void registerCursor(Cursor cursor) { + // We take this opportunity to clean the cursors list + // of WeakReference objects to garbage-collected cursors. + for (Iterator it = cursors.iterator(); it.hasNext();) { + WeakReference ref = (WeakReference) it.next(); + if (ref.get() == null) { + it.remove(); + } + } + cursors.add(new WeakReference(cursor)); + } + + /** + * Deregisters a cursor from the list to be notified of changes. + * + * @param cursor the cursor to deregister + */ + protected void unregisterCursor(Cursor cursor) { + for (Iterator it = cursors.iterator(); it.hasNext();) { + WeakReference ref = (WeakReference) it.next(); + Cursor cur = (Cursor) ref.get(); + if (cur == null) { + // some other unrelated cursor object has been + // garbage-collected; let's take the opportunity to + // clean up the cursors list anyway.. + it.remove(); + + } else if (cur == cursor) { + ref.clear(); + it.remove(); + break; + } + } + } + + //----------------------------------------------------------------------- + /** + * Informs all of my registered cursors that the specified + * element was changed. + * + * @param node the node that was changed + */ + protected void broadcastNodeChanged(Node node) { + Iterator it = cursors.iterator(); + while (it.hasNext()) { + WeakReference ref = (WeakReference) it.next(); + Cursor cursor = (Cursor) ref.get(); + if (cursor == null) { + it.remove(); // clean up list + } else { + cursor.nodeChanged(node); + } + } + } + + /** + * Informs all of my registered cursors that the specified + * element was just removed from my list. + * + * @param node the node that was changed + */ + protected void broadcastNodeRemoved(Node node) { + Iterator it = cursors.iterator(); + while (it.hasNext()) { + WeakReference ref = (WeakReference) it.next(); + Cursor cursor = (Cursor) ref.get(); + if (cursor == null) { + it.remove(); // clean up list + } else { + cursor.nodeRemoved(node); + } + } + } + + /** + * Informs all of my registered cursors that the specified + * element was just added to my list. + * + * @param node the node that was changed + */ + protected void broadcastNodeInserted(Node node) { + Iterator it = cursors.iterator(); + while (it.hasNext()) { + WeakReference ref = (WeakReference) it.next(); + Cursor cursor = (Cursor) ref.get(); + if (cursor == null) { + it.remove(); // clean up list + } else { + cursor.nodeInserted(node); + } + } + } + + //----------------------------------------------------------------------- + /** + * Serializes the data held in this object to the stream specified. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Deserializes the data held in this object to the stream specified. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + + //----------------------------------------------------------------------- + /** + * An extended ListIterator that allows concurrent changes to + * the underlying list. + */ + public static class Cursor extends AbstractLinkedList.LinkedListIterator { + /** Is the cursor valid (not closed) */ + boolean valid = true; + /** Is the next index valid */ + boolean nextIndexValid = true; + + /** + * Constructs a new cursor. + * + * @param index the index to start from + */ + protected Cursor(CursorableLinkedList parent, int index) { + super(parent, index); + valid = true; + } + + /** + * Adds an object to the list. + * The object added here will be the new 'previous' in the iterator. + * + * @param obj the object to add + */ + public void add(Object obj) { + super.add(obj); + // add on iterator does not return the added element + next = next.next; + } + + /** + * Gets the index of the next element to be returned. + * + * @return the next index + */ + public int nextIndex() { + if (nextIndexValid == false) { + if (next == list.header) { + nextIndex = list.size(); + } else { + int pos = 0; + Node temp = list.header.next; + while (temp != next) { + pos++; + temp = temp.next; + } + nextIndex = pos; + } + nextIndexValid = true; + } + return nextIndex; + } + + /** + * Handle event from the list when a node has changed. + * + * @param node the node that changed + */ + protected void nodeChanged(Node node) { + // do nothing + } + + /** + * Handle event from the list when a node has been removed. + * + * @param node the node that was removed + */ + protected void nodeRemoved(Node node) { + if (node == next) { + next = node.next; + } else if (node == current) { + current = null; + nextIndex--; + } else { + nextIndexValid = false; + } + } + + /** + * Handle event from the list when a node has been added. + * + * @param node the node that was added + */ + protected void nodeInserted(Node node) { + if (node.previous == current) { + next = node; + } else if (next.previous == node) { + next = node; + } else { + nextIndexValid = false; + } + } + + /** + * Override superclass modCount check, and replace it with our valid flag. + */ + protected void checkModCount() { + if (!valid) { + throw new ConcurrentModificationException("Cursor closed"); + } + } + + /** + * Mark this cursor as no longer being needed. Any resources + * associated with this cursor are immediately released. + * In previous versions of this class, it was mandatory to close + * all cursor objects to avoid memory leaks. It is no longer + * necessary to call this close method; an instance of this class + * can now be treated exactly like a normal iterator. + */ + public void close() { + if (valid) { + ((CursorableLinkedList) list).unregisterCursor(this); + valid = false; + } + } + } +} + +//class CursorableSubList extends CursorableLinkedList implements List { +// +// //--- constructors ----------------------------------------------- +// +// CursorableSubList(CursorableLinkedList list, int from, int to) { +// if(0 > from || list.size() < to) { +// throw new IndexOutOfBoundsException(); +// } else if(from > to) { +// throw new IllegalArgumentException(); +// } +// _list = list; +// if(from < list.size()) { +// _head.setNext(_list.getListableAt(from)); +// _pre = (null == _head.next()) ? null : _head.next().prev(); +// } else { +// _pre = _list.getListableAt(from-1); +// } +// if(from == to) { +// _head.setNext(null); +// _head.setPrev(null); +// if(to < list.size()) { +// _post = _list.getListableAt(to); +// } else { +// _post = null; +// } +// } else { +// _head.setPrev(_list.getListableAt(to-1)); +// _post = _head.prev().next(); +// } +// _size = to - from; +// _modCount = _list._modCount; +// } +// +// //--- public methods ------------------------------------------ +// +// public void clear() { +// checkForComod(); +// Iterator it = iterator(); +// while(it.hasNext()) { +// it.next(); +// it.remove(); +// } +// } +// +// public Iterator iterator() { +// checkForComod(); +// return super.iterator(); +// } +// +// public int size() { +// checkForComod(); +// return super.size(); +// } +// +// public boolean isEmpty() { +// checkForComod(); +// return super.isEmpty(); +// } +// +// public Object[] toArray() { +// checkForComod(); +// return super.toArray(); +// } +// +// public Object[] toArray(Object a[]) { +// checkForComod(); +// return super.toArray(a); +// } +// +// public boolean contains(Object o) { +// checkForComod(); +// return super.contains(o); +// } +// +// public boolean remove(Object o) { +// checkForComod(); +// return super.remove(o); +// } +// +// public Object removeFirst() { +// checkForComod(); +// return super.removeFirst(); +// } +// +// public Object removeLast() { +// checkForComod(); +// return super.removeLast(); +// } +// +// public boolean addAll(Collection c) { +// checkForComod(); +// return super.addAll(c); +// } +// +// public boolean add(Object o) { +// checkForComod(); +// return super.add(o); +// } +// +// public boolean addFirst(Object o) { +// checkForComod(); +// return super.addFirst(o); +// } +// +// public boolean addLast(Object o) { +// checkForComod(); +// return super.addLast(o); +// } +// +// public boolean removeAll(Collection c) { +// checkForComod(); +// return super.removeAll(c); +// } +// +// public boolean containsAll(Collection c) { +// checkForComod(); +// return super.containsAll(c); +// } +// +// public boolean addAll(int index, Collection c) { +// checkForComod(); +// return super.addAll(index,c); +// } +// +// public int hashCode() { +// checkForComod(); +// return super.hashCode(); +// } +// +// public boolean retainAll(Collection c) { +// checkForComod(); +// return super.retainAll(c); +// } +// +// public Object set(int index, Object element) { +// checkForComod(); +// return super.set(index,element); +// } +// +// public boolean equals(Object o) { +// checkForComod(); +// return super.equals(o); +// } +// +// public Object get(int index) { +// checkForComod(); +// return super.get(index); +// } +// +// public Object getFirst() { +// checkForComod(); +// return super.getFirst(); +// } +// +// public Object getLast() { +// checkForComod(); +// return super.getLast(); +// } +// +// public void add(int index, Object element) { +// checkForComod(); +// super.add(index,element); +// } +// +// public ListIterator listIterator(int index) { +// checkForComod(); +// return super.listIterator(index); +// } +// +// public Object remove(int index) { +// checkForComod(); +// return super.remove(index); +// } +// +// public int indexOf(Object o) { +// checkForComod(); +// return super.indexOf(o); +// } +// +// public int lastIndexOf(Object o) { +// checkForComod(); +// return super.lastIndexOf(o); +// } +// +// public ListIterator listIterator() { +// checkForComod(); +// return super.listIterator(); +// } +// +// public List subList(int fromIndex, int toIndex) { +// checkForComod(); +// return super.subList(fromIndex,toIndex); +// } +// +// //--- protected methods ------------------------------------------ +// +// /** +// * Inserts a new value into my +// * list, after the specified before element, and before the +// * specified after element +// * +// * @return the newly created {@link CursorableLinkedList.Listable} +// */ +// protected Listable insertListable(Listable before, Listable after, Object value) { +// _modCount++; +// _size++; +// Listable elt = _list.insertListable((null == before ? _pre : before), (null == after ? _post : after),value); +// if(null == _head.next()) { +// _head.setNext(elt); +// _head.setPrev(elt); +// } +// if(before == _head.prev()) { +// _head.setPrev(elt); +// } +// if(after == _head.next()) { +// _head.setNext(elt); +// } +// broadcastListableInserted(elt); +// return elt; +// } +// +// /** +// * Removes the given {@link CursorableLinkedList.Listable} from my list. +// */ +// protected void removeListable(Listable elt) { +// _modCount++; +// _size--; +// if(_head.next() == elt && _head.prev() == elt) { +// _head.setNext(null); +// _head.setPrev(null); +// } +// if(_head.next() == elt) { +// _head.setNext(elt.next()); +// } +// if(_head.prev() == elt) { +// _head.setPrev(elt.prev()); +// } +// _list.removeListable(elt); +// broadcastListableRemoved(elt); +// } +// +// /** +// * Test to see if my underlying list has been modified +// * by some other process. If it has, throws a +// * {@link ConcurrentModificationException}, otherwise +// * quietly returns. +// * +// * @throws ConcurrentModificationException +// */ +// protected void checkForComod() throws ConcurrentModificationException { +// if(_modCount != _list._modCount) { +// throw new ConcurrentModificationException(); +// } +// } +// +// //--- protected attributes --------------------------------------- +// +// /** My underlying list */ +// protected CursorableLinkedList _list = null; +// +// /** The element in my underlying list preceding the first element in my list. */ +// protected Listable _pre = null; +// +// /** The element in my underlying list following the last element in my list. */ +// protected Listable _post = null; +// +//} diff --git a/src/java/org/apache/commons/collections/list/NodeCachingLinkedList.java b/src/java/org/apache/commons/collections/list/NodeCachingLinkedList.java index c5a0e9310..1ea0ca577 100644 --- a/src/java/org/apache/commons/collections/list/NodeCachingLinkedList.java +++ b/src/java/org/apache/commons/collections/list/NodeCachingLinkedList.java @@ -1,5 +1,5 @@ /* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/list/NodeCachingLinkedList.java,v 1.1 2003/12/11 00:18:06 scolebourne Exp $ + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/list/NodeCachingLinkedList.java,v 1.2 2003/12/24 01:15:40 scolebourne Exp $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -64,12 +64,21 @@ import java.io.Serializable; import java.util.Collection; /** - * A linked list implementation that caches the nodes used internally to prevent - * unnecessary object creates and deletion. This results in a performance - * improvement for long-lived lists which both add and remove. + * A List implementation that stores a cache of internal Node objects + * in an effort to reduce wasteful object creation. + *

+ * A linked list creates one Node for each item of data added. This can result in + * a lot of object creation and garbage collection. This implementation seeks to + * avoid that by maintaining a store of cached nodes. + *

+ * This implementation is suitable for long-lived lists where both add and remove + * are used. Short-lived lists, or lists which only grow will have worse performance + * using this class. + *

+ * Note that this implementation is not synchronized. * * @since Commons Collections 3.0 - * @version $Revision: 1.1 $ $Date: 2003/12/11 00:18:06 $ + * @version $Revision: 1.2 $ $Date: 2003/12/24 01:15:40 $ * * @author Jeff Varszegi * @author Rich Dougherty @@ -135,6 +144,8 @@ public class NodeCachingLinkedList extends AbstractLinkedList implements Seriali //----------------------------------------------------------------------- /** * Gets the maximum size of the cache. + * + * @return the maximum cache size */ protected int getMaximumCacheSize() { return maximumCacheSize; @@ -142,6 +153,8 @@ public class NodeCachingLinkedList extends AbstractLinkedList implements Seriali /** * Sets the maximum size of the cache. + * + * @param maximumCacheSize the new maximum cache size */ protected void setMaximumCacheSize(int maximumCacheSize) { this.maximumCacheSize = maximumCacheSize; @@ -163,7 +176,7 @@ public class NodeCachingLinkedList extends AbstractLinkedList implements Seriali * {@link #cacheSize} is decreased accordingly. The node that is returned * will have null values for next, previous and element. * - * @return A node, or null if there are no nodes in the cache. + * @return a node, or null if there are no nodes in the cache. */ protected Node getNodeFromCache() { if (cacheSize == 0) { @@ -177,20 +190,27 @@ public class NodeCachingLinkedList extends AbstractLinkedList implements Seriali return cachedNode; } + /** + * Checks whether the cache is full. + * + * @return true if the cache is full + */ protected boolean isCacheFull() { return cacheSize >= maximumCacheSize; } /** - * Adds a node to the cache, if the cache isn't full. The node's contents - * are cleared to so they can be garbage collected. + * Adds a node to the cache, if the cache isn't full. + * The node's contents are cleared to so they can be garbage collected. + * + * @param node the node to add to the cache */ protected void addNodeToCache(Node node) { if (isCacheFull()) { - // Don't cache the node. + // don't cache the node. return; } - // Clear the node's contents and add it to the cache. + // clear the node's contents and add it to the cache. Node nextCachedNode = firstCachedNode; node.previous = null; node.next = nextCachedNode; @@ -201,47 +221,39 @@ public class NodeCachingLinkedList extends AbstractLinkedList implements Seriali //----------------------------------------------------------------------- /** - * Create a node, getting it from the cache if possible. - */ - protected Node createHeaderNode() { - Node cachedNode = getNodeFromCache(); - if (cachedNode == null) { - return super.createHeaderNode(); - } else { - return cachedNode; - } - } - - /** - * Creates a new node with the specified properties, using a cached Node - * if possible. + * Creates a new node, either by reusing one from the cache or creating + * a new one. * - * @param previous node to precede the new node - * @param next node to follow the new node * @param value value of the new node + * @return the newly created node */ - protected Node createNode(Node previous, Node next, Object value) { + protected Node createNode(Object value) { Node cachedNode = getNodeFromCache(); if (cachedNode == null) { - return super.createNode(previous, next, value); + return super.createNode(value); } else { - cachedNode.next = next; - cachedNode.previous = previous; cachedNode.value = value; return cachedNode; } } /** - * Calls the superclass' implementation then calls - * addNodeToCache on the node which has - * been removed. + * Removes the node from the list, storing it in the cache for reuse + * if the cache is not yet full. + * + * @param node the node to remove */ protected void removeNode(Node node) { super.removeNode(node); addNodeToCache(node); } + /** + * Removes all the nodes from the list, storing as many as required in the + * cache for reuse. + * + * @param node the node to remove + */ protected void removeAllNodes() { // Add the removed nodes to the cache, then remove the rest. // We can add them to the cache before removing them, since diff --git a/src/test/org/apache/commons/collections/list/TestAll.java b/src/test/org/apache/commons/collections/list/TestAll.java index 0328a8bc1..f6efa8024 100644 --- a/src/test/org/apache/commons/collections/list/TestAll.java +++ b/src/test/org/apache/commons/collections/list/TestAll.java @@ -1,5 +1,5 @@ /* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/list/TestAll.java,v 1.2 2003/12/11 00:18:06 scolebourne Exp $ + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/list/TestAll.java,v 1.3 2003/12/24 01:15:40 scolebourne Exp $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -65,7 +65,7 @@ import junit.framework.TestSuite; * Entry point for tests. * * @since Commons Collections 3.0 - * @version $Revision: 1.2 $ $Date: 2003/12/11 00:18:06 $ + * @version $Revision: 1.3 $ $Date: 2003/12/24 01:15:40 $ * * @author Stephen Colebourne */ @@ -83,6 +83,7 @@ public class TestAll extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); + suite.addTest(TestCursorableLinkedList.suite()); suite.addTest(TestNodeCachingLinkedList.suite()); suite.addTest(TestFixedSizeList.suite()); diff --git a/src/test/org/apache/commons/collections/list/TestCursorableLinkedList.java b/src/test/org/apache/commons/collections/list/TestCursorableLinkedList.java new file mode 100644 index 000000000..626eb583c --- /dev/null +++ b/src/test/org/apache/commons/collections/list/TestCursorableLinkedList.java @@ -0,0 +1,1160 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/list/TestCursorableLinkedList.java,v 1.1 2003/12/24 01:15:40 scolebourne Exp $ + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.collections.list; + +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import junit.framework.Test; + +import org.apache.commons.collections.BulkTest; + +/** + * Test class. + * + * @version $Revision: 1.1 $ $Date: 2003/12/24 01:15:40 $ + * + * @author Rodney Waldhoff + * @author Simon Kitching + */ +public class TestCursorableLinkedList extends TestAbstractLinkedList { + public TestCursorableLinkedList(String testName) { + super(testName); + } + + public static Test suite() { + return BulkTest.makeSuite(TestCursorableLinkedList.class); + } + + public static void main(String args[]) { + String[] testCaseName = { TestCursorableLinkedList.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + private CursorableLinkedList list = null; + + public void setUp() { + list = new CursorableLinkedList(); + } + + public List makeEmptyList() { + return new CursorableLinkedList(); + } + + public void testAdd() { + assertEquals("[]",list.toString()); + assertTrue(list.add(new Integer(1))); + assertEquals("[1]",list.toString()); + assertTrue(list.add(new Integer(2))); + assertEquals("[1, 2]",list.toString()); + assertTrue(list.add(new Integer(3))); + assertEquals("[1, 2, 3]",list.toString()); + assertTrue(list.addFirst(new Integer(0))); + assertEquals("[0, 1, 2, 3]",list.toString()); + assertTrue(list.addLast(new Integer(4))); + assertEquals("[0, 1, 2, 3, 4]",list.toString()); + list.add(0,new Integer(-2)); + assertEquals("[-2, 0, 1, 2, 3, 4]",list.toString()); + list.add(1,new Integer(-1)); + assertEquals("[-2, -1, 0, 1, 2, 3, 4]",list.toString()); + list.add(7,new Integer(5)); + assertEquals("[-2, -1, 0, 1, 2, 3, 4, 5]",list.toString()); + + java.util.List list2 = new java.util.LinkedList(); + list2.add("A"); + list2.add("B"); + list2.add("C"); + + assertTrue(list.addAll(list2)); + assertEquals("[-2, -1, 0, 1, 2, 3, 4, 5, A, B, C]",list.toString()); + assertTrue(list.addAll(3,list2)); + assertEquals("[-2, -1, 0, A, B, C, 1, 2, 3, 4, 5, A, B, C]",list.toString()); + } + + public void testClear() { + assertEquals(0,list.size()); + assertTrue(list.isEmpty()); + list.clear(); + assertEquals(0,list.size()); + assertTrue(list.isEmpty()); + + list.add("element"); + assertEquals(1,list.size()); + assertTrue(!list.isEmpty()); + + list.clear(); + assertEquals(0,list.size()); + assertTrue(list.isEmpty()); + + list.add("element1"); + list.add("element2"); + assertEquals(2,list.size()); + assertTrue(!list.isEmpty()); + + list.clear(); + assertEquals(0,list.size()); + assertTrue(list.isEmpty()); + + for(int i=0;i<1000;i++) { + list.add(new Integer(i)); + } + assertEquals(1000,list.size()); + assertTrue(!list.isEmpty()); + + list.clear(); + assertEquals(0,list.size()); + assertTrue(list.isEmpty()); + } + + public void testContains() { + assertTrue(!list.contains("A")); + assertTrue(list.add("A")); + assertTrue(list.contains("A")); + assertTrue(list.add("B")); + assertTrue(list.contains("A")); + assertTrue(list.addFirst("a")); + assertTrue(list.contains("A")); + assertTrue(list.remove("a")); + assertTrue(list.contains("A")); + assertTrue(list.remove("A")); + assertTrue(!list.contains("A")); + } + + public void testContainsAll() { + assertTrue(list.containsAll(list)); + java.util.List list2 = new java.util.LinkedList(); + assertTrue(list.containsAll(list2)); + list2.add("A"); + assertTrue(!list.containsAll(list2)); + list.add("B"); + list.add("A"); + assertTrue(list.containsAll(list2)); + list2.add("B"); + assertTrue(list.containsAll(list2)); + list2.add("C"); + assertTrue(!list.containsAll(list2)); + list.add("C"); + assertTrue(list.containsAll(list2)); + list2.add("C"); + assertTrue(list.containsAll(list2)); + assertTrue(list.containsAll(list)); + } + + public void testCursorNavigation() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + CursorableLinkedList.Cursor it = list.cursor(); + assertTrue(it.hasNext()); + assertTrue(!it.hasPrevious()); + assertEquals("1",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("1",it.previous()); + assertTrue(it.hasNext()); + assertTrue(!it.hasPrevious()); + assertEquals("1",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("2",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("2",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("2",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("3",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("4",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("5",it.next()); + assertTrue(!it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("5",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("4",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("3",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("2",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals("1",it.previous()); + assertTrue(it.hasNext()); + assertTrue(!it.hasPrevious()); + it.close(); + } + + public void testCursorSet() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + + CursorableLinkedList.Cursor it = list.cursor(); + assertEquals("1",it.next()); + it.set("a"); + assertEquals("a",it.previous()); + it.set("A"); + assertEquals("A",it.next()); + assertEquals("2",it.next()); + it.set("B"); + assertEquals("3",it.next()); + assertEquals("4",it.next()); + it.set("D"); + assertEquals("5",it.next()); + it.set("E"); + assertEquals("[A, B, 3, D, E]",list.toString()); + it.close(); + } + + public void testCursorRemove() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + + CursorableLinkedList.Cursor it = list.cursor(); + try { + it.remove(); + fail(); + } catch(IllegalStateException e) { + // expected + } + assertEquals("1",it.next()); + assertEquals("2",it.next()); + assertEquals("[1, 2, 3, 4, 5]",list.toString()); + it.remove(); + assertEquals("[1, 3, 4, 5]",list.toString()); + assertEquals("3",it.next()); + assertEquals("3",it.previous()); + assertEquals("1",it.previous()); + it.remove(); + assertEquals("[3, 4, 5]",list.toString()); + assertTrue(!it.hasPrevious()); + assertEquals("3",it.next()); + it.remove(); + assertEquals("[4, 5]",list.toString()); + try { + it.remove(); + } catch(IllegalStateException e) { + // expected + } + assertEquals("4",it.next()); + assertEquals("5",it.next()); + it.remove(); + assertEquals("[4]",list.toString()); + assertEquals("4",it.previous()); + it.remove(); + assertEquals("[]",list.toString()); + it.close(); + } + + public void testCursorAdd() { + CursorableLinkedList.Cursor it = list.cursor(); + it.add("1"); + assertEquals("[1]",list.toString()); + it.add("3"); + assertEquals("[1, 3]",list.toString()); + it.add("5"); + assertEquals("[1, 3, 5]",list.toString()); + assertEquals("5",it.previous()); + it.add("4"); + assertEquals("[1, 3, 4, 5]",list.toString()); + assertEquals("4",it.previous()); + assertEquals("3",it.previous()); + it.add("2"); + assertEquals("[1, 2, 3, 4, 5]",list.toString()); + it.close(); + } + + public void testCursorConcurrentModification() { + // this test verifies that cursors remain valid when the list + // is modified via other means. + list.add("1"); + list.add("2"); + list.add("3"); + list.add("5"); + list.add("7"); + list.add("9"); + + CursorableLinkedList.Cursor c1 = list.cursor(); + CursorableLinkedList.Cursor c2 = list.cursor(); + Iterator li = list.iterator(); + + // test cursors remain valid when list modified by std Iterator + // test cursors skip elements removed via ListIterator + assertEquals("1",li.next()); + assertEquals("2",li.next()); + li.remove(); + assertEquals("3",li.next()); + assertEquals("1",c1.next()); + assertEquals("3",c1.next()); + assertEquals("1",c2.next()); + + // test cursor c1 can remove elements from previously modified list + // test cursor c2 skips elements removed via different cursor + c1.remove(); + assertEquals("5",c2.next()); + c2.add("6"); + assertEquals("5",c1.next()); + assertEquals("6",c1.next()); + assertEquals("7",c1.next()); + + // test cursors remain valid when list mod via CursorableLinkedList + // test cursor remains valid when elements inserted into list before + // the current position of the cursor. + list.add(0, "0"); + + // test cursor remains valid when element inserted immediately after + // current element of a cursor, and the element is seen on the + // next call to the next method of that cursor. + list.add(5, "8"); + + assertEquals("8",c1.next()); + assertEquals("9",c1.next()); + c1.add("10"); + assertEquals("7",c2.next()); + assertEquals("8",c2.next()); + assertEquals("9",c2.next()); + assertEquals("10",c2.next()); + + try { + c2.next(); + fail(); + } catch (NoSuchElementException nse) {} + + try { + li.next(); + fail(); + } catch (ConcurrentModificationException cme) {} + + c1.close(); // not necessary + c2.close(); // not necessary + } + + public void testCursorNextIndexMid() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("5"); + + CursorableLinkedList.Cursor c1 = list.cursor(); + Iterator li = list.iterator(); + + // test cursors remain valid when list modified by std Iterator + // test cursors skip elements removed via ListIterator + assertEquals("1", li.next()); + assertEquals("2", li.next()); + li.remove(); + assertEquals(0, c1.nextIndex()); + assertEquals("1", c1.next()); + assertEquals(1, c1.nextIndex()); + assertEquals("3", c1.next()); + } + + public void testCursorNextIndexFirst() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("5"); + + CursorableLinkedList.Cursor c1 = list.cursor(); + + assertEquals(0, c1.nextIndex()); + list.remove(0); + assertEquals(0, c1.nextIndex()); + assertEquals("2", c1.next()); + assertEquals(1, c1.nextIndex()); + assertEquals("3", c1.next()); + } + + public void testCursorNextIndexAddBefore() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("5"); + + CursorableLinkedList.Cursor c1 = list.cursor(); + + assertEquals(0, c1.nextIndex()); + assertEquals("1", c1.next()); + list.add(0, "0"); + assertEquals(2, c1.nextIndex()); + assertEquals("2", c1.next()); + } + + public void testCursorNextIndexAddNext() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("5"); + + CursorableLinkedList.Cursor c1 = list.cursor(); + + assertEquals(0, c1.nextIndex()); + list.add(0, "0"); + assertEquals(0, c1.nextIndex()); + assertEquals("0", c1.next()); + assertEquals(1, c1.nextIndex()); + assertEquals("1", c1.next()); + } + + public void testCursorNextIndexAddAfter() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("5"); + + CursorableLinkedList.Cursor c1 = list.cursor(); + + assertEquals(0, c1.nextIndex()); + list.add(1, "0"); + assertEquals(0, c1.nextIndex()); + assertEquals("1", c1.next()); + assertEquals(1, c1.nextIndex()); + assertEquals("0", c1.next()); + } + + public void testEqualsAndHashCode() { + assertTrue(list.equals(list)); + assertEquals(list.hashCode(),list.hashCode()); + list.add("A"); + assertTrue(list.equals(list)); + assertEquals(list.hashCode(),list.hashCode()); + + CursorableLinkedList list2 = new CursorableLinkedList(); + assertTrue(!list.equals(list2)); + assertTrue(!list2.equals(list)); + + java.util.List list3 = new java.util.LinkedList(); + assertTrue(!list.equals(list3)); + assertTrue(!list3.equals(list)); + assertTrue(list2.equals(list3)); + assertTrue(list3.equals(list2)); + assertEquals(list2.hashCode(),list3.hashCode()); + + list2.add("A"); + assertTrue(list.equals(list2)); + assertTrue(list2.equals(list)); + assertTrue(!list2.equals(list3)); + assertTrue(!list3.equals(list2)); + + list3.add("A"); + assertTrue(list2.equals(list3)); + assertTrue(list3.equals(list2)); + assertEquals(list2.hashCode(),list3.hashCode()); + + list.add("B"); + assertTrue(list.equals(list)); + assertTrue(!list.equals(list2)); + assertTrue(!list2.equals(list)); + assertTrue(!list.equals(list3)); + assertTrue(!list3.equals(list)); + + list2.add("B"); + list3.add("B"); + assertTrue(list.equals(list)); + assertTrue(list.equals(list2)); + assertTrue(list2.equals(list)); + assertTrue(list2.equals(list3)); + assertTrue(list3.equals(list2)); + assertEquals(list2.hashCode(),list3.hashCode()); + + list.add("C"); + list2.add("C"); + list3.add("C"); + assertTrue(list.equals(list)); + assertTrue(list.equals(list2)); + assertTrue(list2.equals(list)); + assertTrue(list2.equals(list3)); + assertTrue(list3.equals(list2)); + assertEquals(list.hashCode(),list2.hashCode()); + assertEquals(list2.hashCode(),list3.hashCode()); + + list.add("D"); + list2.addFirst("D"); + assertTrue(list.equals(list)); + assertTrue(!list.equals(list2)); + assertTrue(!list2.equals(list)); + } + + public void testGet() { + try { + list.get(0); + fail("shouldn't get here"); + } catch(IndexOutOfBoundsException e) { + // expected + } + + assertTrue(list.add("A")); + assertEquals("A",list.get(0)); + assertTrue(list.add("B")); + assertEquals("A",list.get(0)); + assertEquals("B",list.get(1)); + + try { + list.get(-1); + fail("shouldn't get here"); + } catch(IndexOutOfBoundsException e) { + // expected + } + + try { + list.get(2); + fail("shouldn't get here"); + } catch(IndexOutOfBoundsException e) { + // expected + } + } + + public void testIndexOf() { + assertEquals(-1,list.indexOf("A")); + assertEquals(-1,list.lastIndexOf("A")); + list.add("A"); + assertEquals(0,list.indexOf("A")); + assertEquals(0,list.lastIndexOf("A")); + assertEquals(-1,list.indexOf("B")); + assertEquals(-1,list.lastIndexOf("B")); + list.add("B"); + assertEquals(0,list.indexOf("A")); + assertEquals(0,list.lastIndexOf("A")); + assertEquals(1,list.indexOf("B")); + assertEquals(1,list.lastIndexOf("B")); + list.addFirst("B"); + assertEquals(1,list.indexOf("A")); + assertEquals(1,list.lastIndexOf("A")); + assertEquals(0,list.indexOf("B")); + assertEquals(2,list.lastIndexOf("B")); + } + + public void testIsEmpty() { + assertTrue(list.isEmpty()); + list.add("element"); + assertTrue(!list.isEmpty()); + list.remove("element"); + assertTrue(list.isEmpty()); + list.add("element"); + assertTrue(!list.isEmpty()); + list.clear(); + assertTrue(list.isEmpty()); + } + + public void testIterator() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + Iterator it = list.iterator(); + assertTrue(it.hasNext()); + assertEquals("1",it.next()); + assertTrue(it.hasNext()); + assertEquals("2",it.next()); + assertTrue(it.hasNext()); + assertEquals("3",it.next()); + assertTrue(it.hasNext()); + assertEquals("4",it.next()); + assertTrue(it.hasNext()); + assertEquals("5",it.next()); + assertTrue(!it.hasNext()); + + it = list.iterator(); + assertTrue(it.hasNext()); + assertEquals("1",it.next()); + it.remove(); + assertEquals("[2, 3, 4, 5]",list.toString()); + assertTrue(it.hasNext()); + assertEquals("2",it.next()); + it.remove(); + assertEquals("[3, 4, 5]",list.toString()); + assertTrue(it.hasNext()); + assertEquals("3",it.next()); + it.remove(); + assertEquals("[4, 5]",list.toString()); + assertTrue(it.hasNext()); + assertEquals("4",it.next()); + it.remove(); + assertEquals("[5]",list.toString()); + assertTrue(it.hasNext()); + assertEquals("5",it.next()); + it.remove(); + assertEquals("[]",list.toString()); + assertTrue(!it.hasNext()); + } + + public void testListIteratorNavigation() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + ListIterator it = list.listIterator(); + assertTrue(it.hasNext()); + assertTrue(!it.hasPrevious()); + assertEquals(-1,it.previousIndex()); + assertEquals(0,it.nextIndex()); + assertEquals("1",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(0,it.previousIndex()); + assertEquals(1,it.nextIndex()); + assertEquals("1",it.previous()); + assertTrue(it.hasNext()); + assertTrue(!it.hasPrevious()); + assertEquals(-1,it.previousIndex()); + assertEquals(0,it.nextIndex()); + assertEquals("1",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(0,it.previousIndex()); + assertEquals(1,it.nextIndex()); + assertEquals("2",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(1,it.previousIndex()); + assertEquals(2,it.nextIndex()); + assertEquals("2",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(0,it.previousIndex()); + assertEquals(1,it.nextIndex()); + assertEquals("2",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(1,it.previousIndex()); + assertEquals(2,it.nextIndex()); + assertEquals("3",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(2,it.previousIndex()); + assertEquals(3,it.nextIndex()); + assertEquals("4",it.next()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(3,it.previousIndex()); + assertEquals(4,it.nextIndex()); + assertEquals("5",it.next()); + assertTrue(!it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(4,it.previousIndex()); + assertEquals(5,it.nextIndex()); + assertEquals("5",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(3,it.previousIndex()); + assertEquals(4,it.nextIndex()); + assertEquals("4",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(2,it.previousIndex()); + assertEquals(3,it.nextIndex()); + assertEquals("3",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(1,it.previousIndex()); + assertEquals(2,it.nextIndex()); + assertEquals("2",it.previous()); + assertTrue(it.hasNext()); + assertTrue(it.hasPrevious()); + assertEquals(0,it.previousIndex()); + assertEquals(1,it.nextIndex()); + assertEquals("1",it.previous()); + assertTrue(it.hasNext()); + assertTrue(!it.hasPrevious()); + assertEquals(-1,it.previousIndex()); + assertEquals(0,it.nextIndex()); + } + + public void testListIteratorSet() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + + ListIterator it = list.listIterator(); + assertEquals("1",it.next()); + it.set("a"); + assertEquals("a",it.previous()); + it.set("A"); + assertEquals("A",it.next()); + assertEquals("2",it.next()); + it.set("B"); + assertEquals("3",it.next()); + assertEquals("4",it.next()); + it.set("D"); + assertEquals("5",it.next()); + it.set("E"); + assertEquals("[A, B, 3, D, E]",list.toString()); + } + + public void testListIteratorRemove() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + + ListIterator it = list.listIterator(); + try { + it.remove(); + } catch(IllegalStateException e) { + // expected + } + assertEquals("1",it.next()); + assertEquals("2",it.next()); + assertEquals("[1, 2, 3, 4, 5]",list.toString()); + it.remove(); + assertEquals("[1, 3, 4, 5]",list.toString()); + assertEquals("3",it.next()); + assertEquals("3",it.previous()); + assertEquals("1",it.previous()); + it.remove(); + assertEquals("[3, 4, 5]",list.toString()); + assertTrue(!it.hasPrevious()); + assertEquals("3",it.next()); + it.remove(); + assertEquals("[4, 5]",list.toString()); + try { + it.remove(); + } catch(IllegalStateException e) { + // expected + } + assertEquals("4",it.next()); + assertEquals("5",it.next()); + it.remove(); + assertEquals("[4]",list.toString()); + assertEquals("4",it.previous()); + it.remove(); + assertEquals("[]",list.toString()); + } + + public void testListIteratorAdd() { + ListIterator it = list.listIterator(); + it.add("1"); + assertEquals("[1]",list.toString()); + it.add("3"); + assertEquals("[1, 3]",list.toString()); + it.add("5"); + assertEquals("[1, 3, 5]",list.toString()); + assertEquals("5",it.previous()); + it.add("4"); + assertEquals("[1, 3, 4, 5]",list.toString()); + assertEquals("4",it.previous()); + assertEquals("3",it.previous()); + it.add("2"); + assertEquals("[1, 2, 3, 4, 5]",list.toString()); + } + + public void testRemoveAll() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + + HashSet set = new HashSet(); + set.add("A"); + set.add("2"); + set.add("C"); + set.add("4"); + set.add("D"); + + assertTrue(list.removeAll(set)); + assertEquals("[1, 3, 5]",list.toString()); + assertTrue(!list.removeAll(set)); + } + + public void testRemoveByIndex() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + assertEquals("[1, 2, 3, 4, 5]",list.toString()); + assertEquals("1",list.remove(0)); + assertEquals("[2, 3, 4, 5]",list.toString()); + assertEquals("3",list.remove(1)); + assertEquals("[2, 4, 5]",list.toString()); + assertEquals("4",list.remove(1)); + assertEquals("[2, 5]",list.toString()); + assertEquals("5",list.remove(1)); + assertEquals("[2]",list.toString()); + assertEquals("2",list.remove(0)); + assertEquals("[]",list.toString()); + } + + public void testRemove() { + list.add("1"); + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + assertEquals("[1, 1, 2, 3, 4, 5, 2, 3, 4, 5]",list.toString()); + assertTrue(!list.remove("6")); + assertTrue(list.remove("5")); + assertEquals("[1, 1, 2, 3, 4, 2, 3, 4, 5]",list.toString()); + assertTrue(list.remove("5")); + assertEquals("[1, 1, 2, 3, 4, 2, 3, 4]",list.toString()); + assertTrue(!list.remove("5")); + assertTrue(list.remove("1")); + assertEquals("[1, 2, 3, 4, 2, 3, 4]",list.toString()); + assertTrue(list.remove("1")); + assertEquals("[2, 3, 4, 2, 3, 4]",list.toString()); + assertTrue(list.remove("2")); + assertEquals("[3, 4, 2, 3, 4]",list.toString()); + assertTrue(list.remove("2")); + assertEquals("[3, 4, 3, 4]",list.toString()); + assertTrue(list.remove("3")); + assertEquals("[4, 3, 4]",list.toString()); + assertTrue(list.remove("3")); + assertEquals("[4, 4]",list.toString()); + assertTrue(list.remove("4")); + assertEquals("[4]",list.toString()); + assertTrue(list.remove("4")); + assertEquals("[]",list.toString()); + } + + public void testRetainAll() { + list.add("1"); + list.add("1"); + list.add("2"); + list.add("2"); + list.add("3"); + list.add("3"); + list.add("4"); + list.add("4"); + list.add("5"); + list.add("5"); + + HashSet set = new HashSet(); + set.add("A"); + set.add("2"); + set.add("C"); + set.add("4"); + set.add("D"); + + assertTrue(list.retainAll(set)); + assertEquals("[2, 2, 4, 4]",list.toString()); + assertTrue(!list.retainAll(set)); + } + + public void testSet() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + assertEquals("[1, 2, 3, 4, 5]",list.toString()); + list.set(0,"A"); + assertEquals("[A, 2, 3, 4, 5]",list.toString()); + list.set(1,"B"); + assertEquals("[A, B, 3, 4, 5]",list.toString()); + list.set(2,"C"); + assertEquals("[A, B, C, 4, 5]",list.toString()); + list.set(3,"D"); + assertEquals("[A, B, C, D, 5]",list.toString()); + list.set(4,"E"); + assertEquals("[A, B, C, D, E]",list.toString()); + } + + public void testSubList() { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + + assertEquals("[A, B, C, D, E]",list.toString()); + assertEquals("[A, B, C, D, E]",list.subList(0,5).toString()); + assertEquals("[B, C, D, E]",list.subList(1,5).toString()); + assertEquals("[C, D, E]",list.subList(2,5).toString()); + assertEquals("[D, E]",list.subList(3,5).toString()); + assertEquals("[E]",list.subList(4,5).toString()); + assertEquals("[]",list.subList(5,5).toString()); + } + + public void testSubListAddEnd() { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + + List sublist = list.subList(5,5); + sublist.add("F"); + assertEquals("[A, B, C, D, E, F]",list.toString()); + assertEquals("[F]",sublist.toString()); + sublist.add("G"); + assertEquals("[A, B, C, D, E, F, G]",list.toString()); + assertEquals("[F, G]",sublist.toString()); + } + + public void testSubListAddBegin() { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + + List sublist = list.subList(0,0); + sublist.add("a"); + assertEquals("[a, A, B, C, D, E]",list.toString()); + assertEquals("[a]",sublist.toString()); + sublist.add("b"); + assertEquals("[a, b, A, B, C, D, E]",list.toString()); + assertEquals("[a, b]",sublist.toString()); + } + + public void testSubListAddMiddle() { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + + List sublist = list.subList(1,3); + sublist.add("a"); + assertEquals("[A, B, C, a, D, E]",list.toString()); + assertEquals("[B, C, a]",sublist.toString()); + sublist.add("b"); + assertEquals("[A, B, C, a, b, D, E]",list.toString()); + assertEquals("[B, C, a, b]",sublist.toString()); + } + + public void testSubListRemove() { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + + List sublist = list.subList(1,4); + assertEquals("[B, C, D]",sublist.toString()); + assertEquals("[A, B, C, D, E]",list.toString()); + sublist.remove("C"); + assertEquals("[B, D]",sublist.toString()); + assertEquals("[A, B, D, E]",list.toString()); + sublist.remove(1); + assertEquals("[B]",sublist.toString()); + assertEquals("[A, B, E]",list.toString()); + sublist.clear(); + assertEquals("[]",sublist.toString()); + assertEquals("[A, E]",list.toString()); + } + + public void testToArray() { + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + list.add("5"); + + Object[] elts = list.toArray(); + assertEquals("1",elts[0]); + assertEquals("2",elts[1]); + assertEquals("3",elts[2]); + assertEquals("4",elts[3]); + assertEquals("5",elts[4]); + assertEquals(5,elts.length); + + String[] elts2 = (String[])(list.toArray(new String[0])); + assertEquals("1",elts2[0]); + assertEquals("2",elts2[1]); + assertEquals("3",elts2[2]); + assertEquals("4",elts2[3]); + assertEquals("5",elts2[4]); + assertEquals(5,elts2.length); + + String[] elts3 = new String[5]; + assertSame(elts3,list.toArray(elts3)); + assertEquals("1",elts3[0]); + assertEquals("2",elts3[1]); + assertEquals("3",elts3[2]); + assertEquals("4",elts3[3]); + assertEquals("5",elts3[4]); + assertEquals(5,elts3.length); + + String[] elts4 = new String[3]; + String[] elts4b = (String[])(list.toArray(elts4)); + assertTrue(elts4 != elts4b); + assertEquals("1",elts4b[0]); + assertEquals("2",elts4b[1]); + assertEquals("3",elts4b[2]); + assertEquals("4",elts4b[3]); + assertEquals("5",elts4b[4]); + assertEquals(5,elts4b.length); + } + + public void testSerialization() throws Exception { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + + java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream out = new java.io.ObjectOutputStream(buf); + out.writeObject(list); + out.flush(); + out.close(); + + java.io.ByteArrayInputStream bufin = new java.io.ByteArrayInputStream(buf.toByteArray()); + java.io.ObjectInputStream in = new java.io.ObjectInputStream(bufin); + Object list2 = in.readObject(); + + assertTrue(list != list2); + assertTrue(list2.equals(list)); + assertTrue(list.equals(list2)); + } + + public void testSerializationWithOpenCursor() throws Exception { + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); + list.add("E"); + CursorableLinkedList.Cursor cursor = list.cursor(); + + java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream out = new java.io.ObjectOutputStream(buf); + out.writeObject(list); + out.flush(); + out.close(); + + java.io.ByteArrayInputStream bufin = new java.io.ByteArrayInputStream(buf.toByteArray()); + java.io.ObjectInputStream in = new java.io.ObjectInputStream(bufin); + Object list2 = in.readObject(); + + assertTrue(list != list2); + assertTrue(list2.equals(list)); + assertTrue(list.equals(list2)); + } + + public void testLongSerialization() throws Exception { + // recursive serialization will cause a stack + // overflow exception with long lists + for(int i=0;i<10000;i++) { + list.add(new Integer(i)); + } + + java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream out = new java.io.ObjectOutputStream(buf); + out.writeObject(list); + out.flush(); + out.close(); + + java.io.ByteArrayInputStream bufin = new java.io.ByteArrayInputStream(buf.toByteArray()); + java.io.ObjectInputStream in = new java.io.ObjectInputStream(bufin); + Object list2 = in.readObject(); + + assertTrue(list != list2); + assertTrue(list2.equals(list)); + assertTrue(list.equals(list2)); + } + + + /** + * Ignore the serialization tests for sublists and sub-sublists. + * + * @return an array of sublist serialization test names + */ + public String[] ignoredTests() { + ArrayList list = new ArrayList(); + String prefix = "TestCursorableLinkedList"; + String bulk = ".bulkTestSubList"; + String[] ignored = new String[] { + ".testEmptyListSerialization", + ".testFullListSerialization", + ".testEmptyListCompatibility", + ".testFullListCompatibility", + ".testSimpleSerialization", + ".testCanonicalEmptyCollectionExists", + ".testCanonicalFullCollectionExists", + ".testSerializeDeserializeThenCompare" + }; + for (int i = 0; i < ignored.length; i++) { + list.add(prefix + bulk + ignored[i]); + list.add(prefix + bulk + bulk + ignored[i]); + } + return (String[])list.toArray(new String[0]); + } + +}