/*
 * Copyright 2015, 2024 StreamEx contributors
 *
 * Licensed 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 one.util.streamex;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.*;
import java.util.regex.Pattern;
import java.util.stream.BaseStream;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static one.util.streamex.Internals.*;

/**
 * A {@link Stream} implementation with additional functionality.
 * 
 * <p>
 * While {@code StreamEx} implements {@code Iterable}, it is not a
 * general-purpose {@code Iterable} as it supports only a single
 * {@code Iterator}; invoking the {@link #iterator iterator} method to obtain a
 * second or subsequent iterator throws {@code IllegalStateException}.
 *
 * @author Tagir Valeev
 *
 * @param <T> the type of the stream elements
 */
@NullMarked
public final class StreamEx<T extends @Nullable Object> extends AbstractStreamEx<T, StreamEx<T>> {

    StreamEx(Stream<? extends T> stream, StreamContext context) {
        super(stream, context);
    }

    StreamEx(Spliterator<? extends T> spliterator, StreamContext context) {
        super(spliterator, context);
    }

    @Override
    StreamEx<T> supply(Stream<T> stream) {
        return new StreamEx<>(stream, context);
    }

    @Override
    StreamEx<T> supply(Spliterator<T> spliterator) {
        return new StreamEx<>(spliterator, context);
    }

    private <R extends @Nullable Object> StreamEx<R> collapseInternal(
            BiPredicate<? super T, ? super T> collapsible, Function<T, R> mapper,
            BiFunction<R, T, R> accumulator, BinaryOperator<R> combiner) {
        CollapseSpliterator<T, R> spliterator = new CollapseSpliterator<>(collapsible, mapper, accumulator, combiner,
                spliterator());
        return new StreamEx<>(spliterator, context);
    }

    @SuppressWarnings("EmptyMethod")
    @Override
    public StreamEx<@NonNull T> nonNull() {
        // this overload is useful to make Eclipse external Null annotations working
        return super.nonNull();
    }

    /**
     * Returns a stream consisting of the elements of this stream which are
     * instances of a given class.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <TT> a type of instances to select.
     * @param clazz a class which instances should be selected
     * @return the new stream
     */
    @SuppressWarnings("unchecked")
    public <TT> StreamEx<TT> select(Class<TT> clazz) {
        return (StreamEx<TT>) filter(clazz::isInstance);
    }
    
    /**
     * Returns a stream consisting of the elements of this stream for which the
     * supplied mapper function returns the given value.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     * 
     * <p>
     * This method behaves like
     * {@code filter(t -> Objects.equals(value, mapper.apply(t)))}.
     *
     * @param <K> type of the value returned by mapper function.
     * @param mapper a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function which is applied to the stream element and its returned
     *        value is compared with the supplied value.
     * @param value a value the mapper function must return to pass the filter.
     * @return the new stream
     * @since 0.6.4
     * @see #filter(Predicate)
     */
    public <K extends @Nullable Object> StreamEx<T> filterBy(Function<? super T, ? extends K> mapper, K value) {
        return value == null ? filter(t -> mapper.apply(t) == null) : filter(t -> value.equals(mapper.apply(t)));
    }

    /**
     * Returns a stream consisting of the elements of this stream except those
     * for which the supplied mapper function returns the given value.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     * 
     * <p>
     * This method behaves like
     * {@code filter(t -> !Objects.equals(value, mapper.apply(t)))}.
     *
     * @param <K> type of the value returned by mapper function.
     * @param mapper a
     *        <a href="package-summary.html#NonInterference">non-interfering
     *        </a>, <a href="package-summary.html#Statelessness">stateless</a>
     *        function which is applied to the stream element and its returned
     *        value is compared with the supplied value.
     * @param value a value the mapper function must not return to pass the
     *        filter.
     * @return the new stream
     * @since 0.6.4
     * @see #remove(Predicate)
     */
    public <K extends @Nullable Object> StreamEx<T> removeBy(Function<? super T, ? extends K> mapper, K value) {
        return value == null ? filter(t -> mapper.apply(t) != null) : filter(t -> !value.equals(mapper.apply(t)));
    }

