From d591c85785eac742e3192d133a673c0a58baa9e7 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Fri, 1 Nov 2024 18:00:57 -0400 Subject: [PATCH] Refactor ExtendedIterator and FilterIterator with a new interface IteratorOperations - Inspired by Apache Jena's ExtendedIterator and Claude Warren's ExtendedIterator - Add missing Javadoc --- src/changes/changes.xml | 1 + .../commons/collections4/IteratorUtils.java | 28 +++ .../iterators/ExtendedIterator.java | 169 ++++++++---------- .../iterators/FilterIterator.java | 14 +- .../iterators/IteratorOperations.java | 88 +++++++++ .../iterators/FilterIteratorTest.java | 61 +++++++ 6 files changed, 254 insertions(+), 107 deletions(-) create mode 100644 src/main/java/org/apache/commons/collections4/iterators/IteratorOperations.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 009514e3b..ef7095fc2 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -61,6 +61,7 @@ Add missing test AbstractIteratorTest.testForEachRemaining(). Add FilterIterator.removeNext() #564. Added ExtendedIterator and tests #564. + Refactor ExtendedIterator and FilterIterator with a new interface IteratorOperations. Bump org.apache.commons:commons-parent from 71 to 78 #534, #545, #550 #555, #566. Update bloom filter documentation #508. diff --git a/src/main/java/org/apache/commons/collections4/IteratorUtils.java b/src/main/java/org/apache/commons/collections4/IteratorUtils.java index 1b6249204..c8b246b3d 100644 --- a/src/main/java/org/apache/commons/collections4/IteratorUtils.java +++ b/src/main/java/org/apache/commons/collections4/IteratorUtils.java @@ -28,7 +28,11 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.IntFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.apache.commons.collections4.functors.EqualPredicate; import org.apache.commons.collections4.iterators.ArrayIterator; @@ -1226,6 +1230,30 @@ public class IteratorUtils { return new SkippingIterator<>(iterator, offset); } + /** + * Creates a stream on the given Iterable. + * + * @param the type of elements in the Iterable. + * @param iterable the Iterable to stream or null. + * @return a new Stream or {@link Stream#empty()} if the Iterable is null. + * @since 4.5.0-M3 + */ + public static Stream stream(final Iterable iterable) { + return iterable == null ? Stream.empty() : StreamSupport.stream(iterable.spliterator(), false); + } + + /** + * Creates a stream on the given Iterator. + * + * @param the type of elements in the Iterator. + * @param iterator the Iterator to stream or null. + * @return a new Stream or {@link Stream#empty()} if the Iterator is null. + * @since 4.5.0-M3 + */ + public static Stream stream(final Iterator iterator) { + return iterator == null ? Stream.empty() : StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); + } + /** * Gets an array based on an iterator. *

