From 613d1acbb10c9c0d04f94eaa9b3686c2e357f1ae Mon Sep 17 00:00:00 2001 From: Matthew Jason Benson Date: Mon, 9 Mar 2009 21:43:53 +0000 Subject: [PATCH] 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 --- .../iterators/ListIteratorWrapper.java | 108 +++++++-- .../iterators/TestListIteratorWrapper.java | 92 +++++++- .../iterators/TestListIteratorWrapper2.java | 213 ++++++++++++++++++ 3 files changed, 392 insertions(+), 21 deletions(-) create mode 100644 src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java 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 Iterators 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 Iterators. *

* 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 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); + } + } + +}