    /**
     * Returns a stream consisting of all elements from this stream until the
     * null element is found. The null element itself is not included in
     * the resulting stream.
     *
     * <p>
     * This is a short-circuiting stateful operation. It can be either <a
     * href="package-summary.html#StreamOps">intermediate or
     * quasi-intermediate</a>.
     *
     * <p>
     * While this operation is quite cheap for sequential stream, it can be
     * quite expensive on parallel pipelines. Using unordered source or making it
     * explicitly unordered with {@link #unordered()} call may improve the parallel
     * processing performance if semantics permit.
     *
     * @return the new stream.
     * @since 0.9.0
     * @see #takeWhile(Predicate)
     * @see #dropWhile(Predicate)
     */
    public StreamEx<@NonNull T> takeUntilNull() {
        return super.takeWhile(Objects::nonNull);
    }

    /**
     * Returns an {@link EntryStream} consisting of the {@link Entry} objects
     * which keys are elements of this stream and values are results of applying
     * the given function to the elements of this stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <V> The {@code Entry} value type
     * @param valueMapper a non-interfering, stateless function to apply to each
     *        element
     * @return the new stream
     */
    public <V extends @Nullable Object> EntryStream<T, V> mapToEntry(Function<? super T, ? extends V> valueMapper) {
        return new EntryStream<>(stream().map(e -> new SimpleImmutableEntry<>(e, valueMapper.apply(e))), context);
    }

    /**
     * Returns an {@link EntryStream} consisting of the {@link Entry} objects
     * which keys and values are results of applying the given functions to the
     * elements of this stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     *
     * @param <K> The {@code Entry} key type
     * @param <V> The {@code Entry} value type
     * @param keyMapper a non-interfering, stateless function to apply to each
     *        element
     * @param valueMapper a non-interfering, stateless function to apply to each
     *        element
     * @return the new stream
     */
    public <K extends @Nullable Object, V extends @Nullable Object> EntryStream<K, V> mapToEntry(
            Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends V> valueMapper) {
        return new EntryStream<>(stream()
                .map(e -> new SimpleImmutableEntry<>(keyMapper.apply(e), valueMapper.apply(e))), context);
    }

    /**
     * Returns a stream where the first element is replaced with the result
     * of applying the given function while the other elements are left intact.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a> with <a href="package-summary.html#TSO">tail-stream
     * optimization</a>.
     *
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to the first element
     * @return the new stream
     * @since 0.4.1
     */
    public StreamEx<T> mapFirst(Function<? super T, ? extends T> mapper) {
        return supply(new PairSpliterator.PSOfRef<>(mapper, spliterator(), true));
    }

    /**
     * Returns a stream where the first element is transformed using
     * {@code firstMapper} function and other elements are transformed using
     * {@code notFirstMapper} function.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream element
     * @param firstMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to the first element
     * @param notFirstMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to all elements except the first one.
     * @return the new stream
     * @since 0.6.0
     * @see #mapFirst(Function)
     */
    public <R extends @Nullable Object> StreamEx<R> mapFirstOrElse(
            Function<? super T, ? extends R> firstMapper,
            Function<? super T, ? extends R> notFirstMapper) {
        return new StreamEx<>(new PairSpliterator.PSOfRef<>(firstMapper, notFirstMapper, spliterator(), true), context);
    }

    /**
     * Returns a stream where the last element is replaced with the result
     * of applying the given function while the other elements are left intact.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     * 
     * <p>
     * The mapper function is called at most once. It could be not called at all
     * if the stream is empty or there is a short-circuiting operation downstream.
     *
     * @param mapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to the last element
     * @return the new stream
     * @since 0.4.1
     */
    public StreamEx<T> mapLast(Function<? super T, ? extends T> mapper) {
        return supply(new PairSpliterator.PSOfRef<>(mapper, spliterator(), false));
    }

