This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-collections.git
commit f2db58ec4083f35ca473476d0cd2fea5bd099ca9 Author: Claude Warren <cla...@apache.org> AuthorDate: Sun Oct 20 10:21:03 2024 +0100 Added ExtendedIterator and tests --- .../collections4/iterators/ExtendedIterator.java | 197 +++++++++++++++++++++ .../iterators/ExtendedIteratorTest.java | 150 ++++++++++++++++ 2 files changed, 347 insertions(+) diff --git a/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java b/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java new file mode 100644 index 000000000..b794958fb --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java @@ -0,0 +1,197 @@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + + +/** + * A ExtendedIterator is an Iterator wrapping around a plain + * (or presented as plain) Iterator. The wrapping allows the usual + * operations found on streams (filtering, concatenating, mapping) to be done on an Iterator derived + * from some other source. It also provides convenience methods for common operations. + * @param <T> The type of object returned from the iterator. + */ +public final class ExtendedIterator<T> implements Iterator<T> { + /** + * Set to <code>true</code> if this wrapping doesn't permit the use of + * {@link #remove()}, otherwise removal is delegated to the base iterator. + */ + private final boolean removeDenied; + + /** + * Creates an ExtendedIterator wrapped round <code>it</code>, + * which does not permit <code>.remove()</code> + * even if <code>it</code> does. + * @param it The Iterator to wrap. + * @return an Extended iterator on {@code it} + * @throws UnsupportedOperationException if remove() is called on the resulting iterator. + */ + public static <T> ExtendedIterator<T> createNoRemove(final Iterator<T> it) { + return new ExtendedIterator<>(it, true); + } + + /** + * Creates an ExtendedIterator wrapped round a {@link Stream}. + * The extended iterator does not permit <code>.remove()</code>. + * <p> + * The stream should not be used directly. The effect of doing so is + * undefined. + * </p> + * @param stream the Stream to create an iterator from. + * @return an Extended iterator on the {@code stream} iterator. + */ + public static <T> ExtendedIterator<T> create(final Stream<T> stream) { + return new ExtendedIterator<T>(stream.iterator(), true); + } + + /** + * Flattens an iterator of iterators into an Iterator over the next level values. + * Similar to list splicing in lisp. + * @param iterators An iterator of iterators. + * @return An iterator over the logical concatenation of the inner iterators. + */ + public static <T> ExtendedIterator<T> flatten(final Iterator<Iterator<T>> iterators) { + return create(new LazyIteratorChain<T>() { + + @Override + protected Iterator<? extends T> nextIterator(final int count) { + return iterators.hasNext() ? iterators.next() : null; + } + + }); + } + + /** + * Creates an empty Extended iterator. + * @return An empty Extended iterator. + */ + public static ExtendedIterator<?> emptyIterator() { + return new ExtendedIterator<>(Collections.emptyIterator(), false); + } + + /** + * Create an ExtendedIterator returning the elements of <code>it</code>. + * If <code>it</code> is itself an ExtendedIterator, return that; + * otherwise wrap <code>it</code>. + * @param it The iterator to wrap. + * @return An Extended iterator wrapping {@code it} + */ + public static <T> ExtendedIterator<T> create(final Iterator<T> it) { + return it instanceof ExtendedIterator<?> + ? (ExtendedIterator<T>) it + : new ExtendedIterator<>(it, false); + } + + /** the base iterator that we wrap */ + private final Iterator<? extends T> base; + + /** + * Initialise this wrapping with the given base iterator and remove-control. + * @param base the base iterator that this iterator wraps + * @param removeDenied true if .remove() must throw an exception + */ + private ExtendedIterator(final Iterator<? extends T> base, final boolean removeDenied) { + this.base = base; + this.removeDenied = removeDenied; + } + + @Override + public boolean hasNext() { + return base.hasNext(); + } + + @Override + public T next() { + return base.next(); + } + + @Override + public void forEachRemaining(final Consumer<? super T> action) { + base.forEachRemaining(action); + } + + @Override + public void remove() { + if (removeDenied) { + throw new UnsupportedOperationException(); + } + base.remove(); + } + + /** + * Returns the next item and removes it from the iterator. + * @return the next item from the iterator. + */ + public T removeNext() { + T result = next(); + remove(); + return result; + } + + /** + * Chains the {@code other} iterator to the end of this one. + * @param other the other iterator to extend this iterator with. + * @return A new iterator returning the contents of {@code this} iterator followed by the contents of {@code other} iterator. + * @param <X> The type of object returned from the other iterator. + */ + public <X extends T> ExtendedIterator<T> andThen(final Iterator<X> other) { + if (base instanceof IteratorChain) { + ((IteratorChain<T>) base).addIterator(other); + return this; + } + return new ExtendedIterator<T>(new IteratorChain<T>(this.base, other), this.removeDenied); + } + + /** + * Filter this iterator using a predicate. Only items for which the predicate returns {@code true} will + * be included in the result. + * @param predicate The predicate to filter the items with. + * @return An iterator filtered by the predicate. + */ + public ExtendedIterator<T> filter(final Predicate<T> predicate) { + return new ExtendedIterator<T>(new FilterIterator<>(this, predicate::test), this.removeDenied); + } + + /** + * Map the elements of the iterator to a now type. + * @param function The function to map elements of {@code <T>} to type {@code <U>}. + * @return An Extended iterator that returns a {@code <U>} for very {@code <T>} in the original iterator. + * @param <U> The object type to return. + */ + public <U> ExtendedIterator<U> map(final Function<T, U> function) { + return new ExtendedIterator<U>(new TransformIterator<>(this, function::apply), false); + } + + /** + * A method to add the remaining elements in the iterator an arbitrary collection. + * This method consumes the iterator. + * @param collection THe collection to add elements to. + * @return the {@code collection} with the elements added. + * @param <U> A collection of objects of type {@code <T>}. + */ + public <U extends Collection<T>> U addTo(final U collection) { + this.forEachRemaining(collection::add); + return collection; + } +} diff --git a/src/test/java/org/apache/commons/collections4/iterators/ExtendedIteratorTest.java b/src/test/java/org/apache/commons/collections4/iterators/ExtendedIteratorTest.java new file mode 100644 index 000000000..936290511 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/iterators/ExtendedIteratorTest.java @@ -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.collections4.iterators; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ExtendedIteratorTest { + /** + * Collection of {@link Integer}s + */ + private List<Integer> collectionA; + + @BeforeEach + public void setUp() { + collectionA = new ArrayList<>(); + collectionA.add(1); + collectionA.add(2); + collectionA.add(3); + collectionA.add(4); + collectionA.add(5); + collectionA.add(6); + } + + @Test + public void testCreateNoRemove() { + Iterator<Integer> iter = ExtendedIterator.createNoRemove(collectionA.iterator()); + assertThrows(UnsupportedOperationException.class, iter::remove); + } + + @Test + public void testCreateWithStream() { + Iterator<Integer> iter = ExtendedIterator.create(collectionA.stream()); + assertThrows(UnsupportedOperationException.class, iter::remove); + List<Integer> actual = new ArrayList<>(); + iter.forEachRemaining(actual::add); + assertEquals(collectionA, actual); + } + + @Test + public void testFlatten() { + Iterator<Iterator<Integer>> iteratorIterator = Arrays.asList( + Arrays.asList(1, 2, 3).iterator(), + Arrays.asList(4, 5, 6).iterator() + ).iterator(); + Iterator<Integer> iter = ExtendedIterator.flatten(iteratorIterator); + List<Integer> actual = new ArrayList<>(); + iter.forEachRemaining(actual::add); + assertEquals(collectionA, actual); + } + + @Test + public void testEmptyIterator() { + assertFalse(ExtendedIterator.emptyIterator().hasNext()); + } + + @Test + public void testCreate() { + Iterator<Integer> iter = ExtendedIterator.create(collectionA.iterator()); + List<Integer> actual = new ArrayList<>(); + iter.forEachRemaining(actual::add); + assertEquals(collectionA, actual); + } + + @Test + public void testRemove() { + Iterator<Integer> iter = ExtendedIterator.create(collectionA.iterator()); + Integer i = iter.next(); + iter.remove(); + assertFalse(collectionA.contains(i)); + List<Integer> actual = new ArrayList<>(); + iter.forEachRemaining(actual::add); + assertEquals(collectionA, actual); + } + + @Test + public void testRemoveNext() { + ExtendedIterator<Integer> iter = ExtendedIterator.create(collectionA.iterator()); + Integer i = iter.removeNext(); + assertFalse(collectionA.contains(i)); + List<Integer> actual = new ArrayList<>(); + iter.forEachRemaining(actual::add); + assertEquals(collectionA, actual); + } + + @Test + public void testAndThen() { + Iterator<Integer> iter1 = Arrays.asList(1, 2, 3).iterator(); + Iterator<Integer> iter2 = Arrays.asList(4, 5, 6).iterator(); + + ExtendedIterator<Integer> underTest = ExtendedIterator.create(iter1).andThen(iter2); + List<Integer> actual = new ArrayList<>(); + underTest.forEachRemaining(actual::add); + assertEquals(collectionA, actual); + } + + @Test + public void testFilter() { + List<Integer> expected = Arrays.asList(2, 4, 6); + Predicate<Integer> predicate = i -> i % 2 == 0; + ExtendedIterator<Integer> underTest = ExtendedIterator.create(collectionA.iterator()).filter(predicate); + List<Integer> actual = new ArrayList<>(); + underTest.forEachRemaining(actual::add); + assertEquals(expected, actual); + } + + @Test + public void testMap() { + List<Double> expected = Arrays.asList(0.5, 1., 1.5, 2.0, 2.5, 3.0); + Function<Integer, Double> function = i -> i / 2.0; + ExtendedIterator<Double> underTest = ExtendedIterator.create(collectionA.iterator()).map(function); + List<Double> actual = new ArrayList<>(); + underTest.forEachRemaining(actual::add); + assertEquals(expected, actual); + } + + @Test + public void testAddTo() { + List<Integer> expected = new ArrayList<>(collectionA); + expected.addAll(collectionA); + List<Integer> actual = ExtendedIterator.create(collectionA.iterator()).addTo(new ArrayList<>(collectionA)); + assertEquals(expected, actual); + } +}