diff --git a/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java b/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java
index 4abd2f5f8..6a9e5bfd5 100644
--- a/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java
+++ b/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java
@@ -16,15 +16,21 @@
*/
package org.apache.commons.collections.iterators;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.NoSuchElementException;
+import org.apache.commons.collections.ResettableIterator;
import org.apache.commons.collections.ResettableListIterator;
/**
- * Converts an iterator into a list iterator by caching the returned entries.
+ * Converts an {@link Iterator} into a {@link ResettableListIterator}.
+ * For plain Iterator
s this is accomplished by caching the returned
+ * elements. This class can also be used to simply add {@link ResettableIterator}
+ * functionality to a given {@link ListIterator}.
*
* The ListIterator
interface has additional useful methods
* for navigation - previous()
and the index methods.
@@ -32,7 +38,7 @@ import org.apache.commons.collections.ResettableListIterator;
* ListIterator
. It achieves this by building a list internally
* of as the underlying iterator is traversed.
*
- * The optional operations of ListIterator
are not supported.
+ * The optional operations of ListIterator
are not supported for plain Iterator
s.
*
* This class implements ResettableListIterator from Commons Collections 3.2.
*
@@ -41,13 +47,17 @@ import org.apache.commons.collections.ResettableListIterator;
*
* @author Morgan Delagrange
* @author Stephen Colebourne
+ * @author Matt Benson
*/
public class ListIteratorWrapper implements ResettableListIterator {
- /** Message used when remove, set or add are called. */
+ /** Message used when set or add are called. */
private static final String UNSUPPORTED_OPERATION_MESSAGE =
"ListIteratorWrapper does not support optional operations of ListIterator.";
+ /** Message used when set or add are called. */
+ private static final String CANNOT_REMOVE_MESSAGE = "Cannot remove element at index {0}.";
+
/** The underlying iterator being decorated. */
private final Iterator extends E> iterator;
/** The list being used to cache the iterator. */
@@ -57,6 +67,8 @@ public class ListIteratorWrapper implements ResettableListIterator {
private int currentIndex = 0;
/** The current index of the wrapped iterator. */
private int wrappedIteratorIndex = 0;
+ /** recall whether the wrapped iterator's "cursor" is in such a state as to allow remove() to be called */
+ private boolean removeState;
// Constructor
//-------------------------------------------------------------------------
@@ -78,12 +90,19 @@ public class ListIteratorWrapper implements ResettableListIterator {
// ListIterator interface
//-------------------------------------------------------------------------
/**
- * Throws {@link UnsupportedOperationException}.
+ * Throws {@link UnsupportedOperationException}
+ * unless the underlying Iterator
is a ListIterator
.
*
- * @param obj the object to add, ignored
- * @throws UnsupportedOperationException always
+ * @param obj the object to add
+ * @throws UnsupportedOperationException
*/
public void add(E obj) throws UnsupportedOperationException {
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ li.add(obj);
+ return;
+ }
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
}
@@ -93,7 +112,7 @@ public class ListIteratorWrapper implements ResettableListIterator {
* @return true if there are more elements
*/
public boolean hasNext() {
- if (currentIndex == wrappedIteratorIndex) {
+ if (currentIndex == wrappedIteratorIndex || iterator instanceof ListIterator) {
return iterator.hasNext();
}
return true;
@@ -105,10 +124,12 @@ public class ListIteratorWrapper implements ResettableListIterator {
* @return true if there are previous elements
*/
public boolean hasPrevious() {
- if (currentIndex == 0) {
- return false;
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ return li.hasPrevious();
}
- return true;
+ return currentIndex > 0;
}
/**
@@ -118,6 +139,10 @@ public class ListIteratorWrapper implements ResettableListIterator {
* @throws NoSuchElementException if there are no more elements
*/
public E next() throws NoSuchElementException {
+ if (iterator instanceof ListIterator) {
+ return iterator.next();
+ }
+
if (currentIndex < wrappedIteratorIndex) {
++currentIndex;
return list.get(currentIndex - 1);
@@ -127,15 +152,21 @@ public class ListIteratorWrapper implements ResettableListIterator {
list.add(retval);
++currentIndex;
++wrappedIteratorIndex;
+ removeState = true;
return retval;
}
/**
- * Returns in the index of the next element.
+ * Returns the index of the next element.
*
* @return the index of the next element
*/
public int nextIndex() {
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ return li.nextIndex();
+ }
return currentIndex;
}
@@ -146,11 +177,17 @@ public class ListIteratorWrapper implements ResettableListIterator {
* @throws NoSuchElementException if there are no previous elements
*/
public E previous() throws NoSuchElementException {
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ return li.previous();
+ }
+
if (currentIndex == 0) {
throw new NoSuchElementException();
}
- --currentIndex;
- return list.get(currentIndex);
+ removeState = wrappedIteratorIndex == currentIndex;
+ return list.get(--currentIndex);
}
/**
@@ -159,25 +196,52 @@ public class ListIteratorWrapper implements ResettableListIterator {
* @return the index of the previous element
*/
public int previousIndex() {
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ return li.previousIndex();
+ }
return currentIndex - 1;
}
/**
- * Throws {@link UnsupportedOperationException}.
+ * Throws {@link UnsupportedOperationException} if {@link #previous()} has ever been called.
*
* @throws UnsupportedOperationException always
*/
public void remove() throws UnsupportedOperationException {
- throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
+ if (iterator instanceof ListIterator) {
+ iterator.remove();
+ return;
+ }
+ int removeIndex = currentIndex;
+ if (currentIndex == wrappedIteratorIndex) {
+ --removeIndex;
+ }
+ if (!removeState || wrappedIteratorIndex - currentIndex > 1) {
+ throw new IllegalStateException(MessageFormat.format(CANNOT_REMOVE_MESSAGE, removeIndex));
+ }
+ iterator.remove();
+ list.remove(removeIndex);
+ currentIndex = removeIndex;
+ wrappedIteratorIndex--;
+ removeState = false;
}
/**
- * Throws {@link UnsupportedOperationException}.
+ * Throws {@link UnsupportedOperationException}
+ * unless the underlying Iterator
is a ListIterator
.
*
- * @param obj the object to set, ignored
- * @throws UnsupportedOperationException always
+ * @param obj the object to set
+ * @throws UnsupportedOperationException
*/
public void set(E obj) throws UnsupportedOperationException {
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ li.set(obj);
+ return;
+ }
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
}
@@ -190,6 +254,14 @@ public class ListIteratorWrapper implements ResettableListIterator {
* @since Commons Collections 3.2
*/
public void reset() {
+ if (iterator instanceof ListIterator) {
+ @SuppressWarnings("unchecked")
+ ListIterator li = (ListIterator) iterator;
+ while (li.previousIndex() >= 0) {
+ li.previous();
+ }
+ return;
+ }
currentIndex = 0;
}
diff --git a/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java b/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java
index 6f108b074..8571356d0 100644
--- a/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java
+++ b/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java
@@ -115,13 +115,99 @@ public class TestListIteratorWrapper extends AbstractTestIterator {
public void testRemove() {
ListIterator iter = makeObject();
+ //initial state:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
try {
iter.remove();
- fail("FilterIterator does not support the remove() method");
- } catch (UnsupportedOperationException e) {
-
+ fail("ListIteratorWrapper#remove() should fail; must be initially positioned first");
+ } catch (IllegalStateException e) {
}
+ //no change from invalid op:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //establish size:
+ int sz = list1.size();
+
+ //verify initial next() call:
+ assertEquals(list1.get(0), iter.next());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ //verify remove():
+ iter.remove();
+ assertEquals(--sz, list1.size());
+ //like we never started iterating:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ try {
+ iter.remove();
+ fail("ListIteratorWrapper#remove() should fail; must be repositioned first");
+ } catch (IllegalStateException e) {
+ }
+
+ //no change from invalid op:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //two consecutive next() calls:
+ assertEquals(list1.get(0), iter.next());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ assertEquals(list1.get(1), iter.next());
+ assertEquals(1, iter.previousIndex());
+ assertEquals(2, iter.nextIndex());
+
+ //call previous():
+ assertEquals(list1.get(1), iter.previous());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ //should support remove() after calling previous() once from tip because we haven't changed the underlying iterator's position:
+ iter.remove();
+ assertEquals(--sz, list1.size());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ //dig into cache
+ assertEquals(list1.get(0), iter.previous());
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ try {
+ iter.remove();
+ fail("ListIteratorWrapper does not support the remove() method while dug into the cache via previous()");
+ } catch (IllegalStateException e) {
+ }
+
+ //no change from invalid op:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //dig out of cache, first next() maintains current position:
+ assertEquals(list1.get(0), iter.next());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+ //continue traversing underlying iterator with this next() call, and we're out of the hole, so to speak:
+ assertEquals(list1.get(1), iter.next());
+ assertEquals(1, iter.previousIndex());
+ assertEquals(2, iter.nextIndex());
+
+ //verify remove() works again:
+ iter.remove();
+ assertEquals(--sz, list1.size());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ assertEquals(list1.get(1), iter.next());
+ assertEquals(1, iter.previousIndex());
+ assertEquals(2, iter.nextIndex());
+
}
public void testReset() {
diff --git a/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java b/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java
new file mode 100644
index 000000000..acad9fe70
--- /dev/null
+++ b/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java
@@ -0,0 +1,213 @@
+/*
+ * 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.collections.iterators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.commons.collections.ResettableListIterator;
+
+/**
+ * Tests the ListIteratorWrapper to insure that it behaves as expected when wrapping a ListIterator.
+ *
+ * @version $Revision$ $Date$
+ *
+ * @author Morgan Delagrange
+ */
+public class TestListIteratorWrapper2 extends AbstractTestIterator {
+
+ protected String[] testArray = {
+ "One", "Two", "Three", "Four", "Five", "Six"
+ };
+
+ protected List list1 = null;
+
+ public static Test suite() {
+ return new TestSuite(TestListIteratorWrapper2.class);
+ }
+
+ public TestListIteratorWrapper2(String testName) {
+ super(testName);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setUp() {
+ list1 = new ArrayList();
+ list1.add((E) "One");
+ list1.add((E) "Two");
+ list1.add((E) "Three");
+ list1.add((E) "Four");
+ list1.add((E) "Five");
+ list1.add((E) "Six");
+ }
+
+ public ResettableListIterator makeEmptyIterator() {
+ ArrayList list = new ArrayList();
+ return new ListIteratorWrapper(list.listIterator());
+ }
+
+ public ResettableListIterator makeObject() {
+ return new ListIteratorWrapper(list1.listIterator());
+ }
+
+ public void testIterator() {
+ ListIterator iter = makeObject();
+ for (int i = 0; i < testArray.length; i++) {
+ Object testValue = testArray[i];
+ Object iterValue = iter.next();
+
+ assertEquals("Iteration value is correct", testValue, iterValue);
+ }
+
+ assertTrue("Iterator should now be empty", !iter.hasNext());
+
+ try {
+ iter.next();
+ } catch (Exception e) {
+ assertTrue("NoSuchElementException must be thrown",
+ e.getClass().equals((new NoSuchElementException()).getClass()));
+ }
+
+ // now, read it backwards
+ for (int i = testArray.length - 1; i > -1; --i) {
+ Object testValue = testArray[i];
+ E iterValue = iter.previous();
+
+ assertEquals( "Iteration value is correct", testValue, iterValue );
+ }
+
+ try {
+ iter.previous();
+ } catch (Exception e) {
+ assertTrue("NoSuchElementException must be thrown",
+ e.getClass().equals((new NoSuchElementException()).getClass()));
+ }
+
+ // now, read it forwards again
+ for (int i = 0; i < testArray.length; i++) {
+ Object testValue = testArray[i];
+ Object iterValue = iter.next();
+
+ assertEquals("Iteration value is correct", testValue, iterValue);
+ }
+
+ }
+
+ public void testRemove() {
+ ListIterator iter = makeObject();
+
+ //initial state:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ try {
+ iter.remove();
+ fail("ListIteratorWrapper#remove() should fail; must be initially positioned first");
+ } catch (IllegalStateException e) {
+ }
+
+ //no change from invalid op:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //establish size:
+ int sz = list1.size();
+
+ //verify initial next() call:
+ assertEquals(list1.get(0), iter.next());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ //verify remove():
+ iter.remove();
+ assertEquals(--sz, list1.size());
+ //like we never started iterating:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ try {
+ iter.remove();
+ fail("ListIteratorWrapper#remove() should fail; must be repositioned first");
+ } catch (IllegalStateException e) {
+ }
+
+ //no change from invalid op:
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //two consecutive next() calls:
+ assertEquals(list1.get(0), iter.next());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ assertEquals(list1.get(1), iter.next());
+ assertEquals(1, iter.previousIndex());
+ assertEquals(2, iter.nextIndex());
+
+ //call previous():
+ assertEquals(list1.get(1), iter.previous());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ //should support remove() after calling previous() once from tip because we haven't changed the underlying iterator's position:
+ iter.remove();
+ assertEquals(--sz, list1.size());
+ assertEquals(0, iter.previousIndex());
+ assertEquals(1, iter.nextIndex());
+
+ //this would dig into cache on a plain Iterator, but forwards directly to wrapped ListIterator:
+ assertEquals(list1.get(0), iter.previous());
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //here's the proof; remove() still works:
+ iter.remove();
+ assertEquals(--sz, list1.size());
+ assertEquals(-1, iter.previousIndex());
+ assertEquals(0, iter.nextIndex());
+
+ //further testing would be fairly meaningless:
+ }
+
+ public void testReset() {
+ ResettableListIterator iter = makeObject();
+ E first = iter.next();
+ E second = iter.next();
+
+ iter.reset();
+
+ // after reset, there shouldn't be any previous elements
+ assertFalse("No previous elements after reset()", iter.hasPrevious());
+
+ // after reset, the results should be the same as before
+ assertEquals("First element should be the same", first, iter.next());
+ assertEquals("Second elment should be the same", second, iter.next());
+
+ // after passing the point, where we resetted, continuation should work as expected
+ for (int i = 2; i < testArray.length; i++) {
+ Object testValue = testArray[i];
+ E iterValue = iter.next();
+
+ assertEquals("Iteration value is correct", testValue, iterValue);
+ }
+ }
+
+}