    /**
     * Returns a stream where the last element is transformed using
     * {@code lastMapper} function and other elements are transformed using
     * {@code notLastMapper} function.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">quasi-intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream element
     * @param notLastMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to all elements except the last one.
     * @param lastMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        function to apply to the last element
     * @return the new stream
     * @since 0.6.0
     * @see #mapFirst(Function)
     */
    public <R extends @Nullable Object> StreamEx<R> mapLastOrElse(
            Function<? super T, ? extends R> notLastMapper,
            Function<? super T, ? extends R> lastMapper) {
        return new StreamEx<>(new PairSpliterator.PSOfRef<>(lastMapper, notLastMapper, spliterator(), false), context);
    }

    /**
     * Returns a stream consisting of the elements of this stream, additionally
     * performing the provided action on the first stream element when it's
     * consumed from the resulting stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The action is called at most once. For parallel stream pipelines, it's
     * not guaranteed in which thread it will be executed, so if it modifies
     * the shared state, it is responsible for providing the required
     * synchronization.
     *
     * <p>
     * Note that the action might not be called at all if the first element is
     * not consumed from the input (for example, if there's a short-circuiting
     * operation downstream that stopped the stream before the first element).
     * 
     * <p>
     * This method exists mainly to support debugging.
     *
     * @param action a <a href="package-summary.html#NonInterference">
     *        non-interfering</a> action to perform on the first stream element
     *        as it is consumed from the stream
     * @return the new stream
     * @since 0.6.0
     */
    public StreamEx<T> peekFirst(Consumer<? super T> action) {
        return mapFirst(x -> {
            action.accept(x);
            return x;
        });
    }

    /**
     * Returns a stream consisting of the elements of this stream, additionally
     * performing the provided action on the last stream element when it's
     * consumed from the resulting stream.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The action is called at most once. For parallel stream pipelines, it's
     * not guaranteed in which thread it will be executed, so if it modifies
     * the shared state, it is responsible for providing the required
     * synchronization.
     * 
     * <p>
     * Note that the action might not be called at all if the last element is
     * not consumed from the input (for example, if there's a short-circuiting
     * operation downstream).
     * 
     * <p>
     * This method exists mainly to support debugging.
     *
     * @param action a <a href="package-summary.html#NonInterference">
     *        non-interfering</a> action to perform on the first stream element
     *        as it is consumed from the stream
     * @return the new stream
     * @since 0.6.0
     */
    public StreamEx<T> peekLast(Consumer<? super T> action) {
        return mapLast(x -> {
            action.accept(x);
            return x;
        });
    }

    /**
     * Creates a new {@code EntryStream} populated from entries of maps produced
     * by supplied mapper function which is applied to the every element of this
     * stream.
     * 
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     * 
     * @param <K> the type of {@code Map} keys.
     * @param <V> the type of {@code Map} values.
     * @param mapper a non-interfering, stateless function to apply to each
     *        element which produces a {@link Map} of the entries corresponding
     *        to the single element of the current stream. The mapper function
     *        may return null or empty {@code Map} if no mapping should
     *        correspond to some element.
     * @return the new {@code EntryStream}
     */
    public <K extends @Nullable Object, V extends @Nullable Object> EntryStream<K, V> flatMapToEntry(
            Function<? super T, ? extends @Nullable Map<K, V>> mapper) {
        return new EntryStream<>(stream().flatMap(e -> {
            Map<K, V> s = mapper.apply(e);
            return s == null ? null : s.entrySet().stream();
        }), context);
    }

