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