You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by tn...@apache.org on 2013/02/28 15:25:43 UTC
svn commit: r1451210 - in /commons/proper/collections/trunk/src:
main/java/org/apache/commons/collections/iterators/LazyIteratorChain.java
test/java/org/apache/commons/collections/iterators/LazyIteratorChainTest.java
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