Add LoopingListIterator

bug 30166, from Eric Crampton


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@131808 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2004-07-17 21:02:47 +00:00
parent 74f6f169c8
commit 487621cb99
4 changed files with 625 additions and 2 deletions

View File

@ -34,7 +34,7 @@ The only new deprecations are ................
<center><h3>NEW CLASSES</h3></center>
<ul>
<li>........</li>
<li>LoopingListIterator - When the end of the list is reached the iteration continues from the start</li>
</ul>
<center><h3>ENHANCEMENTS</h3></center>

View File

@ -0,0 +1,254 @@
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import org.apache.commons.collections.ResettableIterator;
/**
* A ListIterator that restarts when it reaches the end or when it
* reaches the beginning.
* <p>
* The iterator will loop continuously around the provided list,
* unless there are no elements in the collection to begin with, or
* all of the elements have been {@link #remove removed}.
* <p>
* Concurrent modifications are not directly supported, and for most
* collection implementations will throw a
* ConcurrentModificationException.
*
* @since Commons Collections 3.2
* @version $Revision: 1.1 $ $Date: 2004/07/17 21:02:47 $
*
* @author Eric Crampton <ccesc@eonomine.com>
*/
public class LoopingListIterator implements ResettableIterator {
/** The list to base the iterator on */
private List list;
/** The current list iterator */
private ListIterator iterator;
/**
* Constructor that wraps a list.
* <p>
* There is no way to reset a ListIterator instance without
* recreating it from the original source, so the List must be
* passed in and a reference to it held.
*
* @param list the list to wrap
* @throws NullPointerException if the list it null
*/
public LoopingListIterator(List list) {
if (list == null) {
throw new NullPointerException("The list must not be null");
}
this.list = list;
reset();
}
/**
* Returns whether this iterator has any more elements.
* <p>
* Returns false only if the list originally had zero elements, or
* all elements have been {@link #remove removed}.
*
* @return <code>true</code> if there are more elements
*/
public boolean hasNext() {
return !list.isEmpty();
}
/**
* Returns the next object in the list.
* <p>
* If at the end of the list, returns the first element.
*
* @return the object after the last element returned
* @throws NoSuchElementException if there are no elements in the list
*/
public Object next() {
if (list.isEmpty()) {
throw new NoSuchElementException(
"There are no elements for this iterator to loop on");
}
if (iterator.hasNext() == false) {
reset();
}
return iterator.next();
}
/**
* Returns the index of the element that would be returned by a
* subsequent call to {@link #next}.
* <p>
* As would be expected, if the iterator is at the physical end of
* the underlying list, 0 is returned, signifying the beginning of
* the list.
*
* @return the index of the element that would be returned if next() were called
* @throws NoSuchElementException if there are no elements in the list
*/
public int nextIndex() {
if (list.isEmpty()) {
throw new NoSuchElementException(
"There are no elements for this iterator to loop on");
}
if (iterator.hasNext() == false) {
return 0;
} else {
return iterator.nextIndex();
}
}
/**
* Returns whether this iterator has any more previous elements.
* <p>
* Returns false only if the list originally had zero elements, or
* all elements have been {@link #remove removed}.
*
* @return <code>true</code> if there are more elements
*/
public boolean hasPrevious() {
return !list.isEmpty();
}
/**
* Returns the previous object in the list.
* <p>
* If at the beginning of the list, return the last element. Note
* that in this case, traversal to find that element takes linear time.
*
* @return the object before the last element returned
* @throws NoSuchElementException if there are no elements in the list
*/
public Object previous() {
if (list.isEmpty()) {
throw new NoSuchElementException(
"There are no elements for this iterator to loop on");
}
if (iterator.hasPrevious() == false) {
Object result = null;
while (iterator.hasNext()) {
result = iterator.next();
}
iterator.previous();
return result;
} else {
return iterator.previous();
}
}
/**
* Returns the index of the element that would be returned by a
* subsequent call to {@link #previous}.
* <p>
* As would be expected, if at the iterator is at the physical
* beginning of the underlying list, the list's size minus one is
* returned, signifying the end of the list.
*
* @return the index of the element that would be returned if previous() were called
* @throws NoSuchElementException if there are no elements in the list
*/
public int previousIndex() {
if (list.isEmpty()) {
throw new NoSuchElementException(
"There are no elements for this iterator to loop on");
}
if (iterator.hasPrevious() == false) {
return list.size() - 1;
} else {
return iterator.previousIndex();
}
}
/**
* Removes the previously retrieved item from the underlying list.
* <p>
* This feature is only supported if the underlying list's
* {@link List#iterator iterator} method returns an implementation
* that supports it.
* <p>
* This method can only be called after at least one {@link #next}
* or {@link #previous} method call. After a removal, the remove
* method may not be called again until another {@link #next} or
* {@link #previous} has been performed. If the {@link #reset} is
* called, then remove may not be called until {@link #next} or
* {@link #previous} is called again.
*
* @throws UnsupportedOperationException if the remove method is
* not supported by the iterator implementation of the underlying
* list
*/
public void remove() {
iterator.remove();
}
/**
* Inserts the specified element into the underlying list.
* <p>
* The element is inserted before the next element that would be
* returned by {@link #next}, if any, and after the next element
* that would be returned by {@link #previous}, if any.
* <p>
* This feature is only supported if the underlying list's
* {@link List#listIterator} method returns an implementation
* that supports it.
*
* @param obj the element to insert
* @throws UnsupportedOperationException if the add method is not
* supported by the iterator implementation of the underlying list
*/
public void add(Object obj) {
iterator.add(obj);
}
/**
* Replaces the last element that was returned by {@link #next} or
* {@link #previous}.
* <p>
* This feature is only supported if the underlying list's
* {@link List#listIterator} method returns an implementation
* that supports it.
*
* @param obj the element with which to replace the last element returned
* @throws UnsupportedOperationException if the set method is not
* supported by the iterator implementation of the underlying list
*/
public void set(Object obj) {
iterator.set(obj);
}
/**
* Resets the iterator back to the start of the list.
*/
public void reset() {
iterator = list.listIterator();
}
/**
* Gets the size of the list underlying the iterator.
*
* @return the current list size
*/
public int size() {
return list.size();
}
}