diff --git a/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java b/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java index 7b6733543..662d35b89 100644 --- a/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java +++ b/src/main/java/org/apache/commons/collections4/iterators/ExtendedIterator.java @@ -16,7 +16,6 @@ */ package org.apache.commons.collections4.iterators; -import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.function.Consumer; @@ -24,24 +23,45 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; - /** - * Extends Iterator functionality to include operations commonly found on streams (e.g. filtering, concatenating, mapping). - * It also provides convenience methods for common operations. + * Extends Iterator functionality to include operations commonly found on streams (e.g. filtering, concatenating, mapping). It also provides convenience methods + * for common operations. + * * @param The type of object returned from the iterator. * @since 4.5.0-M3 */ -public final class ExtendedIterator implements Iterator { - /** - * Set to true if this wrapping doesn't permit the use of - * {@link #remove()}, otherwise removal is delegated to the base iterator. - */ - private final boolean throwOnRemove; +public final class ExtendedIterator implements IteratorOperations { /** - * Creates an ExtendedIterator wrapped round it, - * which does not permit .remove() - * even if it does. + * Create an ExtendedIterator returning the elements of it. If it is itself an ExtendedIterator, return that; otherwise wrap + * it. + * + * @param The type of object returned from the iterator. + * @param it The iterator to wrap. + * @return An Extended iterator wrapping {@code it} + */ + public static ExtendedIterator create(final Iterator it) { + return it instanceof ExtendedIterator ? (ExtendedIterator) it : new ExtendedIterator<>(it, false); + } + + /** + * Creates an ExtendedIterator wrapped round a {@link Stream}. The extended iterator does not permit .remove(). + *

+ * The stream should not be used directly. The effect of doing so is undefined. + *

+ * + * @param The type of object returned from the iterator. + * @param stream the Stream to create an iterator from. + * @return an Extended iterator on the {@code stream} iterator. + */ + public static ExtendedIterator create(final Stream stream) { + return new ExtendedIterator<>(stream.iterator(), true); + } + + /** + * Creates an ExtendedIterator wrapped round it, which does not permit .remove() even if it does. + * + * @param The type of object returned from the iterator. * @param it The Iterator to wrap. * @return an Extended iterator on {@code it} * @throws UnsupportedOperationException if remove() is called on the resulting iterator. @@ -51,22 +71,18 @@ public final class ExtendedIterator implements Iterator { } /** - * Creates an ExtendedIterator wrapped round a {@link Stream}. - * The extended iterator does not permit .remove(). - *

- * The stream should not be used directly. The effect of doing so is - * undefined. - *

- * @param stream the Stream to create an iterator from. - * @return an Extended iterator on the {@code stream} iterator. + * Creates an empty Extended iterator. + * + * @return An empty Extended iterator. */ - public static ExtendedIterator create(final Stream stream) { - return new ExtendedIterator(stream.iterator(), true); + public static ExtendedIterator emptyIterator() { + return new ExtendedIterator<>(Collections.emptyIterator(), false); } /** - * Flattens an iterator of iterators into an Iterator over the next level values. - * Similar to list splicing in lisp. + * Flattens an iterator of iterators into an Iterator over the next level values. Similar to list splicing in lisp. + * + * @param The type of object returned from the iterator. * @param iterators An iterator of iterators. * @return An iterator over the logical concatenation of the inner iterators. */ @@ -82,32 +98,17 @@ public final class ExtendedIterator implements Iterator { } /** - * Creates an empty Extended iterator. - * @return An empty Extended iterator. + * Set to true if this wrapping doesn't permit the use of {@link #remove()}, otherwise removal is delegated to the base iterator. */ - public static ExtendedIterator emptyIterator() { - return new ExtendedIterator<>(Collections.emptyIterator(), false); - } + private final boolean throwOnRemove; - /** - * Create an ExtendedIterator returning the elements of it. - * If it is itself an ExtendedIterator, return that; - * otherwise wrap it. - * @param it The iterator to wrap. - * @return An Extended iterator wrapping {@code it} - */ - public static ExtendedIterator create(final Iterator it) { - return it instanceof ExtendedIterator - ? (ExtendedIterator) it - : new ExtendedIterator<>(it, false); - } - - /** the base iterator that we wrap */ + /** The base iterator that we wrap */ private final Iterator base; /** - * Initialise this wrapping with the given base iterator and remove-control. - * @param base the base iterator that this iterator wraps + * Initialize this wrapping with the given base iterator and remove-control. + * + * @param base the base iterator that this iterator wraps * @param throwOnRemove true if .remove() must throw an exception */ private ExtendedIterator(final Iterator base, final boolean throwOnRemove) { @@ -115,41 +116,9 @@ public final class ExtendedIterator implements Iterator { this.throwOnRemove = throwOnRemove; } - @Override - public boolean hasNext() { - return base.hasNext(); - } - - @Override - public T next() { - return base.next(); - } - - @Override - public void forEachRemaining(final Consumer action) { - base.forEachRemaining(action); - } - - @Override - public void remove() { - if (throwOnRemove) { - 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 The type of object returned from the other iterator. @@ -159,38 +128,50 @@ public final class ExtendedIterator implements Iterator { ((IteratorChain) base).addIterator(other); return this; } - return new ExtendedIterator(new IteratorChain(this.base, other), this.throwOnRemove); + return new ExtendedIterator<>(new IteratorChain<>(this.base, other), this.throwOnRemove); } /** - * Filter this iterator using a predicate. Only items for which the predicate returns {@code true} will - * be included in the result. + * 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 filter(final Predicate predicate) { - return new ExtendedIterator(new FilterIterator<>(this, predicate::test), this.throwOnRemove); + return new ExtendedIterator<>(new FilterIterator<>(this, predicate::test), this.throwOnRemove); + } + + @Override + public void forEachRemaining(final Consumer action) { + base.forEachRemaining(action); + } + + @Override + public boolean hasNext() { + return base.hasNext(); } /** * Map the elements of the iterator to a now type. + * * @param function The function to map elements of {@code } to type {@code }. * @return An Extended iterator that returns a {@code } for very {@code } in the original iterator. * @param The object type to return. */ public ExtendedIterator map(final Function function) { - return new ExtendedIterator(new TransformIterator<>(this, function::apply), false); + return new ExtendedIterator<>(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 A collection of objects of type {@code }. - */ - public > U addTo(final U collection) { - this.forEachRemaining(collection::add); - return collection; + @Override + public T next() { + return base.next(); + } + + @Override + public void remove() { + if (throwOnRemove) { + throw new UnsupportedOperationException(); + } + base.remove(); } } diff --git a/src/main/java/org/apache/commons/collections4/iterators/FilterIterator.java b/src/main/java/org/apache/commons/collections4/iterators/FilterIterator.java index e9872a2b1..9fc280cd2 100644 --- a/src/main/java/org/apache/commons/collections4/iterators/FilterIterator.java +++ b/src/main/java/org/apache/commons/collections4/iterators/FilterIterator.java @@ -32,7 +32,7 @@ import org.apache.commons.collections4.functors.TruePredicate; * @param the type of elements returned by this iterator. * @since 1.0 */ -public class FilterIterator implements Iterator { +public class FilterIterator implements IteratorOperations { /** The iterator to be filtered. */ private Iterator iterator; @@ -141,18 +141,6 @@ public class FilterIterator implements Iterator { iterator.remove(); } - /** - * Returns the next item and removes it from the iterator. - * - * @return the next item from the iterator. - * @since 4.5.0-M3 - */ - public E removeNext() { - final E result = next(); - remove(); - return result; - } - private Predicate safePredicate(final Predicate predicate) { return predicate != null ? predicate : TruePredicate.truePredicate(); } diff --git a/src/main/java/org/apache/commons/collections4/iterators/IteratorOperations.java b/src/main/java/org/apache/commons/collections4/iterators/IteratorOperations.java new file mode 100644 index 000000000..54056e2cb --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/iterators/IteratorOperations.java @@ -0,0 +1,88 @@ +/* + * 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.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +/** + * Extends {@link Iterator} with additional default methods. + * + * @param the type of elements returned by this iterator. + * @since 4.5.0-M3 + */ +public interface IteratorOperations extends Iterator { + + /** + * Adds the remaining elements in the iterator to an arbitrary {@link Collection}. This method consumes the iterator. + * + * @param collection The target collection to add elements to. + * @return the given {@code collection}. + * @param A collection of objects of type {@code }. + */ + default > C addTo(final C collection) { + forEachRemaining(collection::add); + return collection; + } + + /** + * Returns the next item and removes it from the iterator. + * + * @return the next item from the iterator. + */ + default E removeNext() { + final E result = next(); + remove(); + return result; + } + + /** + * Adds the remaining elements in the iterator to a new {@link Collection} provided by the supplier. This method consumes the iterator. + * + * @param collectionSupplier supplies a collection target. + * @param the collection type. + * @return a new Collection containing the remaining elements of this instance. + */ + default > C toCollection(final Supplier collectionSupplier) { + return addTo(collectionSupplier.get()); + } + + /** + * Adds the remaining elements in the iterator to a new {@link List}. This method consumes the iterator. + * + * @return a new List containing the remaining elements of this instance. + */ + default List toList() { + return toCollection(ArrayList::new); + } + + /** + * Adds the remaining elements in the iterator to a new {@link Set}. This method consumes the iterator. + * + * @return a new Set containing the remaining elements of this instance. + */ + default Set toSet() { + return toCollection(HashSet::new); + } + +} diff --git a/src/test/java/org/apache/commons/collections4/iterators/FilterIteratorTest.java b/src/test/java/org/apache/commons/collections4/iterators/FilterIteratorTest.java index 582e79a4d..ed28a538e 100644 --- a/src/test/java/org/apache/commons/collections4/iterators/FilterIteratorTest.java +++ b/src/test/java/org/apache/commons/collections4/iterators/FilterIteratorTest.java @@ -16,17 +16,22 @@ */ package org.apache.commons.collections4.iterators; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; 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 static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.Predicate; @@ -125,6 +130,38 @@ public class FilterIteratorTest extends AbstractIteratorTest { iterator = null; } + @Test + public void testAddTo() { + final List expected = new ArrayList<>(list); + expected.addAll(list); + final FilterIterator filterIterator = new FilterIterator<>(list.iterator()); + final List actual = filterIterator.addTo(new ArrayList<>(list)); + assertEquals(expected, actual); + } + + @Test + public void testAddToCollection() { + final List expected = new ArrayList<>(list); + expected.addAll(list); + final FilterIterator filterIterator = new FilterIterator<>(list.iterator()); + final List actual = filterIterator.toCollection(() -> new ArrayList<>(list)); + assertEquals(expected, actual); + } + + @Test + public void testAddToEmpty() { + final FilterIterator filterIterator = makeEmptyIterator(); + final List actual = filterIterator.addTo(new ArrayList<>(list)); + assertEquals(list, actual); + } + + @Test + public void testAddToEmptyToEmpty() { + final FilterIterator filterIterator = makeEmptyIterator(); + final List actual = filterIterator.addTo(new ArrayList<>()); + assertTrue(actual.isEmpty()); + } + /** * Tests a predicate that accepts some but not all elements. */ @@ -254,6 +291,30 @@ public class FilterIteratorTest extends AbstractIteratorTest { assertFalse(filterIterator.hasNext()); } + @Test + public void testToCollectionAsDeque() { + final Deque expected = new ArrayDeque<>(list); + final FilterIterator filterIterator = new FilterIterator<>(list.iterator()); + final Deque actual = filterIterator.toCollection(ArrayDeque::new); + assertArrayEquals(expected.toArray(), actual.toArray()); + } + + @Test + public void testToList() { + final List expected = new ArrayList<>(list); + final FilterIterator filterIterator = new FilterIterator<>(list.iterator()); + final List actual = filterIterator.toList(); + assertEquals(expected, actual); + } + + @Test + public void testToSet() { + final Set expected = new HashSet<>(list); + final FilterIterator filterIterator = new FilterIterator<>(list.iterator()); + final Set actual = filterIterator.toSet(); + assertEquals(expected, actual); + } + private void verifyElementsInPredicate(final String[] elements) { final Predicate pred = x -> { for (final String element : elements) {