    /**
     *
     * Returns an {@link EntryStream} consisting of the {@link Entry} objects
     * which keys are elements of this stream and values are results of applying
     * the given partial function to the elements of this stream
     * removing the elements to which the function is not applicable.
     *
     * <p>
     * If the mapping function returns {@link Optional#empty()}, the original
     * value will be removed from the resulting stream. The mapping function
     * may not return null.
     *
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * <p>
     * The {@code mapToEntryPartial()} operation has the effect of applying a
     * one-to-zero-or-one transformation to the elements of the stream, and then
     * flattening the resulting elements into a new stream.
     *
     * @param <V> The {@code Entry} value type
     * @param valueMapper a <a
     *        href="package-summary.html#NonInterference">non-interfering </a>,
     *        <a href="package-summary.html#Statelessness">stateless</a>
     *        partial function to apply to each element which returns a present optional
     *        if it's applicable, or an empty optional otherwise
     * @return the new stream
     * @since 0.8.4
     *
     * @see AbstractStreamEx#mapPartial(Function)
     */
    public <V> EntryStream<T, ? extends V> mapToEntryPartial(Function<? super T, ? extends Optional<? extends V>> valueMapper){
        return new EntryStream<>(stream().map(e -> {
            Optional<? extends V> s = valueMapper.apply(e);
            return s.isPresent() ? new SimpleImmutableEntry<>(e, s.get()) : null;
        }).filter(Objects::nonNull), context);
    }

    /**
     * Performs a cross-product of the current stream with a specified array of
     * elements. As a result the {@link EntryStream} is created whose keys are
     * elements of current stream and values are elements of the specified
     * array.
     * 
     * <p>
     * The resulting stream contains all the possible combinations of keys and
     * values.
     * 
     * <p>
     * For example, writing {@code StreamEx.of(1, 2).cross("a", "b")}, you will
     * have an ordered stream of the following entries: {@code [1, "a"], [1, "b"],
     * [2, "a"], [2, "b"]}. 
     * 
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     * 
     * @param <V> the type of array elements
     * @param other the array to perform a cross-product with
     * @return the new {@code EntryStream}
     * @throws NullPointerException if other is null
     * @since 0.2.3
     */
    @SuppressWarnings("unchecked")
    public <V extends @Nullable Object> EntryStream<T, V> cross(V... other) {
        if (other.length == 0)
            return new EntryStream<>(Spliterators.emptySpliterator(), context);
        if (other.length == 1)
            return mapToEntry(e -> other[0]);
        return cross(t -> of(other));
    }

    /**
     * Performs a cross-product of current stream with specified
     * {@link Collection} of elements. As a result the {@link EntryStream} is
     * created whose keys are elements of current stream and values are elements
     * of the specified collection.
     * 
     * <p>
     * The resulting stream contains all the possible combinations of keys and
     * values.
     * 
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     * 
     * @param <V> the type of collection elements
     * @param other the collection to perform a cross-product with
     * @return the new {@code EntryStream}
     * @throws NullPointerException if other is null
     * @since 0.2.3
     */
    public <V extends @Nullable Object> EntryStream<T, V> cross(Collection<? extends V> other) {
        if (other.isEmpty())
            return new EntryStream<>(Spliterators.emptySpliterator(), context);
        return cross(t -> of(other));
    }

    /**
     * Creates a new {@code EntryStream} whose keys are elements of current
     * stream and corresponding values are supplied by given function. Each
     * mapped stream is {@link java.util.stream.BaseStream#close() closed} after
     * its contents have been placed into this stream. (If a mapped stream is
     * {@code null} an empty stream is used, instead.)
     * 
     * <p>
     * This is an <a href="package-summary.html#StreamOps">intermediate</a>
     * operation.
     * 
     * @param <V> the type of values.
     * @param mapper a non-interfering, stateless function to apply to each
     *        element which produces a stream of the values corresponding to the
     *        single element of the current stream.
     * @return the new {@code EntryStream}
     * @since 0.2.3
     */
    public <V extends @Nullable Object> EntryStream<T, V> cross(Function<? super T, ? extends Stream<? extends V>> mapper) {
        return new EntryStream<>(stream().flatMap(a -> EntryStream.withKey(a, mapper.apply(a))), context);
    }