View File

@ -22,7 +22,7 @@ import junit.framework.TestSuite;
/**
* Entry point for all iterator tests.
*
* @version $Revision: 1.15 $ $Date: 2004/04/09 14:38:47 $
* @version $Revision: 1.16 $ $Date: 2004/07/17 21:02:47 $
*
* @author Rodney Waldhoff
*/
@ -47,6 +47,7 @@ public class TestAll extends TestCase {
suite.addTest(TestIteratorChain.suite());
suite.addTest(TestListIteratorWrapper.suite());
suite.addTest(TestLoopingIterator.suite());
suite.addTest(TestLoopingListIterator.suite());
suite.addTest(TestSingletonIterator.suite());
suite.addTest(TestSingletonIterator2.suite());
suite.addTest(TestSingletonListIterator.suite());

View File

@ -0,0 +1,368 @@
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Tests the LoopingListIterator class.
*
* @version $Revision: 1.1 $ $Date: 2004/07/17 21:02:47 $
*
* @author Eric Crampton <ccesc@eonomine.com>
*/
public class TestLoopingListIterator extends TestCase {
public TestLoopingListIterator(String testName) {
super(testName);
}
public static Test suite() {
return new TestSuite(TestLoopingListIterator.class);
}
/**
* Tests constructor exception.
*/
public void testConstructorEx() throws Exception {
try {
new LoopingListIterator(null);
fail();
} catch (NullPointerException ex) {
}
}
/**
* Tests whether an empty looping list iterator works.
*/
public void testLooping0() throws Exception {
List list = new ArrayList();
LoopingListIterator loop = new LoopingListIterator(list);
assertFalse(loop.hasNext());
assertFalse(loop.hasPrevious());
try {
loop.next();
fail();
} catch (NoSuchElementException ex) {
}
try {
loop.previous();
fail();
} catch (NoSuchElementException ex) {
}
}
/**
* Tests whether a looping list iterator works on a list with only
* one element.
*/
public void testLooping1() throws Exception {
List list = new ArrayList(Arrays.asList(new String[] { "a" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a>
assertTrue(loop.hasNext());
assertEquals("a", loop.next()); // <a>
assertTrue(loop.hasNext());
assertEquals("a", loop.next()); // <a>
assertTrue(loop.hasNext());
assertEquals("a", loop.next()); // <a>
assertTrue(loop.hasPrevious());
assertEquals("a", loop.previous()); // <a>
assertTrue(loop.hasPrevious());
assertEquals("a", loop.previous()); // <a>
assertTrue(loop.hasPrevious());
assertEquals("a", loop.previous()); // <a>
}
/**
* Tests whether a looping list iterator works on a list with two
* elements.
*/
public void testLooping2() throws Exception {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b
assertTrue(loop.hasNext());
assertEquals("a", loop.next()); // a <b>
assertTrue(loop.hasNext());
assertEquals("b", loop.next()); // <a> b
assertTrue(loop.hasNext());
assertEquals("a", loop.next()); // a <b>
// Reset the iterator and try using previous.
loop.reset(); // <a> b
assertTrue(loop.hasPrevious());
assertEquals("b", loop.previous()); // a <b>
assertTrue(loop.hasPrevious());
assertEquals("a", loop.previous()); // <a> b
assertTrue(loop.hasPrevious());
assertEquals("b", loop.previous()); // a <b>
}
/**
* Tests jogging back and forth between two elements, but not over
* the begin/end boundary of the list.
*/
public void testJoggingNotOverBoundary() {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b
// Try jogging back and forth between the elements, but not
// over the begin/end boundary.
loop.reset();
assertEquals("a", loop.next()); // a <b>
assertEquals("a", loop.previous()); // <a> b
assertEquals("a", loop.next()); // a <b>
assertEquals("b", loop.next()); // <a> b
assertEquals("b", loop.previous()); // a <b>
assertEquals("b", loop.next()); // <a> b
}
/**
* Tests jogging back and forth between two elements over the
* begin/end boundary of the list.
*/
public void testJoggingOverBoundary() {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b
// Try jogging back and forth between the elements, but not
// over the begin/end boundary.
assertEquals("b", loop.previous()); // a <b>
assertEquals("b", loop.next()); // <a> b
assertEquals("b", loop.previous()); // a <b>
assertEquals("a", loop.previous()); // <a> b
assertEquals("a", loop.next()); // a <b>
assertEquals("a", loop.previous()); // <a> b
}
/**
* Tests removing an element from a wrapped ArrayList.
*/
public void testRemovingElementsAndIteratingForward() {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b", "c" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b c
assertTrue(loop.hasNext());
assertEquals("a", loop.next()); // a <b> c
loop.remove(); // <b> c
assertEquals(2, list.size());
assertTrue(loop.hasNext());
assertEquals("b", loop.next()); // b <c>
loop.remove(); // <c>
assertEquals(1, list.size());
assertTrue(loop.hasNext());
assertEquals("c", loop.next()); // <c>
loop.remove(); // ---
assertEquals(0, list.size());
assertFalse(loop.hasNext());
try {
loop.next();
fail();
} catch (NoSuchElementException ex) {
}
}
/**
* Tests removing an element from a wrapped ArrayList.
*/
public void testRemovingElementsAndIteratingBackwards() {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b", "c" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b c
assertTrue(loop.hasPrevious());
assertEquals("c", loop.previous()); // a b <c>
loop.remove(); // <a> b
assertEquals(2, list.size());
assertTrue(loop.hasPrevious());
assertEquals("b", loop.previous()); // a <b>
loop.remove(); // <a>
assertEquals(1, list.size());
assertTrue(loop.hasPrevious());
assertEquals("a", loop.previous()); // <a>
loop.remove(); // ---
assertEquals(0, list.size());
assertFalse(loop.hasPrevious());
try {
loop.previous();
fail();
} catch (NoSuchElementException ex) {
}
}
/**
* Tests the reset method.
*/
public void testReset() {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b", "c" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b c
assertEquals("a", loop.next()); // a <b> c
assertEquals("b", loop.next()); // a b <c>
loop.reset(); // <a> b c
assertEquals("a", loop.next()); // a <b> c
loop.reset(); // <a> b c
assertEquals("a", loop.next()); // a <b> c
assertEquals("b", loop.next()); // a b <c>
assertEquals("c", loop.next()); // <a> b c
loop.reset(); // <a> b c
assertEquals("c", loop.previous()); // a b <c>
assertEquals("b", loop.previous()); // a <b> c
loop.reset(); // <a> b c
assertEquals("c", loop.previous()); // a b <c>
loop.reset(); // <a> b c
assertEquals("c", loop.previous()); // a b <c>
assertEquals("b", loop.previous()); // a <b> c
assertEquals("a", loop.previous()); // <a> b c
}
/**
* Tests the add method.
*/
public void testAdd() {
List list = new ArrayList(Arrays.asList(new String[] { "b", "e", "f" }));
LoopingListIterator loop = new LoopingListIterator(list); // <b> e f
loop.add("a"); // <a> b e f
assertEquals("b", loop.next()); // a <b> e f
loop.reset(); // <a> b e f
assertEquals("a", loop.next()); // a <b> e f
assertEquals("b", loop.next()); // a b <e> f
loop.add("c"); // a b c <e> f
assertEquals("e", loop.next()); // a b c e <f>
assertEquals("e", loop.previous()); // a b c <e> f
assertEquals("c", loop.previous()); // a b <c> e f
assertEquals("c", loop.next()); // a b c <e> f
loop.add("d"); // a b c d <e> f
loop.reset(); // <a> b c d e f
assertEquals("a", loop.next()); // a <b> c d e f
assertEquals("b", loop.next()); // a b <c> d e f
assertEquals("c", loop.next()); // a b c <d> e f
assertEquals("d", loop.next()); // a b c d <e> f
assertEquals("e", loop.next()); // a b c d e <f>
assertEquals("f", loop.next()); // <a> b c d e f
assertEquals("a", loop.next()); // a <b> c d e f
list = new ArrayList(Arrays.asList(new String[] { "b", "e", "f" }));
loop = new LoopingListIterator(list); // <b> e f
loop.add("a"); // a <b> e f
assertEquals("a", loop.previous()); // a b e <f>
loop.reset(); // <a> b e f
assertEquals("f", loop.previous()); // a b e <f>
assertEquals("e", loop.previous()); // a b <e> f
loop.add("d"); // a b d <e> f
assertEquals("d", loop.previous()); // a b <d> e f
loop.add("c"); // a b c <d> e f
assertEquals("c", loop.previous()); // a b <c> d e f
loop.reset();
assertEquals("a", loop.next()); // a <b> c d e f
assertEquals("b", loop.next()); // a b <c> d e f
assertEquals("c", loop.next()); // a b c <d> e f
assertEquals("d", loop.next()); // a b c d <e> f
assertEquals("e", loop.next()); // a b c d e <f>
assertEquals("f", loop.next()); // <a> b c d e f
assertEquals("a", loop.next()); // a <b> c d e f
}
/**
* Tests nextIndex and previousIndex.
*/
public void testNextAndPreviousIndex() {
List list = new ArrayList(Arrays.asList(new String[] { "a", "b", "c" }));
LoopingListIterator loop = new LoopingListIterator(list); // <a> b c
assertEquals(0, loop.nextIndex());
assertEquals(2, loop.previousIndex());
assertEquals("a", loop.next()); // a <b> c
assertEquals(1, loop.nextIndex());
assertEquals(0, loop.previousIndex());
assertEquals("a", loop.previous()); // <a> b c
assertEquals(0, loop.nextIndex());
assertEquals(2, loop.previousIndex());
assertEquals("c", loop.previous()); // a b <c>
assertEquals(2, loop.nextIndex());
assertEquals(1, loop.previousIndex());
assertEquals("b", loop.previous()); // a <b> c
assertEquals(1, loop.nextIndex());
assertEquals(0, loop.previousIndex());
assertEquals("a", loop.previous()); // <a> b c
assertEquals(0, loop.nextIndex());
assertEquals(2, loop.previousIndex());
}
/**
* Tests using the set method to change elements.
*/
public void testSet() {
List list = new ArrayList(Arrays.asList(new String[] { "q", "r", "z" }));
LoopingListIterator loop = new LoopingListIterator(list); // <q> r z
assertEquals("z", loop.previous()); // q r <z>
loop.set("c"); // q r <c>
loop.reset(); // <q> r c
assertEquals("q", loop.next()); // q <r> c
loop.set("a"); // a <r> c
assertEquals("r", loop.next()); // a r <c>
loop.set("b"); // a b <c>
loop.reset(); // <a> b c
assertEquals("a", loop.next()); // a <b> c
assertEquals("b", loop.next()); // a b <c>
assertEquals("c", loop.next()); // <a> b c
}
}