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);
+    }
+}

Reply via email to