diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 69d1bc896..300662c0f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -40,8 +40,8 @@ Added clarification to javadoc of "TreeBag#add(Object)" wrt null arguments. - 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(...)". Reverted performance improvement for "SetUniqueList#retainAll(Collection)" diff --git a/src/main/java/org/apache/commons/collections4/CollectionUtils.java b/src/main/java/org/apache/commons/collections4/CollectionUtils.java index e8969869d..8d72b7e55 100644 --- a/src/main/java/org/apache/commons/collections4/CollectionUtils.java +++ b/src/main/java/org/apache/commons/collections4/CollectionUtils.java @@ -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 get(final Iterator 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 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 get(final Iterable iterable, final int index) { - checkIndexBounds(index); - if (iterable instanceof List) { - return ((List) iterable).get(index); - } - return get(iterable.iterator(), index); + return IterableUtils.get(iterable, index); } /** diff --git a/src/main/java/org/apache/commons/collections4/FluentIterable.java b/src/main/java/org/apache/commons/collections4/FluentIterable.java new file mode 100644 index 000000000..d3d77f779 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/FluentIterable.java @@ -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. + *

+ * A FluentIterable can be created either from an Iterable or from a set of elements. + * The following types of methods are provided: + *

    + *
  • fluent methods which return a new {@code FluentIterable} instance + *
  • conversion methods which copy the FluentIterable's contents into a new collection or array (e.g. toList()) + *
  • utility methods which answer questions about the FluentIterable's contents (e.g. size(), anyMatch(Predicate)) + *
  • + *
+ *

+ * The following example outputs the first 3 even numbers in the range [1, 10] into a list: + *

+ *   FluentIterable
+ *       .of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ *       .filter(new Predicate() {
+ *                   public boolean evaluate(Integer number) {
+ *                        return number % 2 == 0;
+ *                   }
+ *              )
+ *       .transform(TransformerUtils.stringValueTransformer())
+ *       .limit(3)
+ *       .toList();
+ * 
+ * + * @param the element type + * @since 4.1 + * @version $Id: $ + */ +public class FluentIterable implements Iterable { + + private final Iterable iterable; + + // Static factory methods + // ---------------------------------------------------------------------- + + public static FluentIterable of(T... elements) { + return of(Arrays.asList(elements)); + } + + public static FluentIterable of(Iterable iterable) { + if (iterable == null) { + throw new NullPointerException("Iterable must not be null"); + } + if (iterable instanceof FluentIterable) { + return (FluentIterable) iterable; + } else { + return new FluentIterable(iterable); + } + } + + // Constructor + // ---------------------------------------------------------------------- + + private FluentIterable(final Iterable iterable) { + this.iterable = iterable; + } + + // fluent construction methods + // ---------------------------------------------------------------------- + + public FluentIterable append(final E... elements) { + return append(Arrays.asList(elements)); + } + + public FluentIterable append(final Iterable other) { + return of(IterableUtils.chainedIterable(iterable, other)); + } + + public FluentIterable eval() { + return of(toList()); + } + + public FluentIterable filter(final Predicate predicate) { + return of(IterableUtils.filteredIterable(iterable, predicate)); + } + + public FluentIterable limit(final int maxSize) { + return of(IterableUtils.boundedIterable(iterable, maxSize)); + } + + public FluentIterable loop() { + return of(IterableUtils.loopingIterable(iterable)); + } + + public FluentIterable skip(int elementsToSkip) { + return of(IterableUtils.skippingIterable(iterable, elementsToSkip)); + } + + public FluentIterable transform(final Transformer transformer) { + return of(IterableUtils.transformedIterable(iterable, transformer)); + } + + public FluentIterable unique() { + return of(IterableUtils.uniqueIterable(iterable)); + } + + // convenience methods + // ---------------------------------------------------------------------- + + public Iterator iterator() { + return iterable.iterator(); + } + + public Enumeration asEnumeration() { + return IteratorUtils.asEnumeration(iterator()); + } + + public boolean allMatch(final Predicate predicate) { + return IterableUtils.matchesAll(iterable, predicate); + } + + public boolean anyMatch(final Predicate 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 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 arrayClass) { + return IteratorUtils.toArray(iterator(), arrayClass); + } + + public List toList() { + return IteratorUtils.toList(iterator()); + } + + @Override + public String toString() { + return IterableUtils.toString(iterable); + } + +} diff --git a/src/main/java/org/apache/commons/collections4/IterableUtils.java b/src/main/java/org/apache/commons/collections4/IterableUtils.java index 7297e21f1..de5ae84b6 100644 --- a/src/main/java/org/apache/commons/collections4/IterableUtils.java +++ b/src/main/java/org/apache/commons/collections4/IterableUtils.java @@ -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. + *

+ * 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. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param 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 Iterable chainedIterable(final Iterable a, final Iterable 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. + *

+ * 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. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param 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 Iterable chainedIterable(final Iterable a, + final Iterable b, + final Iterable c) { + return chainedIterable(new Iterable[] {a, b, c}); + } + + /** + * Combines four iterables into a single iterable. + *

+ * 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. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param 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 Iterable chainedIterable(final Iterable a, + final Iterable b, + final Iterable c, + final Iterable d) { + return chainedIterable(new Iterable[] {a, b, c, d}); + } + + /** + * Combines the provided iterables into a single iterable. + *

+ * 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. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param the element type + * @param iterables the iterables to combine + * @return a new iterable, combining the provided iterables + */ + public static Iterable chainedIterable(final Iterable... iterables) { + return new AbstractIterable() { + @Override + public Iterator iterator() { + return new LazyIteratorChain() { + + @Override + protected Iterator 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. + *

+ * The returned iterable's iterator does not supports {@code remove()}. + * + * @param 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 Iterable filteredIterable(final Iterable iterable, final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("predicate must not be null."); + } + + return new AbstractIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.filteredIterator(emptyIteratorIfNull(iterable), predicate); + } + }; + } + + // Bounded + // ---------------------------------------------------------------------- + + /** + * Returns a view of the given iterable that contains at most the given number + * of elements. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param 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 Iterable boundedIterable(final Iterable iterable, final int maxSize) { + if (maxSize < 0) { + throw new IllegalArgumentException("maxSize parameter must not be negative."); + } + + return new AbstractIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.boundedIterator(emptyIteratorIfNull(iterable), maxSize); + } + }; + } + + // Looping + // ---------------------------------------------------------------------- + + /** + * Returns a view of the given iterable which will cycle infinitely over + * its elements. + *

+ * 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 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 Iterable loopingIterable(final Iterable iterable) { + return new AbstractIterable() { + @Override + public Iterator iterator() { + return new LazyIteratorChain() { + @Override + protected Iterator 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. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param 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 Iterable skippingIterable(final Iterable iterable, final int elementsToSkip) { + if (elementsToSkip < 0) { + throw new IllegalArgumentException("elementsToSkip parameter must not be negative."); + } + + return new AbstractIterable() { + @Override + public Iterator 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. + *

+ * The returned iterable's iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @param 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 Iterable transformedIterable(final Iterable iterable, final Transformer transformer) { + if (transformer == null) { + throw new NullPointerException("transformer must not be null."); + } + + return new AbstractIterable() { + @Override + public Iterator iterator() { + return IteratorUtils.transformedIterator(emptyIteratorIfNull(iterable), transformer); + } + }; + } + + // Unique + // ---------------------------------------------------------------------- + + /** + * Returns a unique view of the given iterable. + *

+ * The returned iterable's iterator does not supports {@code remove()}. + * + * @param the element type + * @param iterable the iterable to transform, may be null + * @return a unique view of the specified iterable + */ + public static Iterable uniqueIterable(final Iterable iterable) { + return new AbstractIterable() { + @Override + public Iterator iterator() { + return new UniqueFilterIterator(emptyIteratorIfNull(iterable)); + } + }; + } + + // Utility methods + // ---------------------------------------------------------------------- + + /** + * Returns an empty iterator if the argument is null, + * or returns {@code iterable.iterator()} otherwise. + * + * @param the element type + * @param iterable the iterable, possibly null + * @return an empty collection if the argument is null + */ + public static Iterator emptyIteratorIfNull(final Iterable iterable) { + return iterable != null ? iterable.iterator() : IteratorUtils.emptyIterator(); + } + + /** + * Answers true if a predicate is true for every element of an iterable. + *

+ * A null or empty iterable returns true. + * + * @param 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 boolean matchesAll(final Iterable iterable, final Predicate predicate) { + return IteratorUtils.matchesAll(emptyIteratorIfNull(iterable), predicate); + } + + /** + * Answers true if a predicate is true for any element of the iterable. + *

+ * A null or empty iterable returns false. + * + * @param 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 boolean matchesAny(final Iterable iterable, final Predicate predicate) { + return IteratorUtils.matchesAny(emptyIteratorIfNull(iterable), predicate); + } + + /** + * Answers true if the provided iterable is empty. + *

+ * A null 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. + *

+ * A null or empty iterable returns false. + * + * @param 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 boolean contains(final Iterable iterable, final Object object) { + if (iterable instanceof Collection) { + return ((Collection) iterable).contains(object); + } else { + return IteratorUtils.contains(emptyIteratorIfNull(iterable), object); + } + } + + /** + * Returns the index-th value in the iterable's {@link Iterator}, throwing + * IndexOutOfBoundsException if there is no such element. + *

+ * If the {@link Iterable} is a {@link List}, then it will use {@link List#get(int)}. + * + * @param 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 get(final Iterable iterable, final int index) { + CollectionUtils.checkIndexBounds(index); + if (iterable instanceof List) { + return ((List) iterable).get(index); + } + return IteratorUtils.get(emptyIteratorIfNull(iterable), index); + } + + /** + * Returns the number of elements contained in the given iterator. + *

+ * A null 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. + *

* 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 the element type - * @param iterable the iterable to convert to a string + * @param 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 String toString(Iterable iterable) { - return toString(iterable, new Transformer() { - public String transform(C input) { - return String.valueOf(input); - } - }, DEFAULT_TOSTRING_DELIMITER, DEFAULT_TOSTRING_PREFIX, DEFAULT_TOSTRING_SUFFIX); + public static String toString(final Iterable iterable) { + return IteratorUtils.toString(emptyIteratorIfNull(iterable)); } /** * Returns a string representation of the elements of the specified iterable. + *

* 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 the element type - * @param iterable the iterable to convert to a string + * @param 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 String toString(Iterable iterable, Transformer transformer) { - return toString(iterable, transformer, DEFAULT_TOSTRING_DELIMITER, - DEFAULT_TOSTRING_PREFIX, DEFAULT_TOSTRING_SUFFIX); + public static String toString(final Iterable iterable, + final Transformer 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. + *

* 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 the element type - * @param iterable the iterable to convert to a string + * @param 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 String toString(Iterable iterable, - Transformer transformer, - String delimiter, - String prefix, - String suffix) { - if (iterable == null) { - throw new IllegalArgumentException("iterable may not be null"); + public static String toString(final Iterable iterable, + final Transformer 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 implements Iterable { + @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(); } } diff --git a/src/main/java/org/apache/commons/collections4/IteratorUtils.java b/src/main/java/org/apache/commons/collections4/IteratorUtils.java index c240d9d63..15eda1b93 100644 --- a/src/main/java/org/apache/commons/collections4/IteratorUtils.java +++ b/src/main/java/org/apache/commons/collections4/IteratorUtils.java @@ -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,10 +118,26 @@ 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. */ - private IteratorUtils() {} + private IteratorUtils() {} // Empty //----------------------------------------------------------------------- @@ -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 BoundedIterator boundedIterator(final Iterator iterator, long max) { @@ -471,6 +489,22 @@ public class IteratorUtils { return new BoundedIterator(iterator, offset, max); } + // Skipping + //----------------------------------------------------------------------- + /** + * Decorates the specified iterator to skip the first N elements. + * + * @param 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 SkippingIterator skippingIterator(final Iterator iterator, long offset) { + return new SkippingIterator(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. + *

+ * A null or empty iterator returns false. + * + * @param 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 boolean matchesAny(final Iterator iterator, final Predicate 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. + *

+ * A null or empty iterator returns true. + * + * @param 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 boolean matchesAll(final Iterator iterator, final Predicate 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. + *

+ * A null 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. + *

+ * A null or empty iterator returns false. + * + * @param 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 boolean contains(final Iterator iterator, final Object object) { + return matchesAny(iterator, EqualPredicate.equalPredicate(object)); + } + + /** + * Returns the index-th value in {@link Iterator}, throwing + * IndexOutOfBoundsException if there is no such element. + *

+ * The Iterator is advanced to index (or to the end, if + * index exceeds the number of entries) as a side effect of this method. + * + * @param 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 get(final Iterator 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. + *

+ * A null 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. + *

+ * 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 the element type + * @param iterable the iterator to convert to a string + * @return a string representation of {@code iterator} + * @since 4.1 + */ + public static String toString(final Iterator 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. + *

+ * 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 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 String toString(final Iterator iterator, + final Transformer 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. + *

+ * 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 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 String toString(final Iterator iterator, + final Transformer 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(); + } + } diff --git a/src/main/java/org/apache/commons/collections4/iterators/SkippingIterator.java b/src/main/java/org/apache/commons/collections4/iterators/SkippingIterator.java new file mode 100644 index 000000000..aa0e7bcab --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/iterators/SkippingIterator.java @@ -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. + *

+ * 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 extends AbstractIteratorDecorator { + + /** 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}. + *

+ * 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 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} + *

+ * 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(); + } + +} diff --git a/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java b/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java index 71e812eeb..dd1fa0d0d 100644 --- a/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java +++ b/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java @@ -65,12 +65,8 @@ public class IterableUtilsTest extends BulkTest { result = IterableUtils.toString(new ArrayList()); 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() { public String transform(Integer input) { @@ -87,17 +83,13 @@ public class IterableUtilsTest extends BulkTest { }); assertEquals("[]", result); - try { - IterableUtils.toString(null, new Transformer() { - 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() { + 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() { - 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() { + public String transform(Integer input) { + fail("not supposed to reach here"); + return ""; + } + }, "", "(", ")"); + assertEquals("()", result); try { IterableUtils.toString(new ArrayList(), 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 } }