mirror of
https://github.com/apache/commons-collections.git
synced 2025-02-17 15:35:00 +00:00
[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:
parent
139aa3660c
commit
71839dd264
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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) {}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user