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

org.neo4j.internal.helpers.collection.Iterators Maven / Gradle / Ivy

There is a newer version: 5.24.0
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.internal.helpers.collection;

import static java.util.Collections.emptyIterator;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.eclipse.collections.api.iterator.LongIterator;
import org.neo4j.collection.RawIterator;
import org.neo4j.function.Predicates;
import org.neo4j.function.ThrowingFunction;
import org.neo4j.graphdb.Resource;
import org.neo4j.graphdb.ResourceIterable;
import org.neo4j.graphdb.ResourceIterator;

/**
 * Contains common functionality regarding {@link Iterator}s and
 * {@link Iterable}s.
 */
public final class Iterators {
    private Iterators() {
        throw new AssertionError("no instance");
    }

    /**
     * Returns the given iterator's first element or {@code null} if no
     * element found.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @return the first element in the {@code iterator}, or {@code null} if no
     * element found.
     */
    public static  T firstOrNull(Iterator iterator) {
        try {
            return iterator.hasNext() ? iterator.next() : null;
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Returns the given iterator's first element or {@param defaultValue} if no
     * element found.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @param defaultValue the default value to return.
     * @return the first element in the {@code iterator}, or {@code null} if no
     * element found.
     */
    public static  T firstOrDefault(Iterator iterator, T defaultValue) {
        try {
            return iterator.hasNext() ? iterator.next() : defaultValue;
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Returns the given iterator's first element. If no element is found a
     * {@link NoSuchElementException} is thrown.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @return the first element in the {@code iterator}.
     * @throws NoSuchElementException if no element found.
     */
    public static  T first(Iterator iterator) {
        return assertNotNull(iterator, firstOrNull(iterator));
    }

    /**
     * Returns the given iterator's last element or {@code null} if no
     * element found.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @return the last element in the {@code iterator}, or {@code null} if no
     * element found.
     */
    static  T lastOrNull(Iterator iterator) {
        try {
            T result = null;
            while (iterator.hasNext()) {
                result = iterator.next();
            }
            return result;
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Returns the given iterator's last element. If no element is found a
     * {@link NoSuchElementException} is thrown.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @return the last element in the {@code iterator}.
     * @throws NoSuchElementException if no element found.
     */
    public static  T last(Iterator iterator) {
        return assertNotNull(iterator, lastOrNull(iterator));
    }

    /**
     * Returns the given iterator's single element or {@code null} if no
     * element found. If there is more than one element in the iterator a
     * {@link NoSuchElementException} will be thrown.
     *
     * If the {@code iterator} implements {@link Resource} it will be {@link Resource#close() closed}
     * in a {@code finally} block after the single item has been retrieved, or failed to be retrieved.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @return the single element in {@code iterator}, or {@code null} if no
     * element found.
     * @throws NoSuchElementException if more than one element was found.
     */
    public static  T singleOrNull(Iterator iterator) {
        try {
            return single(iterator, null);
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Returns the given iterator's single element. If there are no elements
     * or more than one element in the iterator a {@link NoSuchElementException}
     * will be thrown.
     *
     * If the {@code iterator} implements {@link Resource} it will be {@link Resource#close() closed}
     * in a {@code finally} block after the single item has been retrieved, or failed to be retrieved.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @return the single element in the {@code iterator}.
     * @throws NoSuchElementException if there isn't exactly one element.
     */
    public static  T single(Iterator iterator) {
        return assertNotNull(iterator, singleOrNull(iterator));
    }

    /**
     * Returns the iterator's n:th item from the end of the iteration.
     * If the iterator has got less than n-1 items in it
     * {@link NoSuchElementException} is thrown.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @param n the n:th item from the end to get.
     * @return the iterator's n:th item from the end of the iteration.
     * @throws NoSuchElementException if the iterator contains less than n-1 items.
     */
    static  T fromEnd(Iterator iterator, int n) {
        return assertNotNull(iterator, fromEndOrNull(iterator, n));
    }

    /**
     * Returns the iterator's n:th item from the end of the iteration.
     * If the iterator has got less than n-1 items in it {@code null} is returned.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @param n the n:th item from the end to get.
     * @return the iterator's n:th item from the end of the iteration,
     * or {@code null} if the iterator doesn't contain that many items.
     */
    private static  T fromEndOrNull(Iterator iterator, int n) {
        try {
            Deque trail = new ArrayDeque<>(n);
            while (iterator.hasNext()) {
                if (trail.size() > n) {
                    trail.removeLast();
                }
                trail.addFirst(iterator.next());
            }
            return trail.size() == n + 1 ? trail.getLast() : null;
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Iterates over the full iterators, and checks equality for each item in them. Note that this
     * will consume the iterators.
     *
     * @param first the first iterator
     * @param other the other iterator
     * @return {@code true} if all items are equal; otherwise
     */
    public static boolean iteratorsEqual(Iterator first, Iterator other) {
        try {
            while (first.hasNext() && other.hasNext()) {
                if (!Objects.equals(first.next(), other.next())) {
                    return false;
                }
            }
            return first.hasNext() == other.hasNext();
        } finally {
            tryCloseResource(first);
            tryCloseResource(other);
        }
    }

    private static  T assertNotNull(Iterator iterator, T result) {
        if (result == null) {
            throw new NoSuchElementException("No element found in " + iterator);
        }
        return result;
    }

    /**
     * Returns the given iterator's single element or {@code itemIfNone} if no
     * element found. If there is more than one element in the iterator a
     * {@link NoSuchElementException} will be thrown.
     *
     * If the {@code iterator} implements {@link Resource} it will be {@link Resource#close() closed}
     * in a {@code finally} block after the single item has been retrieved, or failed to be retrieved.
     *
     * @param  the type of elements in {@code iterator}.
     * @param iterator the {@link Iterator} to get elements from.
     * @param itemIfNone item to use if none is found
     * @return the single element in {@code iterator}, or {@code itemIfNone} if no
     * element found.
     * @throws NoSuchElementException if more than one element was found.
     */
    public static  T single(Iterator iterator, T itemIfNone) {
        try {
            T result = iterator.hasNext() ? iterator.next() : itemIfNone;
            if (iterator.hasNext()) {
                throw new NoSuchElementException("More than one element in " + iterator + ". First element is '"
                        + result + "' and the second element is '" + iterator.next() + "'");
            }
            return result;
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Adds all the items in {@code iterator} to {@code collection}.
     * @param  the type of {@link Collection} to add to items to.
     * @param  the type of items in the collection and iterator.
     * @param iterator the {@link Iterator} to grab the items from.
     * @param collection the {@link Collection} to add the items to.
     * @return the {@code collection} which was passed in, now filled
     * with the items from {@code iterator}.
     */
    public static , T> C addToCollection(Iterator iterator, C collection) {
        try {
            while (iterator.hasNext()) {
                collection.add(iterator.next());
            }
            return collection;
        } finally {
            tryCloseResource(iterator);
        }
    }

    /**
     * Adds all the items in {@code iterator} to {@code collection}.
     * @param  the type of {@link Collection} to add to items to.
     * @param  the type of items in the collection and iterator.
     * @param iterator the {@link Iterator} to grab the items from.
     * @param collection the {@link Collection} to add the items to.
     * @return the {@code collection} which was passed in, now filled
     * with the items from {@code iterator}.
     */
    private static , T> C addToCollectionUnique(Iterator iterator, C collection) {
        try {
            while (iterator.hasNext()) {
                addUnique(collection, iterator.next());
            }
            return collection;
        } finally {
            tryCloseResource(iterator);
        }
    }

    private static > void addUnique(C collection, T item) {
        if (!collection.add(item)) {
            throw new IllegalStateException("Encountered an already added item:" + item
                    + " when adding items uniquely to a collection:" + collection);
        }
    }

    /**
     * Adds all the items in {@code iterator} to {@code collection}.
     * @param  the type of {@link Collection} to add to items to.
     * @param  the type of items in the collection and iterator.
     * @param iterable the {@link Iterator} to grab the items from.
     * @param collection the {@link Collection} to add the items to.
     * @return the {@code collection} which was passed in, now filled
     * with the items from {@code iterator}.
     */
    static , T> C addToCollectionUnique(Iterable iterable, C collection) {
        return addToCollectionUnique(iterable.iterator(), collection);
    }

    /**
     * Convenience method for looping over an {@link Iterator}. Converts the
     * {@link Iterator} to an {@link Iterable} by wrapping it in an
     * {@link Iterable} that returns the {@link Iterator}. It breaks the
     * contract of {@link Iterable} in that it returns the supplied iterator
     * instance for each call to {@code iterator()} on the returned
     * {@link Iterable} instance. This method exists to make it easy to use an
     * {@link Iterator} in a for-loop.
     *
     * @param  the type of items in the iterator.
     * @param iterator the iterator to expose as an {@link Iterable}.
     * @return the supplied iterator posing as an {@link Iterable}.
     */
    public static  Iterable loop(final Iterator iterator) {
        return () -> iterator;
    }

    /**
     * Counts the number of items in the {@code iterator} by looping
     * through it.
     *
     * If the {@code iterator} implements {@link Resource} it will be {@link Resource#close() closed}
     * in a {@code finally} block after the items have been counted.
     *
     * @param  the type of items in the iterator.
     * @param iterator the {@link Iterator} to count items in.
     * @return the number of items found in {@code iterator}.
     */
    public static  long count(Iterator iterator) {
        return count(iterator, Predicates.alwaysTrue());
    }

    /**
     * Counts the number of filtered in the {@code iterator} by looping
     * through it.
     *
     * If the {@code iterator} implements {@link Resource} it will be {@link Resource#close() closed}
     * in a {@code finally} block after the items have been counted.
     *
     * @param  the type of items in the iterator.
     * @param iterator the {@link Iterator} to count items in.
     * @param filter the filter to test items against
     * @return the number of filtered items found in {@code iterator}.
     */
    public static  long count(Iterator iterator, Predicate filter) {
        try {
            long result = 0;
            while (iterator.hasNext()) {
                if (filter.test(iterator.next())) {
                    result++;
                }
            }
            return result;
        } finally {
            tryCloseResource(iterator);
        }
    }

    public static  Collection asCollection(Iterator iterable) {
        return addToCollection(iterable, new ArrayList<>());
    }

    public static  List asList(Iterator iterator) {
        return addToCollection(iterator, new ArrayList<>());
    }

    public static  List asList(RawIterator iterator) throws EX {
        try {
            List out = new ArrayList<>();
            while (iterator.hasNext()) {
                out.add(iterator.next());
            }
            return out;
        } finally {
            tryCloseResource(iterator);
        }
    }

    public static  Set asSet(Iterator iterator) {
        return addToCollection(iterator, new HashSet<>());
    }

    /**
     * Creates a {@link Set} from an array of items.an
     *
     * @param items the items to add to the set.
     * @param  the type of the items
     * @return the {@link Set} containing the items.
     */
    @SafeVarargs
    public static  Set asSet(T... items) {
        return new HashSet<>(Arrays.asList(items));
    }

    /**
     * Alias for asSet()
     *
     * @param items the items to add to the set.
     * @param  the type of the items
     * @return the {@link Set} containing the items.
     */
    @SafeVarargs
    public static  Set set(T... items) {
        return asSet(items);
    }

    /**
     * Creates a {@link Set} from an array of items.
     *
     * @param items the items to add to the set.
     * @param  the type of the items
     * @return the {@link Set} containing the items.
     */
    @SafeVarargs
    public static  Set asUniqueSet(T... items) {
        Set set = new HashSet<>();
        for (T item : items) {
            addUnique(set, item);
        }
        return set;
    }

    /**
     * Creates a {@link Set} from an array of items.
     *
     * @param iterator the items to add to the set.
     * @param  the type of the items
     * @return the {@link Set} containing the items.
     */
    public static  Set asUniqueSet(Iterator iterator) {
        try {
            Set set = new HashSet<>();
            while (iterator.hasNext()) {
                addUnique(set, iterator.next());
            }
            return set;
        } finally {
            tryCloseResource(iterator);
        }
    }

    public static  SortedSet asSortedSet(Comparator comparator, T... items) {
        SortedSet set = new TreeSet<>(comparator);
        Collections.addAll(set, items);
        return set;
    }

    static Iterator asIterator(final long... array) {
        return new Iterator<>() {
            private int index;

            @Override
            public boolean hasNext() {
                return index < array.length;
            }

            @Override
            public Long next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }

                return array[index++];
            }
        };
    }

    public static Iterator asIterator(final int... array) {
        return new Iterator<>() {
            private int index;

            @Override
            public boolean hasNext() {
                return index < array.length;
            }

            @Override
            public Integer next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }

                return array[index++];
            }
        };
    }

    @SafeVarargs
    private static  Iterator asIterator(final int maxItems, final T... array) {
        return new Iterator<>() {
            int index;

            @Override
            public boolean hasNext() {
                return index < maxItems;
            }

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

                return array[index++];
            }
        };
    }

    public static  Iterator iterator(final T item) {
        if (item == null) {
            return emptyIterator();
        }

        return new Iterator<>() {
            T myItem = item;

            @Override
            public boolean hasNext() {
                return myItem != null;
            }

            @Override
            public T next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                T toReturn = myItem;
                myItem = null;
                return toReturn;
            }
        };
    }

    @SafeVarargs
    public static  Iterator iterator(T... items) {
        return asIterator(items.length, items);
    }

    @SafeVarargs
    public static  Iterator iterator(int maxItems, T... items) {
        return asIterator(maxItems, items);
    }

    public static  Iterator appendTo(Iterator iterator, T... appended) {
        return new Iterator<>() {
            private int index;

            @Override
            public boolean hasNext() {
                return iterator.hasNext() || index < appended.length;
            }

            @Override
            public T next() {
                if (iterator.hasNext()) {
                    return iterator.next();
                } else if (index < appended.length) {
                    return appended[index++];
                } else {
                    tryCloseResource(iterator);
                    throw new NoSuchElementException();
                }
            }
        };
    }

    public static  Iterator prependTo(Iterator iterator, T... prepended) {
        return new Iterator<>() {
            private int index;

            @Override
            public boolean hasNext() {
                return index < prepended.length || iterator.hasNext();
            }

            @Override
            public T next() {
                if (index < prepended.length) {
                    return prepended[index++];
                } else if (iterator.hasNext()) {
                    return iterator.next();
                } else {
                    tryCloseResource(iterator);
                    throw new NoSuchElementException();
                }
            }
        };
    }

    /**
     * Produce a string of the form {@code [A, B, C, ...]} by consuming up to {@code maxItems} objects from the iterator,
     * optionally appending an ellipsis if the iterator has more than {@code maxItems} in it.
     *
     * @param iterator the iterator to format as a string.
     * @param toString the function that converts the iterator elements to strings.
     * @param maxItems the maximum number of items to include in the string.
     * @param  the type of objects in the iterator.
     * @return a string of the form {@code [A, B, C]} or {@code [A, B, C, ...]}.
     */
    public static  String toString(Iterator iterator, Function toString, int maxItems) {
        try {
            StringJoiner joiner = new StringJoiner(", ", "[", "]");
            while (iterator.hasNext() && maxItems > 0) {
                String str = toString.apply(iterator.next());
                joiner.add(str);
                maxItems--;
            }
            if (iterator.hasNext()) {
                joiner.add("...");
            }
            return joiner.toString();
        } finally {
            tryCloseResource(iterator);
        }
    }

    @SuppressWarnings("unchecked")
    public static  ResourceIterator emptyResourceIterator() {
        return (ResourceIterator) EmptyResourceIterator.EMPTY_RESOURCE_ITERATOR;
    }

    public static  ResourceIterator asResourceIterator(final Iterable iterable) {
        if (iterable instanceof ResourceIterable) {
            return ((ResourceIterable) iterable).iterator();
        }
        return asResourceIterator(iterable.iterator());
    }

    public static  ResourceIterator asResourceIterator(final Iterator iterator) {
        if (iterator instanceof ResourceIterator) {
            return (ResourceIterator) iterator;
        }
        return new WrappingResourceIterator<>(iterator);
    }

    public static  ResourceIterator resourceIterator(final Iterator iterator, final Resource resource) {
        return new PrefetchingResourceIterator<>() {
            @Override
            public void close() {
                resource.close();
            }

            @Override
            protected T fetchNextOrNull() {
                return iterator.hasNext() ? iterator.next() : null;
            }
        };
    }

    @SafeVarargs
    public static  T[] array(T... items) {
        return items;
    }

    public static  Iterator filter(Predicate specification, Iterator i) {
        return new FilterIterable.FilterIterator<>(i, specification);
    }

    public static  Iterator map(Function function, Iterator from) {
        return new MapIterable.MapIterator<>(from, function);
    }

    public static  RawIterator map(
            ThrowingFunction function, RawIterator from) {
        return new RawMapIterator<>(from, function);
    }

    public static  RawIterator asRawIterator(Iterator iter) {
        return new RawIterator<>() {
            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public T next() {
                return iter.next();
            }
        };
    }

    public static  RawIterator asRawIterator(Stream stream) {
        return asRawIterator(stream.iterator());
    }

    @SafeVarargs
    @SuppressWarnings("unchecked")
    public static  Iterator concat(Iterator... iterators) {
        return concat(Arrays.asList((Iterator[]) iterators).iterator());
    }

    public static  ResourceIterator concatResourceIterators(Iterator> iterators) {
        return new CombiningResourceIterator<>(iterators);
    }

    public static  Iterator concat(Iterator> iterators) {
        return new CombiningIterator<>(iterators);
    }

    public static  PrefetchingIterator prefetching(Iterator iterator) {
        return iterator instanceof PrefetchingIterator
                ? (PrefetchingIterator) iterator
                : new PrefetchingIterator<>() {
                    @Override
                    protected T fetchNextOrNull() {
                        return iterator.hasNext() ? iterator.next() : null;
                    }
                };
    }

    /**
     * Create a stream from the given iterator.
     * 

* Note: returned stream needs to be closed via {@link Stream#close()} if the given iterator implements * {@link Resource}. * * @param iterator the iterator to convert to stream * @param the type of elements in the given iterator * @return stream over the iterator elements * @throws NullPointerException when the given stream is {@code null} */ public static Stream stream(Iterator iterator) { return stream(iterator, 0); } /** * Create a stream from the given iterator with given characteristics. *

* Note: returned stream needs to be closed via {@link Stream#close()} if the given iterator implements * {@link Resource}. * * @param iterator the iterator to convert to stream * @param characteristics the logical OR of characteristics for the underlying {@link Spliterator} * @param the type of elements in the given iterator * @return stream over the iterator elements * @throws NullPointerException when the given iterator is {@code null} */ public static Stream stream(Iterator iterator, int characteristics) { Objects.requireNonNull(iterator); Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, characteristics); Stream stream = StreamSupport.stream(spliterator, false); if (iterator instanceof Resource resource) { return stream.onClose(resource::close); } return stream; } /** * Process each remaining item in the iterator *

* Note: the iterator will be closed via {@link Resource#close()} if the given iterator implements * {@link Resource}. * * @param iterator the iterator to process * @param consumer the consumer of each remaining item in the iterator * @param the type of elements in the given iterator */ public static void forEachRemaining(Iterator iterator, Consumer consumer) { try { iterator.forEachRemaining(consumer); } finally { tryCloseResource(iterator); } } /** * Skips the first items from the iterator * * @param iterator the iterator to skip items in * @param count number of items to skip */ public static void skip(Iterator iterator, long count) { for (; count > 0 && iterator.hasNext(); count--) { iterator.next(); } } /** * Skips the first items from the iterator * * @param iterator the iterator to skip items in * @param count number of items to skip */ public static void skip(LongIterator iterator, long count) { for (; count > 0 && iterator.hasNext(); count--) { iterator.next(); } } /** * Close the provided {@code iterator} if it implements {@link Resource}. * * @param iterator the iterator to check for closing */ public static void tryCloseResource(Iterator iterator) { if (iterator instanceof Resource closeable) { closeable.close(); } } private static void tryCloseResource(RawIterator iterator) { if (iterator instanceof Resource closeable) { closeable.close(); } } private static class EmptyResourceIterator implements ResourceIterator { private static final ResourceIterator EMPTY_RESOURCE_ITERATOR = new Iterators.EmptyResourceIterator<>(); @Override public void close() {} @Override public boolean hasNext() { return false; } @Override public E next() { throw new NoSuchElementException(); } } }