Author: tn Date: Thu Feb 28 14:25:42 2013 New Revision: 1451210 URL: http://svn.apache.org/r1451210 Log: [COLLECTIONS-396] Added new LazyIteratorChain in iterators. Thanks to Jeff Rodriguez.
Added: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java (with props) commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java (with props) Added: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java?rev=1451210&view=auto ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java (added) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java Thu Feb 28 14:25:42 2013 @@ -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(); + } + +} Propchange: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java ------------------------------------------------------------------------------ svn:keywords = Id Revision HeadURL Propchange: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java?rev=1451210&view=auto ============================================================================== --- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java (added) +++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java Thu Feb 28 14:25:42 2013 @@ -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) {} + } + +} Propchange: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java ------------------------------------------------------------------------------ svn:keywords = Id Revision HeadURL Propchange: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java ------------------------------------------------------------------------------ svn:mime-type = text/plain