    /**
     * Returns a {@code Map} whose keys are the values resulting from applying
     * the classification function to the input elements, and whose
     * corresponding values are {@code List}s containing the input elements
     * which map to the associated key under the classification function.
     *
     * <p>
     * There are no guarantees on the type, mutability, or serializability of the
     * {@code Map} or {@code List} objects returned.
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
     * 
     * @param <K> the type of the keys
     * @param classifier the classifier function mapping input elements to keys
     * @return a {@code Map} containing the results of the group-by operation
     *
     * @see #groupingBy(Function, Collector)
     * @see Collectors#groupingBy(Function)
     * @see Collectors#groupingByConcurrent(Function)
     */
    public <K extends @Nullable Object> Map<K, List<T>> groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, Collectors.toList());
    }

    /**
     * Returns a {@code Map} whose keys are the values resulting from applying
     * the classification function to the input elements, and whose
     * corresponding values are the result of reduction of the input elements
     * that map to the associated key under the classification function.
     *
     * <p>
     * There are no guarantees on the type, mutability, or serializability of the
     * {@code Map} objects returned.
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
     * 
     * @param <K> the type of the keys
     * @param <D> the result type of the downstream reduction
     * @param classifier the classifier function mapping input elements to keys
     * @param downstream a {@code Collector} implementing the downstream
     *        reduction
     * @return a {@code Map} containing the results of the group-by operation
     *
     * @see #groupingBy(Function)
     * @see Collectors#groupingBy(Function, Collector)
     * @see Collectors#groupingByConcurrent(Function, Collector)
     */
    public <K extends @Nullable Object, D extends @Nullable Object> Map<K, D> groupingBy(
            Function<? super T, ? extends K> classifier,
            Collector<? super T, ? extends @Nullable Object, D> downstream) {
        if (isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED))
            return rawCollect(Collectors.groupingByConcurrent(classifier, downstream));
        return rawCollect(Collectors.groupingBy(classifier, downstream));
    }

    /**
     * Returns a {@code Map} whose keys are the values resulting from applying
     * the classification function to the input elements, and whose
     * corresponding values are the result of reduction of the input elements
     * which map to the associated key under the classification function.
     *
     * <p>
     * The {@code Map} will be created using the provided factory function.
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
     * 
     * @param <K> the type of the keys
     * @param <D> the result type of the downstream reduction
     * @param <M> the type of the resulting {@code Map}
     * @param classifier the classifier function mapping input elements to keys
     * @param mapFactory a function which, when called, produces a new empty
     *        {@code Map} of the desired type
     * @param downstream a {@code Collector} implementing the downstream
     *        reduction
     * @return a {@code Map} containing the results of the group-by operation
     *
     * @see #groupingBy(Function)
     * @see Collectors#groupingBy(Function, Supplier, Collector)
     * @see Collectors#groupingByConcurrent(Function, Supplier, Collector)
     */
    @SuppressWarnings("unchecked")
    public <K extends @Nullable Object, D extends @Nullable Object, M extends Map<K, D>> M groupingBy(
            Function<? super T, ? extends K> classifier,
            Supplier<M> mapFactory, Collector<? super T, ?, D> downstream) {
        if (isParallel() && downstream.characteristics().contains(Characteristics.UNORDERED)
            && mapFactory.get() instanceof ConcurrentMap)
            return (M) rawCollect(Collectors.groupingByConcurrent(classifier,
                (Supplier<ConcurrentMap<K, D>>) mapFactory, downstream));
        return rawCollect(Collectors.groupingBy(classifier, mapFactory, downstream));
    }

    /**
     * Returns a {@code Map} whose keys are the values resulting from applying
     * the classification function to the input elements, and whose
     * corresponding values are the collections of the input elements which map
     * to the associated key under the classification function.
     *
     * <p>
     * There are no guarantees on the type, mutability or serializability of the
     * {@code Map} objects returned.
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
     * 
     * @param <K> the type of the keys
     * @param <C> the type of the collection used in resulting {@code Map}
     *        values
     * @param classifier the classifier function mapping input elements to keys
     * @param collectionFactory a function which returns a new empty
     *        {@code Collection} which will be used to store the stream
     *        elements.
     * @return a {@code Map} containing the results of the group-by operation
     *
     * @see #groupingBy(Function, Collector)
     * @see Collectors#groupingBy(Function, Collector)
     * @see Collectors#groupingByConcurrent(Function, Collector)
     * @since 0.2.2
     */
    public <K extends @Nullable Object, C extends Collection<T>> Map<K, C> groupingTo(
            Function<? super T, ? extends K> classifier,
            Supplier<C> collectionFactory) {
        return groupingBy(classifier, Collectors.toCollection(collectionFactory));
    }

    /**
     * Returns a {@code Map} whose keys are the values resulting from applying
     * the classification function to the input elements, and whose
     * corresponding values are the collections of the input elements which map
     * to the associated key under the classification function.
     *
     * <p>
     * The {@code Map} will be created using the provided factory function.
     * 
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
     * 
     * @param <K> the type of the keys
     * @param <C> the type of the collection used in resulting {@code Map}
     *        values
     * @param <M> the type of the resulting {@code Map}
     * @param classifier the classifier function mapping input elements to keys
     * @param mapFactory a function which, when called, produces a new empty
     *        {@code Map} of the desired type
     * @param collectionFactory a function which returns a new empty
     *        {@code Collection} which will be used to store the stream
     *        elements.
     * @return a {@code Map} containing the results of the group-by operation
     *
     * @see #groupingTo(Function, Supplier)
     * @see Collectors#groupingBy(Function, Supplier, Collector)
     * @see Collectors#groupingByConcurrent(Function, Supplier, Collector)
     * @since 0.2.2
     */
    public <K extends @Nullable Object, C extends Collection<T>, M extends Map<K, C>> M groupingTo(
            Function<? super T, ? extends K> classifier,
            Supplier<M> mapFactory, Supplier<C> collectionFactory) {
        return groupingBy(classifier, mapFactory, Collectors.toCollection(collectionFactory));
    }

    /**
     * Returns a {@code Map<Boolean, List<T>>} which contains two partitions of
     * the input elements according to a {@code Predicate}.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
     *
     * <p>
     * There are no guarantees on the type, mutability, serializability, or
     * thread-safety of the {@code Map} returned.
     *
     * @param predicate a predicate used for classifying input elements
     * @return a {@code Map<Boolean, List<T>>} which {@link Boolean#TRUE} key is
     *         mapped to the list of the stream elements for which predicate is
     *         true and {@link Boolean#FALSE} key is mapped to the list of all
     *         other stream elements.
     *
     * @see #partitioningBy(Predicate, Collector)
     * @see Collectors#partitioningBy(Predicate)
     * @since 0.2.2
     */
    public Map<Boolean, List<T>> partitioningBy(Predicate<? super T> predicate) {
        return collect(Collectors.partitioningBy(predicate));
    }

    /**
     * Returns a {@code Map<Boolean, D>} which contains two partitions of the
     * input elements according to a {@code Predicate}, which are reduced
     * according to the supplied {@code Collector}.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation. The operation may short-circuit if the downstream collector is
     * <a
     * href="package-summary.html#ShortCircuitReduction">short-circuiting</a>.
     *
     * <p>
     * There are no guarantees on the type, mutability, serializability, or
     * thread-safety of the {@code Map} returned.
     *
     * @param <D> the result type of the downstream reduction
     * @param predicate a predicate used for classifying input elements
     * @param downstream a {@code Collector} implementing the downstream
     *        reduction
     * @return a {@code Map<Boolean, List<T>>} which {@link Boolean#TRUE} key is
     *         mapped to the result of downstream {@code Collector} collecting
     *         the stream elements for which predicate is true and
     *         {@link Boolean#FALSE} key is mapped to the result of downstream
     *         {@code Collector} collecting the other stream elements.
     *
     * @see #partitioningBy(Predicate)
     * @see Collectors#partitioningBy(Predicate, Collector)
     * @since 0.2.2
     */
    public <D extends @Nullable Object> Map<Boolean, D> partitioningBy(
            Predicate<? super T> predicate, Collector<? super T, ?, D> downstream) {
        return collect(MoreCollectors.partitioningBy(predicate, downstream));
    }

    /**
     * Returns a {@code Map<Boolean, C>} which contains two partitions of the
     * input elements according to a {@code Predicate}.
     *
     * <p>
     * This is a <a href="package-summary.html#StreamOps">terminal</a>
     * operation.
