Author: mbenson Date: Mon Mar 9 21:43:53 2009 New Revision: 751857 URL: http://svn.apache.org/viewvc?rev=751857&view=rev Log: handle more ListIterator functionality when possible
Added: commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java (with props) Modified: commons/proper/collections/branches/collections_jdk5_branch/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java Modified: commons/proper/collections/branches/collections_jdk5_branch/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java URL: http://svn.apache.org/viewvc/commons/proper/collections/branches/collections_jdk5_branch/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java?rev=751857&r1=751856&r2=751857&view=diff ============================================================================== --- commons/proper/collections/branches/collections_jdk5_branch/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java (original) +++ commons/proper/collections/branches/collections_jdk5_branch/src/java/org/apache/commons/collections/iterators/ListIteratorWrapper.java Mon Mar 9 21:43:53 2009 @@ -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 @@ * <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 @@ * * @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 @@ 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 @@ // 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 @@ * @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 @@ * @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 @@ * @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 @@ 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 @@ * @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 @@ * @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 @@ * @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; } Modified: commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java URL: http://svn.apache.org/viewvc/commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java?rev=751857&r1=751856&r2=751857&view=diff ============================================================================== --- commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java (original) +++ commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper.java Mon Mar 9 21:43:53 2009 @@ -115,13 +115,99 @@ 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("FilterIterator does not support the remove() method"); - } catch (UnsupportedOperationException e) { + 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() { Added: commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java URL: http://svn.apache.org/viewvc/commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java?rev=751857&view=auto ============================================================================== --- commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java (added) +++ commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java Mon Mar 9 21:43:53 2009 @@ -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); + } + } + +} Propchange: commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/collections/branches/collections_jdk5_branch/src/test/org/apache/commons/collections/iterators/TestListIteratorWrapper2.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL