All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openstreetmap.atlas.utilities.collections.Iterables Maven / Gradle / Ivy

The newest version!
package org.openstreetmap.atlas.utilities.collections;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.LongFunction;
import java.util.function.Predicate;
import java.util.function.ToLongFunction;
import java.util.stream.StreamSupport;

/**
 * Iterable utility methods
 *
 * @author matthieun
 */
public final class Iterables
{
    /**
     * Adds the contents of the from Iterable to the addHere collection, and returns true if items
     * were added. It's possible that nothing has changed if addHere is a set
     *
     * @param addHere
     *            where to add the items
     * @param from
     *            where to get the items
     * @param 
     *            what kind of objects we're copying
     * @return true if addHere has changed, false otherwise
     */
    public static  boolean addAll(final Collection addHere, final Iterable from)
    {
        final int oldSize = addHere.size();
        StreamSupport.stream(from.spliterator(), false).forEach(addHere::add);
        return oldSize < addHere.size();
    }

    /**
     * Strip down any {@link Iterable} into .. just an {@link Iterable}.
     *
     * @param types
     *            The {@link Iterable} to strip down
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  Iterable asIterable(final Iterable types)
    {
        return types::iterator;
    }

    /**
     * Translate an {@link Iterable} into a {@link List}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  List asList(final Iterable types)
    {
        if (types instanceof List)
        {
            return (List) types;
        }
        final int initialSize;
        if (types instanceof Collection)
        {
            initialSize = ((Collection) types).size();
        }
        else
        {
            initialSize = 0;
        }
        final List result = new ArrayList<>(initialSize);
        types.forEach(result::add);
        return result;
    }

    /**
     * Translate an array to an {@link List}. Unlike {@link java.util.Arrays#asList}, this method
     * does not use the given array as the backing array.
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  List asList(final T[] types)
    {
        // This avoids several grow calls, and ensures that we aren't using the backing array.
        return new ArrayList<>(Arrays.asList(types));
    }

    /**
     * Translate an iterable list of Map entries to a map
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of key of the entry
     * @param 
     *            The type of value of the entry
     * @return The translated {@link Iterable}
     */
    public static  Map asMap(final Iterable> types)
    {
        final Map result = new HashMap<>();
        types.forEach(entry -> result.put(entry.getKey(), entry.getValue()));
        return result;
    }

    /**
     * Translate an {@link Iterable} into a {@link Queue}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  Queue asQueue(final Iterable types)
    {
        final Queue result = new LinkedList<>();
        types.forEach(result::add);
        return result;
    }

    /**
     * Translate an {@link Iterable} into a {@link Set}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  Set asSet(final Iterable types)
    {
        final Set result = new HashSet<>();
        types.forEach(result::add);
        return result;
    }

    /**
     * Translate an array to an {@link Set}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  Set asSet(final T[] types)
    {
        final Set result = new HashSet<>();
        for (final T type : types)
        {
            result.add(type);
        }
        return result;
    }

    /**
     * Translate an {@link Iterable} of items into a {@link SortedSet}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  SortedSet asSortedSet(final Iterable types)
    {
        final SortedSet result = new TreeSet<>();
        types.forEach(result::add);
        return result;
    }

    /**
     * Test if an {@link Iterable} iterates at some point on an item.
     *
     * @param types
     *            The {@link Iterable} to test
     * @param type
     *            The item to test
     * @param 
     *            The type of the {@link Iterable}
     * @return True if the {@link Iterable} iterates at some point on the item.
     */
    public static  boolean contains(final Iterable types, final T type)
    {
        if (types instanceof Collection)
        {
            return ((Collection) types).contains(type);
        }
        for (final T candidate : types)
        {
            if (candidate.equals(type))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Count a set of values
     *
     * @param types
     *            The {@link Iterable} of input type
     * @param typeCounter
     *            The function from type to count
     * @param 
     *            The type of the {@link Iterable}
     * @return The total count
     */
    public static  long count(final Iterable types, final ToLongFunction typeCounter)
    {
        long result = 0;
        for (final T type : types)
        {
            result += typeCounter.applyAsLong(type);
        }
        return result;
    }

    /**
     * @param example
     *            A random object to specify the type
     * @param 
     *            The type of the {@link Iterable}
     * @return An empty {@link Iterable} of the right type
     */
    public static  Iterable emptyIterable(final T example) // NOSONAR
    {
        return () -> new Iterator()
        {
            @Override
            public boolean hasNext()
            {
                return false;
            }

            @Override
            public T next()
            {
                throw new NoSuchElementException();
            }
        };
    }

    /**
     * Test if two {@link Iterable}s iterate on the same items.
     *
     * @param that
     *            The first {@link Iterable}
     * @param other
     *            The second iterable
     * @param 
     *            The type of the {@link Iterable}
     * @return True if the two {@link Iterable}s iterate on the same items.
     */
    public static  boolean equals(final Iterable that, final Iterable other)
    {
        // Handle null iterables
        // If they are both null, then equal
        // If only one of them is null, then NOT equal
        final boolean thatIsNull = that == null;
        final boolean otherIsNull = other == null;
        if (thatIsNull || otherIsNull)
        {
            return thatIsNull && otherIsNull;
        }

        // Iterables are not null, let's check for size first
        // Then the values
        final long thatSize = Iterables.size(that);
        if (thatSize != Iterables.size(other))
        {
            return false;
        }
        final Iterator thatIterator = that.iterator();
        final Iterator otherIterator = other.iterator();
        while (thatIterator.hasNext())
        {
            if (!thatIterator.next().equals(otherIterator.next()))
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Filter an {@link Iterable}
     *
     * @param input
     *            The {@link Iterable} to filter
     * @param matcher
     *            The {@link Predicate} used to filter
     * @param 
     *            The type of the {@link Iterable}
     * @return The filtered {@link Iterable}
     */
    public static  Iterable filter(final Iterable input, final Predicate matcher)
    {
        return filterTranslate(input, item -> item, matcher);
    }

    /**
     * Translate an {@link Iterable} of items into a {@link FilteredIterable}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param filterSet
     *            A set of identifiers for elements to skip (can be empty or have members)
     * @param identifier
     *            A function that takes an element of T for the {@link Iterable} and returns the
     *            identifier for that element
     * @param 
     *            The type of the {@link Iterable}
     * @param 
     *            The type of the Identifier object for the elements in the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  FilteredIterable filter(final Iterable types,
            final Set filterSet, final Function identifier)
    {
        return new FilteredIterable<>(types, filterSet, identifier);
    }

    /**
     * Translate an {@link Iterable} of type I to an {@link Iterable} of type O.
     *
     * @param input
     *            The input {@link Iterable}
     * @param converter
     *            The converter from I to O
     * @param matcher
     *            A {@link Predicate} on I that filters only the items to match
     * @param 
     *            The type of the input {@link Iterable}
     * @param 
     *            The type of the output {@link Iterable}
     * @return The {@link Iterable} of O
     */
    public static  Iterable filterTranslate(final Iterable input,
            final Function converter, final Predicate matcher)
    {
        return new Iterable()
        {
            @Override
            public Iterator iterator()
            {
                return new Iterator()
                {
                    private boolean consumed = true;
                    private final Iterator iterator = input.iterator();
                    private I next = null;
                    private boolean valid = false;

                    @Override
                    public boolean hasNext()
                    {
                        if (this.consumed)
                        {
                            this.next = null;
                            this.valid = false;
                            while (this.iterator.hasNext() && !this.valid)
                            {
                                this.next = this.iterator.next();
                                this.valid = matcher.test(this.next);
                            }
                            this.consumed = false;
                        }
                        return this.valid;
                    }

                    @Override
                    public O next()
                    {
                        if (hasNext())
                        {
                            this.consumed = true;
                            return converter.apply(this.next);
                        }
                        throw new NoSuchElementException();
                    }
                };
            }

            @SuppressWarnings("unused")
            public void useless()
            {
                // Unused
            }
        };
    }

    /**
     * Get the first element of an {@link Iterable}
     *
     * @param types
     *            The items
     * @param 
     *            The type of the {@link Iterable}
     * @return The first element in the {@link Iterable}, or empty if none.
     */
    public static  Optional first(final Iterable types)
    {
        return nth(types, 0);
    }

    public static  Optional firstMatching(final Iterable types, final Predicate matcher)
    {
        return first(filter(types, matcher));
    }

    /**
     * Create an {@link Iterable} from an {@link Enumeration}
     *
     * @param types
     *            The {@link Enumeration}
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    public static  Iterable from(final Enumeration types)
    {
        return () -> new Iterator()
        {
            @Override
            public boolean hasNext()
            {
                return types.hasMoreElements();
            }

            @Override
            public T next()
            {
                if (!hasNext())
                {
                    throw new NoSuchElementException();
                }
                return types.nextElement();
            }
        };
    }

    /**
     * Create an {@link Iterable} from 0 to many items of the provided type
     *
     * @param types
     *            The 0 to many array of items to include
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    @SafeVarargs
    public static  Iterable from(final T... types)
    {
        return asList(types);
    }

    /**
     * Get the head (first) element of an {@link Iterable}
     *
     * @param types
     *            The items
     * @param 
     *            The type of the {@link Iterable}
     * @return The head element in the {@link Iterable}, or null if none.
     */
    public static  T head(final Iterable types)
    {
        final Iterator iterator = types.iterator();
        return iterator.hasNext() ? iterator.next() : null;
    }

    /**
     * Get an {@link Iterable} based on something that can return the value at a specific index.
     *
     * @param size
     *            The total size of the collection
     * @param supplier
     *            The provider of the value based on the index
     * @param 
     *            The type to return within the {@link Iterable}
     * @return The index based {@link Iterable}
     */
    public static  Iterable indexBasedIterable(final long size,
            final LongFunction supplier)
    {
        return () -> new Iterator()
        {
            private long index = 0L;

            @Override
            public boolean hasNext()
            {
                return this.index < size;
            }

            @Override
            public T next()
            {
                if (!hasNext())
                {
                    throw new NoSuchElementException();
                }
                return supplier.apply(this.index++);
            }
        };
    }

    /**
     * Determines if the given iterable is empty
     *
     * @param types
     *            The iterable to check
     * @return {@code true} if the iterable contains no elements
     */
    public static boolean isEmpty(final Iterable types)
    {
        if (types instanceof Collection)
        {
            return ((Collection) types).isEmpty();
        }
        return !types.iterator().hasNext();
    }

    /**
     * Translate a passed array of Items to an {@link Iterable} of Items
     *
     * @param types
     *            The items
     * @param 
     *            The type of the {@link Iterable}
     * @return An {@link Iterable} of items.
     */
    public static  Iterable iterable(@SuppressWarnings("unchecked") final T... types)
    {
        return indexBasedIterable(types.length, index -> types[(int) index]);
    }

    /**
     * Build an new Iterable by prepending the head element to the tail iterable.
     *
     * @param head
     *            The item to place in the head position
     * @param tail
     *            The items positioned after the head
     * @param 
     *            The type of the head and tail {@link Iterable}
     * @return An {@link Iterable}
     */
    public static  Iterable join(final T head, final Iterable tail)
    {
        return () -> new Iterator()
        {
            private final Iterator tailIterator = tail.iterator();
            private boolean headConsumed = false;

            @Override
            public boolean hasNext()
            {
                return !this.headConsumed || this.tailIterator.hasNext();
            }

            @Override
            public T next()
            {
                if (this.headConsumed)
                {
                    return this.tailIterator.next();
                }
                this.headConsumed = true;
                return head;
            }
        };
    }

    /**
     * Get the last element of an {@link Iterable}
     *
     * @param types
     *            The items
     * @param 
     *            The type of the {@link Iterable}
     * @return The last element in the {@link Iterable}
     */
    public static  Optional last(final Iterable types)
    {
        T result = null;
        if (types instanceof List)
        {
            final List list = (List) types;
            if (!list.isEmpty())
            {
                result = list.get(list.size() - 1);
            }
        }
        else
        {
            for (final T type : types)
            {
                result = type;
            }
        }
        return Optional.ofNullable(result);
    }

    public static  Optional lastMatching(final Iterable types, final Predicate matcher)
    {
        return last(filter(types, matcher));
    }

    /**
     * Get the nth element of an {@link Iterable}
     *
     * @param types
     *            The items
     * @param index
     *            The index at which to pick
     * @param 
     *            The type of the {@link Iterable}
     * @return The first element in the {@link Iterable}, or empty if the iterable has no element at
     *         this index.
     */
    public static  Optional nth(final Iterable types, final long index)
    {
        long counter = 0L;
        final Iterator iterator = types.iterator();
        T result = iterator.hasNext() ? iterator.next() : null;
        while (counter++ < index)
        {
            if (iterator.hasNext())
            {
                result = iterator.next();
            }
            else
            {
                result = null;
                break;
            }
        }
        return Optional.ofNullable(result);
    }

    /**
     * Create a {@link StreamIterable} that uses parallelization
     *
     * @param source
     *            The {@link Iterable} to use as source
     * @param 
     *            The type of the source {@link Iterable}
     * @return The corresponding {@link StreamIterable}
     */
    public static  StreamIterable parallelStream(final Iterable source)
    {
        return new StreamIterable<>(source, true);
    }

    public static  void print(final Iterable input, final String name)
    {
        System.out.println(toString(input, name)); // NOSONAR
    }

    /**
     * Iterate over an {@link Iterable} to get its size. If the {@link Iterable} is a sub instance
     * of {@link Collection}, then it reads the size from it directly; it will not iterate
     * unnecessarily.
     *
     * @param 
     *            The type of the {@link Iterable}
     * @param types
     *            The input {@link Iterable}
     * @return The size of the {@link Iterable}
     */
    public static  long size(final Iterable types)
    {
        if (types instanceof Collection)
        {
            return ((Collection) types).size();
        }
        return count(types, type -> 1L);
    }

    /**
     * Create a {@link StreamIterable}
     *
     * @param source
     *            The {@link Iterable} to use as source
     * @param 
     *            The type of the source {@link Iterable}
     * @return The corresponding {@link StreamIterable}
     */
    public static  StreamIterable stream(final Iterable source)
    {
        return new StreamIterable<>(source);
    }

    /**
     * Get an {@link Iterable} of all elements beyond the head.
     *
     * @param types
     *            The items
     * @param 
     *            The type of the {@link Iterable}
     * @return An {@link Iterable}
     */
    public static  Iterable tail(final Iterable types)
    {
        final Iterator iterator = types.iterator();
        if (iterator.hasNext())
        {
            iterator.next();
        }
        return () -> iterator;
    }

    /**
     * Translate an array to an {@link List}
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param 
     *            The type of the {@link Iterable}
     * @return The translated {@link Iterable}
     */
    @SafeVarargs
    public static  List toList(final T... types)
    {
        return asList(types);
    }

    /**
     * Translate an {@link Iterable} to a {@link String}
     *
     * @param input
     *            The input {@link Iterable}
     * @param 
     *            The type of the {@link Iterable}
     * @param name
     *            The name of the input {@link Iterable}
     * @return A {@link String} representation of the {@link Iterable}
     */
    public static  String toString(final Iterable input, final String name)
    {
        return toString(input, name, ", ");
    }

    /**
     * Translate an {@link Iterable} to a {@link String}
     *
     * @param input
     *            The input {@link Iterable}
     * @param 
     *            The type of the {@link Iterable}
     * @param name
     *            The name of the input {@link Iterable}
     * @param separator
     *            The separator to use between each item in the input {@link Iterable}
     * @return A {@link String} representation of the {@link Iterable}
     */
    public static  String toString(final Iterable input, final String name,
            final String separator)
    {
        final StringBuilder builder = new StringBuilder();
        builder.append("[");
        builder.append(name);
        builder.append(": ");
        long index = 0;
        for (final T type : input)
        {
            if (index > 0)
            {
                builder.append(separator);
            }
            builder.append(type.toString());
            index++;
        }
        builder.append("]");
        return builder.toString();
    }

    /**
     * Translate an {@link Iterable} of type I to an {@link Iterable} of type O.
     *
     * @param input
     *            The input {@link Iterable}
     * @param converter
     *            The converter from I to O
     * @param 
     *            The type of the input {@link Iterable}
     * @param 
     *            The type of the output {@link Iterable}
     * @return The {@link Iterable} of O
     */
    public static  Iterable translate(final Iterable input,
            final Function converter)
    {
        return filterTranslate(input, converter, item -> true);
    }

    /**
     * Translate an {@link Iterable} of type I to an {@link Iterable} of type O.
     *
     * @param input
     *            The input {@link Iterable}
     * @param converter
     *            The converter from I to O
     * @param 
     *            The type of the input {@link Iterable}
     * @param 
     *            The type of the output {@link Iterable}
     * @param matcher
     *            A {@link Predicate} on O that filters only the items to match
     * @return The {@link Iterable} of O
     */
    public static  Iterable translateFilter(final Iterable input,
            final Function converter, final Predicate matcher)
    {
        return Iterables.filter(Iterables.translate(input, converter), matcher);
    }

    /**
     * Translate an {@link Iterable} of type I to an {@link Iterable} of O where each converter
     * yields multiple O for each I.
     *
     * @param iterableIn
     *            The input {@link Iterable}
     * @param converter
     *            The converter from I to multiple O
     * @param 
     *            The type of the input {@link Iterable}
     * @param 
     *            The type of the output {@link Iterable}
     * @return The {@link Iterable} of O
     */
    public static  Iterable translateMulti(final Iterable iterableIn,
            final Function> converter)
    {
        return new MultiIterable<>(Iterables.translate(iterableIn, converter));
    }

    /**
     * Truncate an {@link Iterable}.
     *
     * @param types
     *            The {@link Iterable} to translate
     * @param startIndex
     *            The index before which to truncate from the start
     * @param indexFromEnd
     *            The index after which to truncate from the end
     * @param 
     *            The type of the {@link Iterable}
     * @return The truncated {@link Iterable}
     */
    public static  Iterable truncate(final Iterable types, final int startIndex,
            final int indexFromEnd)
    {
        return new SubIterable<>(types, startIndex, indexFromEnd);
    }

    private Iterables()
    {
    }
}