Refactor ExtendedIterator and FilterIterator with a new interface
IteratorOperations - Inspired by Apache Jena's ExtendedIterator and Claude Warren's ExtendedIterator - Add missing Javadoc
This commit is contained in:
parent
c4a0d4c27e
commit
d591c85785
|
@ -61,6 +61,7 @@
|
|||
<action type="fix" dev="ggregory" due-to="Gary Gregory">Add missing test AbstractIteratorTest.testForEachRemaining().</action>
|
||||
<action type="fix" dev="ggregory" due-to="Gary Gregory, Claude Warren">Add FilterIterator.removeNext() #564.</action>
|
||||
<action type="fix" issue="COLLECTIONS-870" dev="ggregory" due-to="Claude Warren, Gary Gregory">Added ExtendedIterator and tests #564.</action>
|
||||
<action type="add" dev="ggregory" due-to="Gary Gregory">Refactor ExtendedIterator and FilterIterator with a new interface IteratorOperations.</action>
|
||||
<!-- UPDATE -->
|
||||
<action type="update" dev="ggregory" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 71 to 78 #534, #545, #550 #555, #566.</action>
|
||||
<action type="update" issue="COLLECTIONS-857" dev="ggregory" due-to="Claude Warren">Update bloom filter documentation #508.</action>
|
||||
|
|
|
@ -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 <E> 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 <E> Stream<E> stream(final Iterable<E> iterable) {
|
||||
return iterable == null ? Stream.empty() : StreamSupport.stream(iterable.spliterator(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stream on the given Iterator.
|
||||
*
|
||||
* @param <E> 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 <E> Stream<E> stream(final Iterator<E> iterator) {
|
||||
return iterator == null ? Stream.empty() : StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array based on an iterator.
|
||||
* <p>
|
||||
|
|
|
@ -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 <T> The type of object returned from the iterator.
|
||||
* @since 4.5.0-M3
|
||||
*/
|
||||
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 throwOnRemove;
|
||||
public final class ExtendedIterator<T> implements IteratorOperations<T> {
|
||||
|
||||
/**
|
||||
* Creates an ExtendedIterator wrapped round <code>it</code>,
|
||||
* which does not permit <code>.remove()</code>
|
||||
* even if <code>it</code> does.
|
||||
* 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 <T> The type of object returned from the iterator.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <T> 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 <T> ExtendedIterator<T> create(final Stream<T> stream) {
|
||||
return new ExtendedIterator<>(stream.iterator(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ExtendedIterator wrapped round <code>it</code>, which does not permit <code>.remove()</code> even if <code>it</code> does.
|
||||
*
|
||||
* @param <T> 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<T> implements Iterator<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Creates an empty Extended iterator.
|
||||
*
|
||||
* @return An empty Extended iterator.
|
||||
*/
|
||||
public static <T> ExtendedIterator<T> create(final Stream<T> stream) {
|
||||
return new ExtendedIterator<T>(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 <T> 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<T> implements Iterator<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an empty Extended iterator.
|
||||
* @return An empty Extended iterator.
|
||||
* Set to <code>true</code> 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 <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 */
|
||||
/** 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
|
||||
* 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<? extends T> base, final boolean throwOnRemove) {
|
||||
|
@ -115,41 +116,9 @@ public final class ExtendedIterator<T> implements Iterator<T> {
|
|||
this.throwOnRemove = throwOnRemove;
|
||||
}
|
||||
|
||||
@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 (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 <X> The type of object returned from the other iterator.
|
||||
|
@ -159,38 +128,50 @@ public final class ExtendedIterator<T> implements Iterator<T> {
|
|||
((IteratorChain<T>) base).addIterator(other);
|
||||
return this;
|
||||
}
|
||||
return new ExtendedIterator<T>(new IteratorChain<T>(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<T> filter(final Predicate<T> predicate) {
|
||||
return new ExtendedIterator<T>(new FilterIterator<>(this, predicate::test), this.throwOnRemove);
|
||||
return new ExtendedIterator<>(new FilterIterator<>(this, predicate::test), this.throwOnRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(final Consumer<? super T> 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 <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);
|
||||
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 <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;
|
||||
@Override
|
||||
public T next() {
|
||||
return base.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (throwOnRemove) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
base.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.apache.commons.collections4.functors.TruePredicate;
|
|||
* @param <E> the type of elements returned by this iterator.
|
||||
* @since 1.0
|
||||
*/
|
||||
public class FilterIterator<E> implements Iterator<E> {
|
||||
public class FilterIterator<E> implements IteratorOperations<E> {
|
||||
|
||||
/** The iterator to be filtered. */
|
||||
private Iterator<? extends E> iterator;
|
||||
|
@ -141,18 +141,6 @@ public class FilterIterator<E> implements Iterator<E> {
|
|||
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<? super E> safePredicate(final Predicate<? super E> predicate) {
|
||||
return predicate != null ? predicate : TruePredicate.truePredicate();
|
||||
}
|
||||
|
|
|
@ -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 <E> the type of elements returned by this iterator.
|
||||
* @since 4.5.0-M3
|
||||
*/
|
||||
public interface IteratorOperations<E> extends Iterator<E> {
|
||||
|
||||
/**
|
||||
* 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 <C> A collection of objects of type {@code <E>}.
|
||||
*/
|
||||
default <C extends Collection<E>> 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 <C> the collection type.
|
||||
* @return a new Collection containing the remaining elements of this instance.
|
||||
*/
|
||||
default <C extends Collection<E>> C toCollection(final Supplier<C> 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<E> 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<E> toSet() {
|
||||
return toCollection(HashSet::new);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<E> extends AbstractIteratorTest<E> {
|
|||
iterator = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddTo() {
|
||||
final List<E> expected = new ArrayList<>(list);
|
||||
expected.addAll(list);
|
||||
final FilterIterator<E> filterIterator = new FilterIterator<>(list.iterator());
|
||||
final List<E> actual = filterIterator.addTo(new ArrayList<>(list));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddToCollection() {
|
||||
final List<E> expected = new ArrayList<>(list);
|
||||
expected.addAll(list);
|
||||
final FilterIterator<E> filterIterator = new FilterIterator<>(list.iterator());
|
||||
final List<E> actual = filterIterator.toCollection(() -> new ArrayList<>(list));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddToEmpty() {
|
||||
final FilterIterator<E> filterIterator = makeEmptyIterator();
|
||||
final List<E> actual = filterIterator.addTo(new ArrayList<>(list));
|
||||
assertEquals(list, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddToEmptyToEmpty() {
|
||||
final FilterIterator<E> filterIterator = makeEmptyIterator();
|
||||
final List<E> 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<E> extends AbstractIteratorTest<E> {
|
|||
assertFalse(filterIterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToCollectionAsDeque() {
|
||||
final Deque<E> expected = new ArrayDeque<>(list);
|
||||
final FilterIterator<E> filterIterator = new FilterIterator<>(list.iterator());
|
||||
final Deque<E> actual = filterIterator.toCollection(ArrayDeque::new);
|
||||
assertArrayEquals(expected.toArray(), actual.toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToList() {
|
||||
final List<E> expected = new ArrayList<>(list);
|
||||
final FilterIterator<E> filterIterator = new FilterIterator<>(list.iterator());
|
||||
final List<E> actual = filterIterator.toList();
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSet() {
|
||||
final Set<E> expected = new HashSet<>(list);
|
||||
final FilterIterator<E> filterIterator = new FilterIterator<>(list.iterator());
|
||||
final Set<E> actual = filterIterator.toSet();
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
private void verifyElementsInPredicate(final String[] elements) {
|
||||
final Predicate<E> pred = x -> {
|
||||
for (final String element : elements) {
|
||||
|
|
Loading…
Reference in New Issue