[COLLECTIONS-396] Added new LazyIteratorChain in iterators. Thanks to Jeff Rodriguez.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1451210 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2013-02-28 14:25:42 +00:00
parent 139aa3660c
commit 71839dd264
2 changed files with 351 additions and 0 deletions

View File

@ -0,0 +1,150 @@
/*
* 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.Iterator;
/**
* An LazyIteratorChain is an Iterator that wraps a number of Iterators in a lazy manner.
* <p>
* This class makes multiple iterators look like one to the caller. When any
* method from the Iterator interface is called, the LazyIteratorChain will delegate
* to a single underlying Iterator. The LazyIteratorChain will invoke the Iterators
* in sequence until all Iterators are exhausted.
* <p>
* The Iterators are provided by {@link #nextIterator(int)} which has to be overridden by
* sub-classes and allows to lazily create the Iterators as they are accessed:
* <pre>
* return new LazyIteratorChain<String>() {
* protected Iterator<String> nextIterator(int count) {
* return count == 1 ? Arrays.asList("foo", "bar").iterator() : null;
* }
* };
* </pre>
* <p>
* Once the inner Iterator's {@link Iterator#hasNext()} method returns false,
* {@link #nextIterator(int)} will be called to obtain another iterator, and so on
* until {@link #nextIterator(int)} returns null, indicating that the chain is exhausted.
* <p>
* NOTE: The LazyIteratorChain may contain no iterators. In this case the class will
* function as an empty iterator.
*
* @since 4.0
* @version $Id $
*/
public abstract class LazyIteratorChain<E> implements Iterator<E> {
/** The number of times {@link #nextIterator()} was already called. */
private int callCounter = 0;
/** Indicates that the Iterator chain has been exhausted. */
private boolean chainExhausted = false;
/** The current iterator. */
private Iterator<? extends E> currentIterator = null;
/**
* The "last used" Iterator is the Iterator upon which next() or hasNext()
* was most recently called used for the remove() operation only.
*/
private Iterator<? extends E> lastUsedIterator = null;
//-----------------------------------------------------------------------
/**
* Gets the next iterator after the previous one has been exhausted.
* <p>
* This method <b>MUST</b> return null when there are no more iterators.
*
* @param count the number of time this method has been called (starts with 1)
* @return the next iterator, or null if there are no more.
*/
protected abstract Iterator<? extends E> nextIterator(int count);
/**
* Updates the current iterator field to ensure that the current Iterator
* is not exhausted.
*/
private void updateCurrentIterator() {
if (callCounter == 0) {
currentIterator = nextIterator(++callCounter);
if (currentIterator == null) {
currentIterator = EmptyIterator.<E>emptyIterator();
chainExhausted = true;
}
// set last used iterator here, in case the user calls remove
// before calling hasNext() or next() (although they shouldn't)
lastUsedIterator = currentIterator;
}
while (currentIterator.hasNext() == false && !chainExhausted) {
final Iterator<? extends E> nextIterator = nextIterator(++callCounter);
if (nextIterator != null) {
currentIterator = nextIterator;
} else {
chainExhausted = true;
}
}
}
//-----------------------------------------------------------------------
/**
* Return true if any Iterator in the chain has a remaining element.
*
* @return true if elements remain
*/
public boolean hasNext() {
updateCurrentIterator();
lastUsedIterator = currentIterator;
return currentIterator.hasNext();
}
/**
* Returns the next element of the current Iterator
*
* @return element from the current Iterator
* @throws java.util.NoSuchElementException if all the Iterators are exhausted
*/
public E next() {
updateCurrentIterator();
lastUsedIterator = currentIterator;
return currentIterator.next();
}
/**
* Removes from the underlying collection the last element returned by the Iterator.
* <p>
* As with next() and hasNext(), this method calls remove() on the underlying Iterator.
* Therefore, this method may throw an UnsupportedOperationException if the underlying
* Iterator does not support this method.
*
* @throws UnsupportedOperationException if the remove operator is not
* supported by the underlying Iterator
* @throws IllegalStateException if the next method has not yet been called,
* or the remove method has already been called after the last call to the next method.
*/
public void remove() {
if (currentIterator == null) {
updateCurrentIterator();
}
lastUsedIterator.remove();
}
}

View File

