diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f7b729baf..33a931369 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -27,6 +27,9 @@ Iterable instances. Additionally various supporting methods have been added to "IterableUtils" and "IteratorUtils". + + Added new "ZippingIterator" and factory methods "IteratorUtils#zippingIterator(...)". + Added new decorator "SkippingIterator" and factory methods "IteratorUtils#skippingIterator(...)". diff --git a/src/main/java/org/apache/commons/collections4/IteratorUtils.java b/src/main/java/org/apache/commons/collections4/IteratorUtils.java index 4ced0681b..7c826f65a 100644 --- a/src/main/java/org/apache/commons/collections4/IteratorUtils.java +++ b/src/main/java/org/apache/commons/collections4/IteratorUtils.java @@ -61,6 +61,7 @@ import org.apache.commons.collections4.iterators.TransformIterator; import org.apache.commons.collections4.iterators.UnmodifiableIterator; import org.apache.commons.collections4.iterators.UnmodifiableListIterator; import org.apache.commons.collections4.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections4.iterators.ZippingIterator; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -489,22 +490,6 @@ public class IteratorUtils { return new BoundedIterator(iterator, offset, max); } - // Skipping - //----------------------------------------------------------------------- - /** - * Decorates the specified iterator to skip the first N elements. - * - * @param the element type - * @param iterator the iterator to decorate - * @param offset the first number of elements to skip - * @return a new skipping iterator - * @throws IllegalArgumentException if the iterator is null or offset is negative - * @since 4.1 - */ - public static SkippingIterator skippingIterator(final Iterator iterator, long offset) { - return new SkippingIterator(iterator, offset); - } - // Unmodifiable //----------------------------------------------------------------------- /** @@ -914,6 +899,68 @@ public class IteratorUtils { return PushbackIterator.pushbackIterator(iterator); } + // Skipping + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator to skip the first N elements. + * + * @param the element type + * @param iterator the iterator to decorate + * @param offset the first number of elements to skip + * @return a new skipping iterator + * @throws IllegalArgumentException if the iterator is null or offset is negative + * @since 4.1 + */ + public static SkippingIterator skippingIterator(final Iterator iterator, long offset) { + return new SkippingIterator(iterator, offset); + } + + // Zipping + //----------------------------------------------------------------------- + /** + * Returns an iterator that interleaves elements from the decorated iterators. + * + * @param the element type + * @param a the first iterator to interleave + * @param b the second iterator to interleave + * @return an iterator, interleaving the decorated iterators + * @throws IllegalArgumentException if any iterator is null + * @since 4.1 + */ + public static ZippingIterator zippingIterator(final Iterator a, final Iterator b) { + return new ZippingIterator(a, b); + } + + /** + * Returns an iterator that interleaves elements from the decorated iterators. + * + * @param the element type + * @param a the first iterator to interleave + * @param b the second iterator to interleave + * @param c the third iterator to interleave + * @return an iterator, interleaving the decorated iterators + * @throws IllegalArgumentException if any iterator is null + * @since 4.1 + */ + public static ZippingIterator zippingIterator(final Iterator a, + final Iterator b, + final Iterator c) { + return new ZippingIterator(a, b, c); + } + + /** + * Returns an iterator that interleaves elements from the decorated iterators. + * + * @param the element type + * @param iterators the array of iterators to interleave + * @return an iterator, interleaving the decorated iterators + * @throws IllegalArgumentException if any iterator is null + * @since 4.1 + */ + public static ZippingIterator zippingIterator(final Iterator... iterators) { + return new ZippingIterator(iterators); + } + // Views //----------------------------------------------------------------------- /** diff --git a/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java b/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java index 21a4aa3ff..8937f3f26 100644 --- a/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java +++ b/src/main/java/org/apache/commons/collections4/iterators/ZippingIterator.java @@ -27,9 +27,9 @@ import org.apache.commons.collections4.FluentIterable; * Provides an interleaved iteration over the elements contained in a * collection of Iterators. *

