diff --git a/src/main/java/org/apache/commons/collections4/iterators/PairingIterator.java b/src/main/java/org/apache/commons/collections4/iterators/PairingIterator.java new file mode 100644 index 0000000000..4b95884ef5 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/iterators/PairingIterator.java @@ -0,0 +1,166 @@ +/* + * 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.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import org.apache.commons.collections4.iterators.PairingIterator.Entry; + +/** + * Provides an iteration over the elements of two iterators. It will be return a + * {@link Entry} containing the child iterators elements. + *

+ * Given two {@link Iterator} instances {@code A} and {@code B}. The + * {@link #next()} method will return a {@link Entry} with the elements of the + * {@code A} and {@code B} as values of the {@link Entry} until both iterators + * are exhausted. + *

+ *

+ * If one of the iterators is null, the result {@link Entry} contains also a + * null values until both child iterators exhausted. + *

+ *

+ * If one iterator has more elements then the other, the result {@link Entry} + * will contain a null value and the value of the not empty child iterator until + * both of the iterator exhausted. + * + * @param type of first value. The first value of {@link Entry} + * + * @param type of second value. The second value of {@link Entry} + * + * @since 4.4 + */ +public class PairingIterator implements Iterator> { + + private final Iterator firstIterator; + private final Iterator secondIterator; + + /** + * Constructs a new PairingIterator that will provide a + * {@link Entry} containing the child iterator elements. + * + * @param firstIterator the first iterator + * @param secondIterator the second iterator + */ + public PairingIterator(Iterator firstIterator, Iterator secondIterator) { + this.firstIterator = firstIterator; + this.secondIterator = secondIterator; + } + + /** + * Returns {@code true} if one of the child iterators has remaining elements. + */ + @Override + public boolean hasNext() { + return (null != firstIterator && firstIterator.hasNext()) + || (null != secondIterator && secondIterator.hasNext()); + } + + /** + * Returns the next {@link Entry} with the next values of the child iterators. + * + * @return a {@link Entry} with the next values of child iterators + * @throws NoSuchElementException if no child iterator has any more elements + */ + @Override + public Entry next() throws NoSuchElementException { + if (!hasNext()) { + throw new NoSuchElementException(); + } + final F firstValue = null != firstIterator && firstIterator.hasNext() ? firstIterator.next() : null; + final S secondValue = null != secondIterator && secondIterator.hasNext() ? secondIterator.next() : null; + return new Entry(firstValue, secondValue); + } + + /** + * Removes the last returned values of the child iterators. + */ + @Override + public void remove() { + if (firstIterator != null) { + firstIterator.remove(); + } + if (secondIterator != null) { + secondIterator.remove(); + } + } + + /** + * Contains two values of different generic types. + * + * @param the type of the first element + * @param the type of the second element + */ + public static class Entry { + private final F firstValue; + private final S secondValue; + + /** + * Constructs a new {@link Entry} of two generic values. + * + * @param firstValue the first value + * @param secondValue the second value + */ + Entry(F firstValue, S secondValue) { + this.firstValue = firstValue; + this.secondValue = secondValue; + } + + /** + * Returns the first value. + * + * @return the first value + */ + public F getFirstValue() { + return firstValue; + } + + /** + * Returns the second value. + * + * @return the second value + */ + public S getSecondValue() { + return secondValue; + } + + @Override + public int hashCode() { + return Objects.hash(firstValue, secondValue); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Entry other = (Entry) obj; + return Objects.equals(firstValue, other.firstValue) && Objects.equals(secondValue, other.secondValue); + } + + @Override + public String toString() { + return "Entry [firstValue=" + firstValue + ", secondValue=" + secondValue + "]"; + } + + } + +} diff --git a/src/test/java/org/apache/commons/collections4/iterators/PairingIteratorTest.java b/src/test/java/org/apache/commons/collections4/iterators/PairingIteratorTest.java new file mode 100644 index 0000000000..8c1d188909 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/iterators/PairingIteratorTest.java @@ -0,0 +1,271 @@ +/* + * 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 java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.iterators.PairingIterator.Entry; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit test suite for {@link PairingIterator}. + * + */ +public class PairingIteratorTest extends AbstractIteratorTest> { + + public PairingIteratorTest(String testName) { + super(testName); + } + + @Test + public void testNullValuesBoth() { + final PairingIterator pairingIterator = new PairingIterator<>(null, null); + + Assert.assertFalse(pairingIterator.hasNext()); + try { + pairingIterator.next(); + fail(); + + } catch (NoSuchElementException e) { + Assert.assertNotNull(e); + } + } + + @Test + public void testNullValueFirst() { + final List firstList = Arrays.asList(new String[] { "A" }); + final PairingIterator pairingIterator = new PairingIterator<>(null, firstList.iterator()); + + Assert.assertTrue(pairingIterator.hasNext()); + + Entry next = pairingIterator.next(); + Assert.assertEquals(null, next.getFirstValue()); + Assert.assertEquals("A", next.getSecondValue()); + } + + @Test + public void testNullValueSecond() { + final List firstList = Arrays.asList(new String[] { "A" }); + final PairingIterator pairingIterator = new PairingIterator<>(firstList.iterator(), null); + + Assert.assertTrue(pairingIterator.hasNext()); + + Entry next = pairingIterator.next(); + Assert.assertEquals("A", next.getFirstValue()); + Assert.assertEquals(null, next.getSecondValue()); + } + + @Test + public void testTwoIteratorsWithBothTwoElements() { + final List firstList = Arrays.asList(new String[] { "A1", "A2" }); + final List secondList = Arrays.asList(new String[] { "B1", "B2" }); + final PairingIterator pairingIterator = new PairingIterator<>(firstList.iterator(), + secondList.iterator()); + + Assert.assertTrue(pairingIterator.hasNext()); + + Entry next = pairingIterator.next(); + Assert.assertEquals("A1", next.getFirstValue()); + Assert.assertEquals("B1", next.getSecondValue()); + + Assert.assertTrue(pairingIterator.hasNext()); + next = pairingIterator.next(); + Assert.assertEquals("A2", next.getFirstValue()); + Assert.assertEquals("B2", next.getSecondValue()); + + Assert.assertFalse(pairingIterator.hasNext()); + } + + @Test + public void testTwoIteratorsWithDifferentSize() { + final List firstList = Arrays.asList(new String[] { "A1", "A2", "A3" }); + final List secondList = Arrays.asList(new String[] { "B1", "B2" }); + final PairingIterator pairingIterator = new PairingIterator<>(firstList.iterator(), + secondList.iterator()); + + Assert.assertTrue(pairingIterator.hasNext()); + + Entry next = pairingIterator.next(); + Assert.assertEquals("A1", next.getFirstValue()); + Assert.assertEquals("B1", next.getSecondValue()); + + Assert.assertTrue(pairingIterator.hasNext()); + + next = pairingIterator.next(); + Assert.assertEquals("A2", next.getFirstValue()); + Assert.assertEquals("B2", next.getSecondValue()); + + Assert.assertTrue(pairingIterator.hasNext()); + next = pairingIterator.next(); + Assert.assertEquals("A3", next.getFirstValue()); + Assert.assertEquals(null, next.getSecondValue()); + + Assert.assertFalse(pairingIterator.hasNext()); + } + + @Test + public void testTwoIteratorsWithDifferentSize2() { + final List firstList = Arrays.asList(new String[] { "A1", }); + final List secondList = Arrays.asList(new String[] { "B1", "B2" }); + final PairingIterator pairingIterator = new PairingIterator<>(firstList.iterator(), + secondList.iterator()); + + Assert.assertTrue(pairingIterator.hasNext()); + + Entry next = pairingIterator.next(); + Assert.assertEquals("A1", next.getFirstValue()); + Assert.assertEquals("B1", next.getSecondValue()); + + Assert.assertTrue(pairingIterator.hasNext()); + + next = pairingIterator.next(); + Assert.assertEquals(null, next.getFirstValue()); + Assert.assertEquals("B2", next.getSecondValue()); + + Assert.assertFalse(pairingIterator.hasNext()); + } + + @Test + public void testTwoIteratorsWithNullValues() { + final List firstList = Arrays.asList(new String[] { null, "A2" }); + final List secondList = Arrays.asList(new String[] { "B1", null }); + final PairingIterator pairingIterator = new PairingIterator<>(firstList.iterator(), + secondList.iterator()); + + Assert.assertTrue(pairingIterator.hasNext()); + + Entry next = pairingIterator.next(); + Assert.assertEquals(null, next.getFirstValue()); + Assert.assertEquals("B1", next.getSecondValue()); + + Assert.assertTrue(pairingIterator.hasNext()); + next = pairingIterator.next(); + Assert.assertEquals("A2", next.getFirstValue()); + Assert.assertEquals(null, next.getSecondValue()); + + Assert.assertFalse(pairingIterator.hasNext()); + } + + @Test + public void testRemoveWithOneNullIterator() { + final List firstList = new ArrayList<>(); + firstList.add("A1"); + final Iterator leftIterator = firstList.iterator(); + final PairingIterator pairingIterator = new PairingIterator<>(leftIterator, null); + + pairingIterator.next(); + pairingIterator.remove(); + Assert.assertTrue(firstList.isEmpty()); + } + + @Test + public void testRemoveWithOneNullIterator2() { + final List secondList = new ArrayList<>(); + secondList.add("A1"); + final Iterator rightIterator = secondList.iterator(); + final PairingIterator pairingIterator = new PairingIterator<>(null, rightIterator); + + pairingIterator.next(); + pairingIterator.remove(); + Assert.assertTrue(secondList.isEmpty()); + } + + @Test + public void testEntryEquals_notEquals() { + final String firstValue = "A"; + final String secondValue = "B"; + final Entry entry1 = new Entry<>(firstValue, secondValue); + final Entry entry2 = new Entry<>(secondValue, firstValue); + + Assert.assertNotEquals(entry1, entry2); + } + + @Test + public void testEntryEquals_equals() { + final String firstValue = "A"; + final String secondValue = "A"; + final Entry entry1 = new Entry<>(firstValue, secondValue); + final Entry entry2 = new Entry<>(secondValue, firstValue); + + Assert.assertEquals(entry1, entry2); + } + + @Test + public void testEntryEquals_equalsSameInstance() { + final String firstValue = "A"; + final String secondValue = "A"; + final Entry entry1 = new Entry<>(firstValue, secondValue); + + Assert.assertEquals(entry1, entry1); + } + + @Test + public void testEntryEquals_null() { + final String firstValue = "A"; + final String secondValue = "A"; + final Entry entry1 = new Entry<>(firstValue, secondValue); + + Assert.assertNotEquals(entry1, null); + } + + @Test + public void testEntryEquals_differentClasses() { + final String firstValue = "A"; + final String secondValue = "A"; + final Entry entry1 = new Entry<>(firstValue, secondValue); + + Assert.assertNotEquals(entry1, ""); + } + + @Test + public void testEntryToString() { + final String firstValue = "A"; + final String secondValue = "A"; + final Entry entry = new Entry<>(firstValue, secondValue); + + String string = entry.toString(); + Assert.assertTrue(string.contains(firstValue)); + Assert.assertTrue(string.contains(secondValue)); + + } + + @Override + public Iterator> makeEmptyIterator() { + return new PairingIterator(IteratorUtils.emptyIterator(), + IteratorUtils.emptyIterator()); + } + + @Override + public Iterator> makeObject() { + final List firstList = new ArrayList<>(); + firstList.add("A1"); + firstList.add("A2"); + firstList.add("A3"); + final List secondList = new ArrayList<>(); + secondList.add("B1"); + secondList.add("B2"); + return new PairingIterator<>(firstList.iterator(), secondList.iterator()); + } + +}