[COLLECTIONS-464] Add first version of a FluentIterable implementation, cleanup recently toString methods in IterableUtils, add SkippingIterator and additional methods to IteratorUtils and IterableUtils.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1681783 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2015-05-26 15:54:09 +00:00
parent 1918bb005e
commit 27a92653a0
7 changed files with 1002 additions and 112 deletions

View File

@ -40,8 +40,8 @@
Added clarification to javadoc of "TreeBag#add(Object)" wrt null arguments.
</action>
<action issue="COLLECTIONS-427" dev="tn" type="add" due-to="Gonçalo Marques">
Added "toString(...)" methods to newly created "IteratorUtils" class to get a
string representation of an Iterable instance similar to "Arrays#toString(...)".
Added "toString(...)" methods to newly created "IterableUtils" and existing "IteratorUtils"
to get a string representation of an Iterable/Iterator instance similar to "Arrays#toString(...)".
</action>
<action issue="COLLECTIONS-427" dev="tn" type="fix">
Reverted performance improvement for "SetUniqueList#retainAll(Collection)"

View File

@ -1384,18 +1384,11 @@ public class CollectionUtils {
* @return the object at the specified index
* @throws IndexOutOfBoundsException if the index is invalid
* @throws IllegalArgumentException if the object type is invalid
* @deprecated since 4.1, use {@code IteratorUtils.get(Iterator, int)} instead
*/
@Deprecated
public static <T> T get(final Iterator<T> iterator, final int index) {
int i = index;
checkIndexBounds(i);
while (iterator.hasNext()) {
i--;
if (i == -1) {
return iterator.next();
}
iterator.next();
}
throw new IndexOutOfBoundsException("Entry does not exist: " + i);
return IteratorUtils.get(iterator, index);
}
/**
@ -1432,7 +1425,7 @@ public class CollectionUtils {
* @param index the index to check.
* @throws IndexOutOfBoundsException if the index is negative.
*/
private static void checkIndexBounds(final int index) {
static void checkIndexBounds(final int index) {
if (index < 0) {
throw new IndexOutOfBoundsException("Index cannot be negative: " + index);
}
@ -1449,13 +1442,11 @@ public class CollectionUtils {
* @param <T> the type of object in the {@link Iterable}.
* @return the object at the specified index
* @throws IndexOutOfBoundsException if the index is invalid
* @deprecated since 4.1, use {@code IterableUtils.get(Iterable, int)} instead
*/
@Deprecated
public static <T> T get(final Iterable<T> iterable, final int index) {
checkIndexBounds(index);
if (iterable instanceof List<?>) {
return ((List<T>) iterable).get(index);
}
return get(iterable.iterator(), index);
return IterableUtils.get(iterable, index);
}
/**

View File

@ -0,0 +1,181 @@
/*
* 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;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
/**
* A FluentIterable provides a powerful yet simple API for manipulating Iterable instances in a fluent manner.
* <p>
* A FluentIterable can be created either from an Iterable or from a set of elements.
* The following types of methods are provided:
* <ul>
* <li>fluent methods which return a new {@code FluentIterable} instance
* <li>conversion methods which copy the FluentIterable's contents into a new collection or array (e.g. toList())
* <li>utility methods which answer questions about the FluentIterable's contents (e.g. size(), anyMatch(Predicate))
* <li>
* </ul>
* <p>
* The following example outputs the first 3 even numbers in the range [1, 10] into a list:
* <pre>
* FluentIterable
* .of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
* .filter(new Predicate<Integer>() {
* public boolean evaluate(Integer number) {
* return number % 2 == 0;
* }
* )
* .transform(TransformerUtils.stringValueTransformer())
* .limit(3)
* .toList();
* </pre>
*
* @param <E> the element type
* @since 4.1
* @version $Id: $
*/
public class FluentIterable<E> implements Iterable<E> {
private final Iterable<E> iterable;
// Static factory methods
// ----------------------------------------------------------------------
public static <T> FluentIterable<T> of(T... elements) {
return of(Arrays.asList(elements));
}
public static <T> FluentIterable<T> of(Iterable<T> iterable) {
if (iterable == null) {
throw new NullPointerException("Iterable must not be null");
}
if (iterable instanceof FluentIterable<?>) {
return (FluentIterable<T>) iterable;
} else {
return new FluentIterable<T>(iterable);
}
}
// Constructor
// ----------------------------------------------------------------------
private FluentIterable(final Iterable<E> iterable) {
this.iterable = iterable;
}
// fluent construction methods
// ----------------------------------------------------------------------
public FluentIterable<E> append(final E... elements) {
return append(Arrays.asList(elements));
}
public FluentIterable<E> append(final Iterable<E> other) {
return of(IterableUtils.chainedIterable(iterable, other));
}
public FluentIterable<E> eval() {
return of(toList());
}
public FluentIterable<E> filter(final Predicate<E> predicate) {
return of(IterableUtils.filteredIterable(iterable, predicate));
}
public FluentIterable<E> limit(final int maxSize) {
return of(IterableUtils.boundedIterable(iterable, maxSize));
}
public FluentIterable<E> loop() {
return of(IterableUtils.loopingIterable(iterable));
}
public FluentIterable<E> skip(int elementsToSkip) {
return of(IterableUtils.skippingIterable(iterable, elementsToSkip));
}
public <O> FluentIterable<O> transform(final Transformer<? super E, ? extends O> transformer) {
return of(IterableUtils.transformedIterable(iterable, transformer));
}
public FluentIterable<E> unique() {
return of(IterableUtils.uniqueIterable(iterable));
}
// convenience methods
// ----------------------------------------------------------------------
public Iterator<E> iterator() {
return iterable.iterator();
}
public Enumeration<E> asEnumeration() {
return IteratorUtils.asEnumeration(iterator());
}
public boolean allMatch(final Predicate<? super E> predicate) {
return IterableUtils.matchesAll(iterable, predicate);
}
public boolean anyMatch(final Predicate<? super E> predicate) {
return IterableUtils.matchesAny(iterable, predicate);
}
public boolean isEmpty() {
return IterableUtils.isEmpty(iterable);
}
public boolean contains(final Object object) {
return IterableUtils.contains(iterable, object);
}
public E get(int position) {
return IterableUtils.get(iterable, position);
}
public int size() {
return IterableUtils.size(iterable);
}
public void copyInto(final Collection<? super E> collection) {
if (collection == null) {
throw new NullPointerException("Collection must not be null");
}
for (final E element : iterable) {
collection.add(element);
}
}
public E[] toArray(final Class<E> arrayClass) {
return IteratorUtils.toArray(iterator(), arrayClass);
}
public List<E> toList() {
return IteratorUtils.toList(iterator());
}
@Override
public String toString() {
return IterableUtils.toString(iterable);
}
}

View File

@ -16,6 +16,13 @@
*/
package org.apache.commons.collections4;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections4.iterators.LazyIteratorChain;
import org.apache.commons.collections4.iterators.UniqueFilterIterator;
/**
* Provides utility methods and decorators for {@link Iterable} instances.
*
@ -24,105 +31,472 @@ package org.apache.commons.collections4;
*/
public class IterableUtils {
/**
* Default prefix used while converting an Iterable to its String representation.
*/
private static final String DEFAULT_TOSTRING_PREFIX = "[";
// Chained
// ----------------------------------------------------------------------
/**
* Default suffix used while converting an Iterable to its String representation.
* Combines two iterables into a single iterable.
* <p>
* The returned iterable has an iterator that traverses the elements in {@code a},
* followed by the elements in {@code b}. The source iterators are not polled until
* necessary.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param a the first iterable
* @param b the second iterable
* @return a new iterable, combining the provided iterables
*/
private static final String DEFAULT_TOSTRING_SUFFIX = "]";
@SuppressWarnings("unchecked")
public static <E> Iterable<E> chainedIterable(final Iterable<? extends E> a, final Iterable<? extends E> b) {
return chainedIterable(new Iterable[] {a, b});
}
/**
* Default delimiter used to delimit elements while converting an Iterable
* to its String representation.
* Combines three iterables into a single iterable.
* <p>
* The returned iterable has an iterator that traverses the elements in {@code a},
* followed by the elements in {@code b} and {@code c}. The source iterators are
* not polled until necessary.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param a the first iterable
* @param b the second iterable
* @param c the third iterable
* @return a new iterable, combining the provided iterables
*/
private static final String DEFAULT_TOSTRING_DELIMITER = ", ";
@SuppressWarnings("unchecked")
public static <E> Iterable<E> chainedIterable(final Iterable<? extends E> a,
final Iterable<? extends E> b,
final Iterable<? extends E> c) {
return chainedIterable(new Iterable[] {a, b, c});
}
/**
* Combines four iterables into a single iterable.
* <p>
* The returned iterable has an iterator that traverses the elements in {@code a},
* followed by the elements in {@code b}, {@code c} and {@code d}. The source
* iterators are not polled until necessary.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param a the first iterable
* @param b the second iterable
* @param c the third iterable
* @param d the fourth iterable
* @return a new iterable, combining the provided iterables
*/
@SuppressWarnings("unchecked")
public static <E> Iterable<E> chainedIterable(final Iterable<? extends E> a,
final Iterable<? extends E> b,
final Iterable<? extends E> c,
final Iterable<? extends E> d) {
return chainedIterable(new Iterable[] {a, b, c, d});
}
/**
* Combines the provided iterables into a single iterable.
* <p>
* The returned iterable has an iterator that traverses the elements in the order
* of the arguments, i.e. iterables[0], iterables[1], .... The source iterators
* are not polled until necessary.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param iterables the iterables to combine
* @return a new iterable, combining the provided iterables
*/
public static <E> Iterable<E> chainedIterable(final Iterable<? extends E>... iterables) {
return new AbstractIterable<E>() {
@Override
public Iterator<E> iterator() {
return new LazyIteratorChain<E>() {
@Override
protected Iterator<? extends E> nextIterator(int count) {
if (count > iterables.length) {
return null;
} else {
return emptyIteratorIfNull(iterables[count - 1]);
}
}
};
}
};
}
// Filtered
// ----------------------------------------------------------------------
/**
* Returns a view of the given iterable that only contains elements matching
* the provided predicate.
* <p>
* The returned iterable's iterator does not supports {@code remove()}.
*
* @param <E> the element type
* @param iterable the iterable to filter, may be null
* @param predicate the predicate used to filter elements, must not be null
* @return a filtered view on the specified iterable
* @throws NullPointerException if predicate is null
*/
public static <E> Iterable<E> filteredIterable(final Iterable<E> iterable, final Predicate<? super E> predicate) {
if (predicate == null) {
throw new NullPointerException("predicate must not be null.");
}
return new AbstractIterable<E>() {
@Override
public Iterator<E> iterator() {
return IteratorUtils.filteredIterator(emptyIteratorIfNull(iterable), predicate);
}
};
}
// Bounded
// ----------------------------------------------------------------------
/**
* Returns a view of the given iterable that contains at most the given number
* of elements.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param iterable the iterable to limit, may be null
* @param maxSize the maximum number of elements, must not be negative
* @return a bounded view on the specified iterable
* @throws IllegalArgumentException if maxSize is negative
*/
public static <E> Iterable<E> boundedIterable(final Iterable<E> iterable, final int maxSize) {
if (maxSize < 0) {
throw new IllegalArgumentException("maxSize parameter must not be negative.");
}
return new AbstractIterable<E>() {
@Override
public Iterator<E> iterator() {
return IteratorUtils.boundedIterator(emptyIteratorIfNull(iterable), maxSize);
}
};
}
// Looping
// ----------------------------------------------------------------------
/**
* Returns a view of the given iterable which will cycle infinitely over
* its elements.
* <p>
* The returned iterable's iterator supports {@code remove()} if
* {@code iterable.iterator()} does. After {@code remove()} is called, subsequent
* cycles omit the removed element, which is no longer in {@code iterable}. The
* iterator's {@code hasNext()} method returns {@code true} until {@code iterable}
* is empty.
*
* @param <E> the element type
* @param iterable the iterable to loop, may be null
* @return a view of the iterable, providing an infinite loop over its elements
*/
public static <E> Iterable<E> loopingIterable(final Iterable<E> iterable) {
return new AbstractIterable<E>() {
@Override
public Iterator<E> iterator() {
return new LazyIteratorChain<E>() {
@Override
protected Iterator<? extends E> nextIterator(int count) {
if (iterable != null) {
if (isEmpty(iterable)) {
return null;
} else {
return iterable.iterator();
}
} else {
return null;
}
}
};
}
};
}
// Skipping
// ----------------------------------------------------------------------
/**
* Returns a view of the given iterable that skips the first N elements.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param iterable the iterable to use, may be null
* @param elementsToSkip the number of elements to skip from the start, must not be negative
* @return a view of the specified iterable, skipping the first N elements
* @throws IllegalArgumentException if elementsToSkip is negative
*/
public static <E> Iterable<E> skippingIterable(final Iterable<E> iterable, final int elementsToSkip) {
if (elementsToSkip < 0) {
throw new IllegalArgumentException("elementsToSkip parameter must not be negative.");
}
return new AbstractIterable<E>() {
@Override
public Iterator<E> iterator() {
return IteratorUtils.skippingIterator(emptyIteratorIfNull(iterable), elementsToSkip);
}
};
}
// Transformed
// ----------------------------------------------------------------------
/**
* Returns a transformed view of the given iterable where all of its elements
* have been transformed by the provided transformer.
* <p>
* The returned iterable's iterator supports {@code remove()} when the corresponding
* input iterator supports it.
*
* @param <E> the element type
* @param iterable the iterable to transform, may be null
* @param transformer the transformer , must not be null
* @return a transformed view of the specified iterable
* @throws NullPointerException if transformer is null
*/
public static <I, O> Iterable<O> transformedIterable(final Iterable<I> iterable, final Transformer<? super I, ? extends O> transformer) {
if (transformer == null) {
throw new NullPointerException("transformer must not be null.");
}
return new AbstractIterable<O>() {
@Override
public Iterator<O> iterator() {
return IteratorUtils.transformedIterator(emptyIteratorIfNull(iterable), transformer);
}
};
}
// Unique
// ----------------------------------------------------------------------
/**
* Returns a unique view of the given iterable.
* <p>
* The returned iterable's iterator does not supports {@code remove()}.
*
* @param <E> the element type
* @param iterable the iterable to transform, may be null
* @return a unique view of the specified iterable
*/
public static <E> Iterable<E> uniqueIterable(final Iterable<E> iterable) {
return new AbstractIterable<E>() {
@Override
public Iterator<E> iterator() {
return new UniqueFilterIterator<E>(emptyIteratorIfNull(iterable));
}
};
}
// Utility methods
// ----------------------------------------------------------------------
/**
* Returns an empty iterator if the argument is <code>null</code>,
* or returns {@code iterable.iterator()} otherwise.
*
* @param <E> the element type
* @param iterable the iterable, possibly <code>null</code>
* @return an empty collection if the argument is <code>null</code>
*/
public static <E> Iterator<E> emptyIteratorIfNull(final Iterable<E> iterable) {
return iterable != null ? iterable.iterator() : IteratorUtils.<E>emptyIterator();
}
/**
* Answers true if a predicate is true for every element of an iterable.
* <p>
* A <code>null</code> or empty iterable returns true.
*
* @param <E> the type of object the {@link Iterable} contains
* @param input the {@link Iterable} to use, may be null
* @param predicate the predicate to use, may not be null
* @return true if every element of the collection matches the predicate or if the
* collection is empty, false otherwise
* @throws NullPointerException if predicate is null
*/
public static <E> boolean matchesAll(final Iterable<E> iterable, final Predicate<? super E> predicate) {
return IteratorUtils.matchesAll(emptyIteratorIfNull(iterable), predicate);
}
/**
* Answers true if a predicate is true for any element of the iterable.
* <p>
* A <code>null</code> or empty iterable returns false.
*
* @param <E> the type of object the {@link Iterable} contains
* @param input the {@link Iterable} to use, may be null
* @param predicate the predicate to use, may not be null
* @return true if any element of the collection matches the predicate, false otherwise
* @throws NullPointerException if predicate is null
*/
public static <E> boolean matchesAny(final Iterable<E> iterable, final Predicate<? super E> predicate) {
return IteratorUtils.matchesAny(emptyIteratorIfNull(iterable), predicate);
}
/**
* Answers true if the provided iterable is empty.
* <p>
* A <code>null</code> iterable returns true.
*
* @param iterable the {@link Iterable to use}, may be null
* @return true if the iterable is null or empty, false otherwise
*/
public static boolean isEmpty(final Iterable<?> iterable) {
if (iterable instanceof Collection<?>) {
return ((Collection<?>) iterable).isEmpty();
} else {
return IteratorUtils.isEmpty(emptyIteratorIfNull(iterable));
}
}
/**
* Checks if the object is contained in the given iterable.
* <p>
* A <code>null</code> or empty iterable returns false.
*
* @param <E> the type of object the {@link Iterable} contains
* @param iterator the iterable to check, may be null
* @param object the object to check
* @return true if the object is contained in the iterable, false otherwise
*/
public static <E> boolean contains(final Iterable<E> iterable, final Object object) {
if (iterable instanceof Collection<?>) {
return ((Collection<E>) iterable).contains(object);
} else {
return IteratorUtils.contains(emptyIteratorIfNull(iterable), object);
}
}
/**
* Returns the <code>index</code>-th value in the <code>iterable</code>'s {@link Iterator}, throwing
* <code>IndexOutOfBoundsException</code> if there is no such element.
* <p>
* If the {@link Iterable} is a {@link List}, then it will use {@link List#get(int)}.
*
* @param <T> the type of object in the {@link Iterable}.
* @param iterable the {@link Iterable} to get a value from, may be null
* @param index the index to get
* @return the object at the specified index
* @throws IndexOutOfBoundsException if the index is invalid
*/
public static <T> T get(final Iterable<T> iterable, final int index) {
CollectionUtils.checkIndexBounds(index);
if (iterable instanceof List<?>) {
return ((List<T>) iterable).get(index);
}
return IteratorUtils.get(emptyIteratorIfNull(iterable), index);
}
/**
* Returns the number of elements contained in the given iterator.
* <p>
* A <code>null</code> or empty iterator returns {@code 0}.
*
* @param iterable the iterable to check, may be null
* @return the number of elements contained in the iterable
*/
public static int size(final Iterable<?> iterable) {
if (iterable instanceof Collection<?>) {
return ((Collection<?>) iterable).size();
} else {
return IteratorUtils.size(emptyIteratorIfNull(iterable));
}
}
/**
* Returns a string representation of the elements of the specified iterable.
* <p>
* The string representation consists of a list of the iterable's elements,
* enclosed in square brackets ({@code "[]"}). Adjacent elements are separated
* by the characters {@code ", "} (a comma followed by a space). Elements are
* converted to strings as by {@code String.valueOf(Object)}.
*
* @param <C> the element type
* @param iterable the iterable to convert to a string
* @param <E> the element type
* @param iterable the iterable to convert to a string, may be null
* @return a string representation of {@code iterable}
* @throws IllegalArgumentException if {@code iterable} is null
*/
public static <C> String toString(Iterable<C> iterable) {
return toString(iterable, new Transformer<C, String>() {
public String transform(C input) {
return String.valueOf(input);
}
}, DEFAULT_TOSTRING_DELIMITER, DEFAULT_TOSTRING_PREFIX, DEFAULT_TOSTRING_SUFFIX);
public static <E> String toString(final Iterable<E> iterable) {
return IteratorUtils.toString(emptyIteratorIfNull(iterable));
}
/**
* Returns a string representation of the elements of the specified iterable.
* <p>
* The string representation consists of a list of the iterable's elements,
* enclosed in square brackets ({@code "[]"}). Adjacent elements are separated
* by the characters {@code ", "} (a comma followed by a space). Elements are
* converted to strings as by using the provided {@code transformer}.
*
* @param <C> the element type
* @param iterable the iterable to convert to a string
* @param <E> the element type
* @param iterable the iterable to convert to a string, may be null
* @param transformer the transformer used to get a string representation of an element
* @return a string representation of {@code iterable}
* @throws IllegalArgumentException if {@code iterable} or {@code transformer} is null
* @throws NullPointerException if {@code transformer} is null
*/
public static <C> String toString(Iterable<C> iterable, Transformer<? super C, String> transformer) {
return toString(iterable, transformer, DEFAULT_TOSTRING_DELIMITER,
DEFAULT_TOSTRING_PREFIX, DEFAULT_TOSTRING_SUFFIX);
public static <E> String toString(final Iterable<E> iterable,
final Transformer<? super E, String> transformer) {
if (transformer == null) {
throw new NullPointerException("transformer may not be null");
}
return IteratorUtils.toString(emptyIteratorIfNull(iterable), transformer);
}
/**
* Returns a string representation of the elements of the specified iterable.
* <p>
* The string representation consists of a list of the iterable's elements,
* enclosed by the provided {@code prefix} and {@code suffix}. Adjacent elements
* are separated by the provided {@code delimiter}. Elements are converted to
* strings as by using the provided {@code transformer}.
*
* @param <C> the element type
* @param iterable the iterable to convert to a string
* @param <E> the element type
* @param iterable the iterable to convert to a string, may be null
* @param transformer the transformer used to get a string representation of an element
* @param delimiter the string to delimit elements
* @param prefix the prefix, prepended to the string representation
* @param suffix the suffix, appended to the string representation
* @return a string representation of {@code iterable}
* @throws IllegalArgumentException if any argument is null
* @throws NullPointerException if either transformer, delimiter, prefix or suffix is null
*/
public static <C> String toString(Iterable<C> iterable,
Transformer<? super C, String> transformer,
String delimiter,
String prefix,
String suffix) {
if (iterable == null) {
throw new IllegalArgumentException("iterable may not be null");
public static <E> String toString(final Iterable<E> iterable,
final Transformer<? super E, String> transformer,
final String delimiter,
final String prefix,
final String suffix) {
return IteratorUtils.toString(emptyIteratorIfNull(iterable),
transformer, delimiter, prefix, suffix);
}
// Inner classes
// ----------------------------------------------------------------------
private static abstract class AbstractIterable<E> implements Iterable<E> {
@Override
public String toString() {
return IterableUtils.toString(this);
}
if (transformer == null) {
throw new IllegalArgumentException("transformer may not be null");
}
if (delimiter == null) {
throw new IllegalArgumentException("delimiter may not be null");
}
if (prefix == null) {
throw new IllegalArgumentException("prefix may not be null");
}
if (suffix == null) {
throw new IllegalArgumentException("suffix may not be null");
}
final StringBuilder stringBuilder = new StringBuilder(prefix);
for(final C element : iterable) {
stringBuilder.append(transformer.transform(element));
stringBuilder.append(delimiter);
}
if(stringBuilder.length() > prefix.length()) {
stringBuilder.setLength(stringBuilder.length() - delimiter.length());
}
stringBuilder.append(suffix);
return stringBuilder.toString();
}
}

View File

@ -29,6 +29,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.apache.commons.collections4.functors.EqualPredicate;
import org.apache.commons.collections4.iterators.ArrayIterator;
import org.apache.commons.collections4.iterators.ArrayListIterator;
import org.apache.commons.collections4.iterators.BoundedIterator;
@ -55,6 +56,7 @@ import org.apache.commons.collections4.iterators.PeekingIterator;
import org.apache.commons.collections4.iterators.PushbackIterator;
import org.apache.commons.collections4.iterators.SingletonIterator;
import org.apache.commons.collections4.iterators.SingletonListIterator;
import org.apache.commons.collections4.iterators.SkippingIterator;
import org.apache.commons.collections4.iterators.TransformIterator;
import org.apache.commons.collections4.iterators.UnmodifiableIterator;
import org.apache.commons.collections4.iterators.UnmodifiableListIterator;
@ -116,6 +118,22 @@ public class IteratorUtils {
@SuppressWarnings("rawtypes")
public static final OrderedMapIterator EMPTY_ORDERED_MAP_ITERATOR = EmptyOrderedMapIterator.INSTANCE;
/**
* Default prefix used while converting an Iterator to its String representation.
*/
private static final String DEFAULT_TOSTRING_PREFIX = "[";
/**
* Default suffix used while converting an Iterator to its String representation.
*/
private static final String DEFAULT_TOSTRING_SUFFIX = "]";
/**
* Default delimiter used to delimit elements while converting an Iterator
* to its String representation.
*/
private static final String DEFAULT_TOSTRING_DELIMITER = ", ";
/**
* IteratorUtils is not normally instantiated.
*/
@ -443,7 +461,7 @@ public class IteratorUtils {
* @param iterator the iterator to decorate
* @param max the maximum number of elements returned by this iterator
* @return a new bounded iterator
* @throws IllegalArgumentException if the iterator is null or either offset or max is negative
* @throws IllegalArgumentException if the iterator is null or max is negative
* @since 4.1
*/
public static <E> BoundedIterator<E> boundedIterator(final Iterator<? extends E> iterator, long max) {
@ -471,6 +489,22 @@ public class IteratorUtils {
return new BoundedIterator<E>(iterator, offset, max);
}
// Skipping
//-----------------------------------------------------------------------
/**
* Decorates the specified iterator to skip the first N elements.
*
* @param <E> the element type
* @param iterator the iterator to decorate
* @param offset the first number of elements to skip
* @return a new skipping iterator
* @throws IllegalArgumentException if the iterator is null or offset is negative
* @since 4.1
*/
public static <E> SkippingIterator<E> skippingIterator(final Iterator<E> iterator, long offset) {
return new SkippingIterator<E>(iterator, offset);
}
// Unmodifiable
//-----------------------------------------------------------------------
/**
@ -1138,4 +1172,230 @@ public class IteratorUtils {
return singletonIterator(obj);
}
// Utility methods
//-----------------------------------------------------------------------
/**
* Answers true if a predicate is true for any element of the iterator.
* <p>
* A <code>null</code> or empty iterator returns false.
*
* @param <E> the type of object the {@link Iterator} contains
* @param input the {@link Iterator} to use, may be null
* @param predicate the predicate to use, may not be null
* @return true if any element of the collection matches the predicate, false otherwise
* @throws NullPointerException if predicate is null
* @since 4.1
*/
public static <E> boolean matchesAny(final Iterator<E> iterator, final Predicate<? super E> predicate) {
if (predicate == null) {
throw new NullPointerException("Predicate must not be null");
}
if (iterator != null) {
while (iterator.hasNext()) {
final E element = iterator.next();
if (predicate.evaluate(element)) {
return true;
}
}
}
return false;
}
/**
* Answers true if a predicate is true for every element of an iterator.
* <p>
* A <code>null</code> or empty iterator returns true.
*
* @param <E> the type of object the {@link Iterator} contains
* @param input the {@link Iterator} to use, may be null
* @param predicate the predicate to use, may not be null
* @return true if every element of the collection matches the predicate or if the
* collection is empty, false otherwise
* @throws NullPointerException if predicate is null
* @since 4.1
*/
public static <E> boolean matchesAll(final Iterator<E> iterator, final Predicate<? super E> predicate) {
if (predicate == null) {
throw new NullPointerException("Predicate must not be null");
}
if (iterator != null) {
while (iterator.hasNext()) {
final E element = iterator.next();
if (!predicate.evaluate(element)) {
return false;
}
}
}
return true;
}
/**
* Checks if the given iterator is empty.
* <p>
* A <code>null</code> or empty iterator returns true.
*
* @param iterator the {@link Iterator} to use, may be null
* @return true if the iterator is exhausted or null, false otherwise
* @since 4.1
*/
public static boolean isEmpty(final Iterator<?> iterator) {
return iterator == null || !iterator.hasNext();
}
/**
* Checks if the object is contained in the given iterator.
* <p>
* A <code>null</code> or empty iterator returns false.
*
* @param <E> the type of object the {@link Iterator} contains
* @param iterator the iterator to check, may be null
* @param object the object to check
* @return true if the object is contained in the iterator, false otherwise
* @since 4.1
*/
public static <E> boolean contains(final Iterator<E> iterator, final Object object) {
return matchesAny(iterator, EqualPredicate.equalPredicate(object));
}
/**
* Returns the <code>index</code>-th value in {@link Iterator}, throwing
* <code>IndexOutOfBoundsException</code> if there is no such element.
* <p>
* The Iterator is advanced to <code>index</code> (or to the end, if
* <code>index</code> exceeds the number of entries) as a side effect of this method.
*
* @param <E> the type of object in the {@link Iterator}
* @param iterator the iterator to get a value from
* @param index the index to get
* @return the object at the specified index
* @throws IndexOutOfBoundsException if the index is invalid
* @throws IllegalArgumentException if the object type is invalid
* @since 4.1
*/
public static <E> E get(final Iterator<E> iterator, final int index) {
int i = index;
CollectionUtils.checkIndexBounds(i);
while (iterator.hasNext()) {
i--;
if (i == -1) {
return iterator.next();
}
iterator.next();
}
throw new IndexOutOfBoundsException("Entry does not exist: " + i);
}
/**
* Returns the number of elements contained in the given iterator.
* <p>
* A <code>null</code> or empty iterator returns {@code 0}.
*
* @param iterator the iterator to check, may be null
* @return the number of elements contained in the iterator
* @since 4.1
*/
public static int size(final Iterator<?> iterator) {
int size = 0;
if (iterator != null) {
while (iterator.hasNext()) {
iterator.next();
size++;
}
}
return size;
}
/**
* Returns a string representation of the elements of the specified iterator.
* <p>
* The string representation consists of a list of the iterator's elements,
* enclosed in square brackets ({@code "[]"}). Adjacent elements are separated
* by the characters {@code ", "} (a comma followed by a space). Elements are
* converted to strings as by {@code String.valueOf(Object)}.
*
* @param <E> the element type
* @param iterable the iterator to convert to a string
* @return a string representation of {@code iterator}
* @since 4.1
*/
public static <E> String toString(final Iterator<E> iterator) {
return toString(iterator, TransformerUtils.stringValueTransformer(),
DEFAULT_TOSTRING_DELIMITER, DEFAULT_TOSTRING_PREFIX,
DEFAULT_TOSTRING_SUFFIX);
}
/**
* Returns a string representation of the elements of the specified iterator.
* <p>
* The string representation consists of a list of the iterable's elements,
* enclosed in square brackets ({@code "[]"}). Adjacent elements are separated
* by the characters {@code ", "} (a comma followed by a space). Elements are
* converted to strings as by using the provided {@code transformer}.
*
* @param <E> the element type
* @param iterable the iterator to convert to a string, may be null
* @param transformer the transformer used to get a string representation of an element
* @return a string representation of {@code iterator}
* @throws NullPointerException if {@code transformer} is null
* @since 4.1
*/
public static <E> String toString(final Iterator<E> iterator,
final Transformer<? super E, String> transformer) {
return toString(iterator, transformer, DEFAULT_TOSTRING_DELIMITER,
DEFAULT_TOSTRING_PREFIX, DEFAULT_TOSTRING_SUFFIX);
}
/**
* Returns a string representation of the elements of the specified iterator.
* <p>
* The string representation consists of a list of the iterator's elements,
* enclosed by the provided {@code prefix} and {@code suffix}. Adjacent elements
* are separated by the provided {@code delimiter}. Elements are converted to
* strings as by using the provided {@code transformer}.
*
* @param <E> the element type
* @param iterator the iterator to convert to a string, may be null
* @param transformer the transformer used to get a string representation of an element
* @param delimiter the string to delimit elements
* @param prefix the prefix, prepended to the string representation
* @param suffix the suffix, appended to the string representation
* @return a string representation of {@code iterator}
* @throws NullPointerException if either transformer, delimiter, prefix or suffix is null
* @since 4.1
*/
public static <E> String toString(final Iterator<E> iterator,
final Transformer<? super E, String> transformer,
final String delimiter,
final String prefix,
final String suffix) {
if (transformer == null) {
throw new NullPointerException("transformer may not be null");
}
if (delimiter == null) {
throw new NullPointerException("delimiter may not be null");
}
if (prefix == null) {
throw new NullPointerException("prefix may not be null");
}
if (suffix == null) {
throw new NullPointerException("suffix may not be null");
}
final StringBuilder stringBuilder = new StringBuilder(prefix);
if (iterator != null) {
while (iterator.hasNext()) {
final E element = iterator.next();
stringBuilder.append(transformer.transform(element));
stringBuilder.append(delimiter);
}
if(stringBuilder.length() > prefix.length()) {
stringBuilder.setLength(stringBuilder.length() - delimiter.length());
}
}
stringBuilder.append(suffix);
return stringBuilder.toString();
}
}

View File

@ -0,0 +1,96 @@
/*
* 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;
/**
* Decorates another iterator to skip the first N elements.
* <p>
* In case an offset parameter other than 0 is provided, the decorated
* iterator is immediately advanced to this position, skipping all elements
* before that position.
*
* @since 4.1
* @version $Id: $
*/
public class SkippingIterator<E> extends AbstractIteratorDecorator<E> {
/** The offset to bound the first element return */
private final long offset;
/** The position of the current element */
private long pos;
//-----------------------------------------------------------------------
/**
* Decorates the specified iterator to skip all elements until the iterator
* reaches the position at {@code offset}.
* <p>
* The iterator is immediately advanced until it reaches the position at {@code offset},
* incurring O(n) time.
*
* @param iterator the iterator to be decorated
* @param offset the index of the first element of the decorated iterator to return
* @throws IllegalArgumentException if iterator is null, or offset is negative
*/
public SkippingIterator(final Iterator<E> iterator, final long offset) {
super(iterator);
if (offset < 0) {
throw new IllegalArgumentException("Offset parameter must not be negative.");
}
this.offset = offset;
this.pos = 0;
init();
}
/**
* Skips the given number of elements.
*/
private void init() {
while (pos < offset && hasNext()) {
next();
pos++;
}
}
//-----------------------------------------------------------------------
@Override
public E next() {
final E next = super.next();
pos++;
return next;
}
/**
* {@inheritDoc}
* <p>
* In case an offset other than 0 was specified, the underlying iterator will be advanced
* to this position upon creation. A call to {@link #remove()} will still result in an
* {@link IllegalStateException} if no explicit call to {@link #next()} has been made prior
* to calling {@link #remove()}.
*/
@Override
public void remove() {
if (pos <= offset) {
throw new IllegalStateException("remove() can not be called before calling next()");
}
super.remove();
}
}

View File

@ -65,12 +65,8 @@ public class IterableUtilsTest extends BulkTest {
result = IterableUtils.toString(new ArrayList<Integer>());
assertEquals("[]", result);
try {
IterableUtils.toString(null);
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
// expected
}
result = IterableUtils.toString(null);
assertEquals("[]", result);
result = IterableUtils.toString(iterableA, new Transformer<Integer, String>() {
public String transform(Integer input) {
@ -87,17 +83,13 @@ public class IterableUtilsTest extends BulkTest {
});
assertEquals("[]", result);
try {
IterableUtils.toString(null, new Transformer<Integer, String>() {
public String transform(Integer input) {
fail("not supposed to reach here");
return "";
}
});
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
// expected
}
result = IterableUtils.toString(null, new Transformer<Integer, String>() {
public String transform(Integer input) {
fail("not supposed to reach here");
return "";
}
});
assertEquals("[]", result);
}
public void testToStringDelimiter() {
@ -137,22 +129,18 @@ public class IterableUtilsTest extends BulkTest {
}
public void testToStringWithNullArguments() {
try {
IterableUtils.toString(null, new Transformer<Integer, String>() {
public String transform(Integer input) {
fail("not supposed to reach here");
return "";
}
}, "", "(", ")");
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
// expected
}
String result = IterableUtils.toString(null, new Transformer<Integer, String>() {
public String transform(Integer input) {
fail("not supposed to reach here");
return "";
}
}, "", "(", ")");
assertEquals("()", result);
try {
IterableUtils.toString(new ArrayList<Integer>(), null, "", "(", ")");
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
fail("expecting NullPointerException");
} catch (final NullPointerException ex) {
// expected
}
@ -163,8 +151,8 @@ public class IterableUtilsTest extends BulkTest {
return "";
}
}, null, "(", ")");
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
fail("expecting NullPointerException");
} catch (final NullPointerException ex) {
// expected
}
@ -175,8 +163,8 @@ public class IterableUtilsTest extends BulkTest {
return "";
}
}, "", null, ")");
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
fail("expecting NullPointerException");
} catch (final NullPointerException ex) {
// expected
}
@ -187,8 +175,8 @@ public class IterableUtilsTest extends BulkTest {
return "";
}
}, "", "(", null);
fail("expecting IllegalArgumentException");
} catch (final IllegalArgumentException iae) {
fail("expecting NullPointerException");
} catch (final NullPointerException ex) {
// expected
}
}