- * Given two {@link Iterator} instances A and - * B, the {@link #next} method on this iterator will - * alternate between A.next() and B.next(). + * Given two {@link Iterator} instances {@code A} and {@code B}, the + * {@link #next} method on this iterator will switch between {@code A.next()} + * and {@code B.next()} until both iterators are exhausted. * * @since 4.1 * @version $Id$ @@ -38,8 +38,10 @@ public class ZippingIterator implements Iterator { /** The {@link Iterator}s to evaluate. */ private final Iterator> iterators; + /** The next iterator to use for next(). */ private Iterator nextIterator = null; + /** The last iterator which was used for next(). */ private Iterator lastReturned = null; @@ -50,8 +52,8 @@ public class ZippingIterator implements Iterator { * Constructs a new ZippingIterator that will provide * interleaved iteration over the two given iterators. * - * @param a the first child iterator - * @param b the second child iterator + * @param a the first child iterator + * @param b the second child iterator * @throws NullPointerException if either iterator is null */ @SuppressWarnings("unchecked") @@ -60,19 +62,34 @@ public class ZippingIterator implements Iterator { } /** - * Constructs a new ZippingIterator that will use the - * specified comparator to provide ordered iteration over the array of - * iterators. + * Constructs a new ZippingIterator that will provide + * interleaved iteration over the three given iterators. * - * @param iterators the array of iterators - * @throws NullPointerException if iterators array is or contains null + * @param a the first child iterator + * @param b the second child iterator + * @param c the third child iterator + * @throws NullPointerException if either iterator is null + */ + @SuppressWarnings("unchecked") + public ZippingIterator(final Iterator a, + final Iterator b, + final Iterator c) { + this(new Iterator[] {a, b, c}); + } + + /** + * Constructs a new ZippingIterator that will provide + * interleaved iteration of the specified iterators. + * + * @param iterators the array of iterators + * @throws NullPointerException if any iterator is null */ public ZippingIterator(final Iterator... iterators) { - // create a mutable list + // create a mutable list to be able to remove exhausted iterators final List> list = new ArrayList>(); - for (Iterator iterator : iterators) { + for (final Iterator iterator : iterators) { if (iterator == null) { - throw new NullPointerException("Iterator must not be null"); + throw new NullPointerException("Iterator must not be null."); } list.add(iterator); } @@ -83,21 +100,21 @@ public class ZippingIterator implements Iterator { // ------------------------------------------------------------------- /** - * Returns true if any child iterator has remaining elements. + * Returns {@code true} if any child iterator has remaining elements. * * @return true if this iterator has remaining elements */ public boolean hasNext() { // the next iterator has already been determined - // this might happen if hasNext() was called multiple + // this might happen if hasNext() is called multiple if (nextIterator != null) { return true; } while(iterators.hasNext()) { - final Iterator iterator = iterators.next(); - if (iterator.hasNext()) { - nextIterator = iterator; + final Iterator childIterator = iterators.next(); + if (childIterator.hasNext()) { + nextIterator = childIterator; return true; } else { // iterator is exhausted, remove it diff --git a/src/test/java/org/apache/commons/collections4/iterators/ZippingIteratorTest.java b/src/test/java/org/apache/commons/collections4/iterators/ZippingIteratorTest.java new file mode 100644 index 000000000..121a03712 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/iterators/ZippingIteratorTest.java @@ -0,0 +1,191 @@ +/* + * 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.collections4.iterators; + +import java.util.ArrayList; + +import org.apache.commons.collections4.IteratorUtils; + +/** + * Unit test suite for {@link ZippingIterator}. + * + * @version $Id$ + */ +@SuppressWarnings("boxing") +public class ZippingIteratorTest extends AbstractIteratorTest { + + //------------------------------------------------------------ Conventional + + public ZippingIteratorTest(final String testName) { + super(testName); + } + + //--------------------------------------------------------------- Lifecycle + + private ArrayList evens = null; + private ArrayList odds = null; + private ArrayList fib = null; + + @Override + public void setUp() throws Exception { + super.setUp(); + evens = new ArrayList(); + odds = new ArrayList(); + for (int i = 0; i < 20; i++) { + if (0 == i % 2) { + evens.add(i); + } else { + odds.add(i); + } + } + fib = new ArrayList(); + fib.add(1); + fib.add(1); + fib.add(2); + fib.add(3); + fib.add(5); + fib.add(8); + fib.add(13); + fib.add(21); + } + + //---------------------------------------------------- TestIterator Methods + + @Override + @SuppressWarnings("unchecked") + public ZippingIterator makeEmptyIterator() { + return new ZippingIterator(IteratorUtils.emptyIterator()); + } + + @Override + public ZippingIterator makeObject() { + return new ZippingIterator(evens.iterator(), odds.iterator(), fib.iterator()); + } + + //------------------------------------------------------------------- Tests + + public void testIterateEven() { + @SuppressWarnings("unchecked") + final ZippingIterator iter = new ZippingIterator(evens.iterator()); + for (int i = 0; i < evens.size(); i++) { + assertTrue(iter.hasNext()); + assertEquals(evens.get(i), iter.next()); + } + assertTrue(!iter.hasNext()); + } + + public void testIterateEvenOdd() { + final ZippingIterator iter = new ZippingIterator(evens.iterator(), odds.iterator()); + for (int i = 0; i < 20; i++) { + assertTrue(iter.hasNext()); + assertEquals(Integer.valueOf(i), iter.next()); + } + assertTrue(!iter.hasNext()); + } + + public void testIterateOddEven() { + final ZippingIterator iter = new ZippingIterator(odds.iterator(), evens.iterator()); + for (int i = 0, j = 0; i < 20; i++) { + assertTrue(iter.hasNext()); + int val = iter.next(); + if (i % 2 == 0) { + assertEquals(odds.get(j).intValue(), val); + } else { + assertEquals(evens.get(j).intValue(), val); + j++; + } + } + assertTrue(!iter.hasNext()); + } + + public void testIterateEvenEven() { + final ZippingIterator iter = new ZippingIterator(evens.iterator(), evens.iterator()); + for (int i = 0; i < evens.size(); i++) { + assertTrue(iter.hasNext()); + assertEquals(evens.get(i), iter.next()); + assertTrue(iter.hasNext()); + assertEquals(evens.get(i), iter.next()); + } + assertTrue(!iter.hasNext()); + } + + public void testIterateFibEvenOdd() { + final ZippingIterator iter = new ZippingIterator(fib.iterator(), evens.iterator(), odds.iterator()); + + assertEquals(Integer.valueOf(1),iter.next()); // fib 1 + assertEquals(Integer.valueOf(0),iter.next()); // even 0 + assertEquals(Integer.valueOf(1),iter.next()); // odd 1 + assertEquals(Integer.valueOf(1),iter.next()); // fib 1 + assertEquals(Integer.valueOf(2),iter.next()); // even 2 + assertEquals(Integer.valueOf(3),iter.next()); // odd 3 + assertEquals(Integer.valueOf(2),iter.next()); // fib 2 + assertEquals(Integer.valueOf(4),iter.next()); // even 4 + assertEquals(Integer.valueOf(5),iter.next()); // odd 5 + assertEquals(Integer.valueOf(3),iter.next()); // fib 3 + assertEquals(Integer.valueOf(6),iter.next()); // even 6 + assertEquals(Integer.valueOf(7),iter.next()); // odd 7 + assertEquals(Integer.valueOf(5),iter.next()); // fib 5 + assertEquals(Integer.valueOf(8),iter.next()); // even 8 + assertEquals(Integer.valueOf(9),iter.next()); // odd 9 + assertEquals(Integer.valueOf(8),iter.next()); // fib 8 + assertEquals(Integer.valueOf(10),iter.next()); // even 10 + assertEquals(Integer.valueOf(11),iter.next()); // odd 11 + assertEquals(Integer.valueOf(13),iter.next()); // fib 13 + assertEquals(Integer.valueOf(12),iter.next()); // even 12 + assertEquals(Integer.valueOf(13),iter.next()); // odd 13 + assertEquals(Integer.valueOf(21),iter.next()); // fib 21 + assertEquals(Integer.valueOf(14),iter.next()); // even 14 + assertEquals(Integer.valueOf(15),iter.next()); // odd 15 + assertEquals(Integer.valueOf(16),iter.next()); // even 16 + assertEquals(Integer.valueOf(17),iter.next()); // odd 17 + assertEquals(Integer.valueOf(18),iter.next()); // even 18 + assertEquals(Integer.valueOf(19),iter.next()); // odd 19 + + assertTrue(!iter.hasNext()); + } + + public void testRemoveFromSingle() { + @SuppressWarnings("unchecked") + final ZippingIterator iter = new ZippingIterator(evens.iterator()); + int expectedSize = evens.size(); + while (iter.hasNext()) { + final Object o = iter.next(); + final Integer val = (Integer) o; + if (val.intValue() % 4 == 0) { + expectedSize--; + iter.remove(); + } + } + assertEquals(expectedSize, evens.size()); + } + + public void testRemoveFromDouble() { + final ZippingIterator iter = new ZippingIterator(evens.iterator(), odds.iterator()); + int expectedSize = evens.size() + odds.size(); + while (iter.hasNext()) { + final Object o = iter.next(); + final Integer val = (Integer) o; + if (val.intValue() % 4 == 0 || val.intValue() % 3 == 0) { + expectedSize--; + iter.remove(); + } + } + assertEquals(expectedSize, evens.size() + odds.size()); + } + +} +