@ -0,0 +1,201 @@
/*
* 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.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Predicate;
/**
* Tests the LazyIteratorChain class.
*
* @version $Id$
*/
public class LazyIteratorChainTest extends AbstractIteratorTest<String> {
protected String[] testArray = {
"One", "Two", "Three", "Four", "Five", "Six"
};
protected List<String> list1 = null;
protected List<String> list2 = null;
protected List<String> list3 = null;
public LazyIteratorChainTest(final String testName) {
super(testName);
}
@Override
public void setUp() {
list1 = new ArrayList<String>();
list1.add("One");
list1.add("Two");
list1.add("Three");
list2 = new ArrayList<String>();
list2.add("Four");
list3 = new ArrayList<String>();
list3.add("Five");
list3.add("Six");
}
@Override
public LazyIteratorChain<String> makeEmptyIterator() {
return new LazyIteratorChain<String>() {
@Override
protected Iterator<String> nextIterator(int count) {
return null;
}
};
}
@Override
public LazyIteratorChain<String> makeObject() {
final LazyIteratorChain<String> chain = new LazyIteratorChain<String>() {
@Override
protected Iterator<String> nextIterator(int count) {
switch (count) {
case 1:
return list1.iterator();
case 2:
return list2.iterator();
case 3:
return list3.iterator();
}
return null;
}
};
return chain;
}
public void testIterator() {
final Iterator<String> iter = makeObject();
for (final String testValue : testArray) {
final Object iterValue = iter.next();
assertEquals( "Iteration value is correct", testValue, iterValue );
}
assertTrue("Iterator should now be empty", !iter.hasNext());
try {
iter.next();
} catch (final Exception e) {
assertTrue("NoSuchElementException must be thrown",
e.getClass().equals(new NoSuchElementException().getClass()));
}
}
public void testRemoveFromFilteredIterator() {
final Predicate<Integer> myPredicate = new Predicate<Integer>() {
public boolean evaluate(final Integer i) {
return i.compareTo(new Integer(4)) < 0;
}
};
final List<Integer> list1 = new ArrayList<Integer>();
final List<Integer> list2 = new ArrayList<Integer>();
list1.add(new Integer(1));
list1.add(new Integer(2));
list2.add(new Integer(3));
list2.add(new Integer(4)); // will be ignored by the predicate
final Iterator<Integer> it1 = IteratorUtils.filteredIterator(list1.iterator(), myPredicate);
final Iterator<Integer> it2 = IteratorUtils.filteredIterator(list2.iterator(), myPredicate);
final Iterator<Integer> it = IteratorUtils.chainedIterator(it1, it2);
while (it.hasNext()) {
it.next();
it.remove();
}
assertEquals(0, list1.size());
assertEquals(1, list2.size());
}
@Override
public void testRemove() {
final Iterator<String> iter = makeObject();
try {
iter.remove();
fail("Calling remove before the first call to next() should throw an exception");
} catch (final IllegalStateException e) {
}
for (final String testValue : testArray) {
final String iterValue = iter.next();
assertEquals("Iteration value is correct", testValue, iterValue);
if (!iterValue.equals("Four")) {
iter.remove();
}
}
assertTrue("List is empty",list1.size() == 0);
assertTrue("List is empty",list2.size() == 1);
assertTrue("List is empty",list3.size() == 0);
}
public void testFirstIteratorIsEmptyBug() {
final List<String> empty = new ArrayList<String>();
final List<String> notEmpty = new ArrayList<String>();
notEmpty.add("A");
notEmpty.add("B");
notEmpty.add("C");
final LazyIteratorChain<String> chain = new LazyIteratorChain<String>() {
@Override
protected Iterator<String> nextIterator(int count) {
switch (count) {
case 1:
return empty.iterator();
case 2:
return notEmpty.iterator();
}
return null;
}
};
assertTrue("should have next",chain.hasNext());
assertEquals("A",chain.next());
assertTrue("should have next",chain.hasNext());
assertEquals("B",chain.next());
assertTrue("should have next",chain.hasNext());
assertEquals("C",chain.next());
assertTrue("should not have next",!chain.hasNext());
}
public void testEmptyChain() {
final LazyIteratorChain<String> chain = makeEmptyIterator();
assertEquals(false, chain.hasNext());
try {
chain.next();
fail();
} catch (final NoSuchElementException ex) {}
try {
chain.remove();
fail();
} catch (final IllegalStateException ex) {}
}
}