handle more ListIterator functionality when possible

git-svn-id: 
https://svn.apache.org/repos/asf/commons/proper/collections/branches/collections_jdk5_branch@751857
 13f79535-47bb-0310-9956-ffa450edef68


Project: http://git-wip-us.apache.org/repos/asf/commons-collections/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/commons-collections/commit/613d1acb
Tree: http://git-wip-us.apache.org/repos/asf/commons-collections/tree/613d1acb
Diff: http://git-wip-us.apache.org/repos/asf/commons-collections/diff/613d1acb

Branch: refs/heads/collections_jdk5_branch
Commit: 613d1acbb10c9c0d04f94eaa9b3686c2e357f1ae
Parents: e53e8f2
Author: Matthew Jason Benson <mben...@apache.org>
Authored: Mon Mar 9 21:43:53 2009 +0000
Committer: Matthew Jason Benson <mben...@apache.org>
Committed: Mon Mar 9 21:43:53 2009 +0000

----------------------------------------------------------------------
 .../iterators/ListIteratorWrapper.java          | 108 ++++++++--
 .../iterators/TestListIteratorWrapper.java      |  90 +++++++-
 .../iterators/TestListIteratorWrapper2.java     | 213 +++++++++++++++++++
 3 files changed, 391 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-collections/blob/613d1acb/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java 
b/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java
index 4abd2f5..6a9e5bf 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 <code>Iterator</code>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}.
  * <p>
  * The <code>ListIterator</code> interface has additional useful methods
  * for navigation - <code>previous()</code> and the index methods.
@@ -32,7 +38,7 @@ import org.apache.commons.collections.ResettableListIterator;
  * <code>ListIterator</code>. It achieves this by building a list internally
  * of as the underlying iterator is traversed.
  * <p>
- * The optional operations of <code>ListIterator</code> are not supported.
+ * The optional operations of <code>ListIterator</code> are not supported for 
plain <code>Iterator</code>s.
  * <p>
  * 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<E> implements ResettableListIterator<E> {
 
-    /** 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<E> implements 
ResettableListIterator<E> {
     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<E> implements 
ResettableListIterator<E> {
     // ListIterator interface
     //-------------------------------------------------------------------------
     /**
-     * Throws {@link UnsupportedOperationException}.
+     * Throws {@link UnsupportedOperationException}
+     * unless the underlying <code>Iterator</code> is a 
<code>ListIterator</code>.
      *
-     * @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<E> li = (ListIterator<E>) iterator;
+            li.add(obj);
+            return;
+        }
         throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
     }
 
@@ -93,7 +112,7 @@ public class ListIteratorWrapper<E> implements 
ResettableListIterator<E> {
      * @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<E> implements 
ResettableListIterator<E> {
      * @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<E> implements 
ResettableListIterator<E> {
      * @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<E> implements 
ResettableListIterator<E> {
         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<E> implements 
ResettableListIterator<E> {
      * @throws NoSuchElementException  if there are no previous elements
      */
     public E previous() throws NoSuchElementException {
+        if (iterator instanceof ListIterator) {
+            @SuppressWarnings("unchecked")
+            ListIterator<E> li = (ListIterator<E>) 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<E> implements 
ResettableListIterator<E> {
      * @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 <code>Iterator</code> is a 
<code>ListIterator</code>.
      *
-     * @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<E> li = (ListIterator<E>) iterator;
+            li.set(obj);
+            return;
+        }
         throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
     }
 
@@ -190,6 +254,14 @@ public class ListIteratorWrapper<E> implements 
ResettableListIterator<E> {
      * @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;
     }
 

http://git-wip-us.apache.org/repos/asf/commons-collections/blob/613d1acb/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java
----------------------------------------------------------------------
diff --git 
a/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java
 
b/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java
index 6f108b0..8571356 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<E> extends 
AbstractTestIterator<E> {
     public void testRemove() {
         ListIterator<E> 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() {

http://git-wip-us.apache.org/repos/asf/commons-collections/blob/613d1acb/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java
----------------------------------------------------------------------
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 0000000..acad9fe
--- /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<E> extends AbstractTestIterator<E> {
+
+    protected String[] testArray = {
+        "One", "Two", "Three", "Four", "Five", "Six"
+    };
+
+    protected List<E> 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<E>();
+        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<E> makeEmptyIterator() {
+        ArrayList<E> list = new ArrayList<E>();
+        return new ListIteratorWrapper<E>(list.listIterator());
+    }
+
+    public ResettableListIterator<E> makeObject() {
+        return new ListIteratorWrapper<E>(list1.listIterator());
+    }
+
+    public void testIterator() {
+        ListIterator<E> 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<E> 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<E> 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);
+        }
+    }
+
+}

Reply via email to