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

com.landawn.abacus.util.stream.AbstractStream Maven / Gradle / Ivy

There is a newer version: 1.10.1
Show newest version
/*
 * Copyright (C) 2016 HaiYang Li
 *
 * 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 com.landawn.abacus.util.stream;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import com.landawn.abacus.DataSet;
import com.landawn.abacus.exception.UncheckedIOException;
import com.landawn.abacus.exception.UncheckedSQLException;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.Array;
import com.landawn.abacus.util.BufferedWriter;
import com.landawn.abacus.util.Comparators;
import com.landawn.abacus.util.Fn;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.IOUtil;
import com.landawn.abacus.util.Indexed;
import com.landawn.abacus.util.Iterators;
import com.landawn.abacus.util.Joiner;
import com.landawn.abacus.util.ListMultimap;
import com.landawn.abacus.util.Matrix;
import com.landawn.abacus.util.Multimap;
import com.landawn.abacus.util.Multiset;
import com.landawn.abacus.util.MutableBoolean;
import com.landawn.abacus.util.MutableLong;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Nth;
import com.landawn.abacus.util.ObjIterator;
import com.landawn.abacus.util.ObjectFactory;
import com.landawn.abacus.util.Optional;
import com.landawn.abacus.util.OptionalDouble;
import com.landawn.abacus.util.Pair;
import com.landawn.abacus.util.Percentage;
import com.landawn.abacus.util.PermutationIterator;
import com.landawn.abacus.util.Try;
import com.landawn.abacus.util.function.BiConsumer;
import com.landawn.abacus.util.function.BiFunction;
import com.landawn.abacus.util.function.BiPredicate;
import com.landawn.abacus.util.function.BinaryOperator;
import com.landawn.abacus.util.function.BooleanSupplier;
import com.landawn.abacus.util.function.Consumer;
import com.landawn.abacus.util.function.Function;
import com.landawn.abacus.util.function.IntFunction;
import com.landawn.abacus.util.function.Predicate;
import com.landawn.abacus.util.function.Supplier;
import com.landawn.abacus.util.function.ToDoubleFunction;
import com.landawn.abacus.util.function.ToIntFunction;
import com.landawn.abacus.util.function.ToLongFunction;
import com.landawn.abacus.util.function.TriFunction;

/**
 * This class is a sequential, stateful and immutable stream implementation.
 *
 * @param 
 * @since 0.8
 * 
 * @author Haiyang Li
 */
abstract class AbstractStream extends Stream {

    AbstractStream(final boolean sorted, final Comparator cmp, final Collection closeHandlers) {
        super(sorted, cmp, closeHandlers);
    }

    @Override
    public  Stream filter(final U seed, final BiPredicate predicate) {
        return filter(Fn.p(seed, predicate));
    }

    @Override
    public  Stream takeWhile(final U seed, final BiPredicate predicate) {
        return takeWhile(Fn.p(seed, predicate));
    }

    @Override
    public  Stream dropWhile(final U seed, final BiPredicate predicate) {
        return dropWhile(Fn.p(seed, predicate));
    }

    @Override
    public Stream skip(final long n, final Consumer action) {
        N.checkArgNotNegative(n, "n");

        if (n == 0) {
            return this;
        }

        final Predicate filter = isParallel() ? new Predicate() {
            final AtomicLong cnt = new AtomicLong(n);

            @Override
            public boolean test(T value) {
                return cnt.getAndDecrement() > 0;
            }
        } : new Predicate() {
            final MutableLong cnt = MutableLong.of(n);

            @Override
            public boolean test(T value) {
                return cnt.getAndDecrement() > 0;
            }
        };

        return dropWhile(filter, action);
    }

    @Override
    public Stream peekFirst(final Consumer action) {
        final Function mapperForFirst = new Function() {
            @Override
            public T apply(T t) {
                action.accept(t);
                return t;
            }
        };

        return mapFirst(mapperForFirst);
    }

    @Override
    public Stream peekLast(final Consumer action) {
        final Function mapperForFirst = new Function() {
            @Override
            public T apply(T t) {
                action.accept(t);
                return t;
            }
        };

        return mapLast(mapperForFirst);
    }

    @Override
    public Stream removeIf(final Predicate predicate) {
        N.checkArgNotNull(predicate);

        return filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return predicate.test(value) == false;
            }
        });
    }

    @Override
    public  Stream removeIf(final U seed, final BiPredicate predicate) {
        N.checkArgNotNull(predicate);

        return filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return predicate.test(value, seed) == false;
            }
        });
    }

    @Override
    public Stream removeIf(final Predicate predicate, final Consumer action) {
        N.checkArgNotNull(predicate);
        N.checkArgNotNull(action);

        return filter(new Predicate() {
            @Override
            public boolean test(T value) {
                if (predicate.test(value)) {
                    action.accept(value);
                    return false;
                }

                return true;
            }
        });
    }

    @Override
    public  Stream removeIf(final U seed, final BiPredicate predicate, final Consumer action) {
        N.checkArgNotNull(predicate);
        N.checkArgNotNull(action);

        return filter(new Predicate() {
            @Override
            public boolean test(T value) {
                if (predicate.test(value, seed)) {
                    action.accept(value);
                    return false;
                }

                return true;
            }
        });
    }

    @Override
    public Stream dropWhile(final Predicate predicate, final Consumer action) {
        N.checkArgNotNull(predicate);
        N.checkArgNotNull(action);

        return dropWhile(new Predicate() {
            @Override
            public boolean test(T value) {
                if (predicate.test(value)) {
                    action.accept(value);
                    return true;
                }

                return false;
            }
        });
    }

    @Override
    public  Stream dropWhile(final U seed, final BiPredicate predicate, final Consumer action) {
        N.checkArgNotNull(predicate);
        N.checkArgNotNull(action);

        return dropWhile(new Predicate() {
            @Override
            public boolean test(T value) {
                if (predicate.test(value, seed)) {
                    action.accept(value);
                    return true;
                }

                return false;
            }
        });
    }

    @Override
    public Stream step(final long step) {
        N.checkArgPositive(step, "step");

        if (step == 1) {
            return this;
        }

        final long skip = step - 1;
        final ObjIteratorEx iter = iteratorEx();

        final Iterator iterator = new ObjIteratorEx() {
            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

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

        return newStream(iterator, sorted, cmp);
    }

    @Override
    public  Stream map(final U seed, final BiFunction mapper) {
        return map(Fn.f(seed, mapper));
    }

    //    @Override
    //    public  Stream biMap(BiFunction mapper) {
    //        return biMap(mapper, false);
    //    }
    //
    //    @Override
    //    public  Stream triMap(TriFunction mapper) {
    //        return triMap(mapper, false);
    //    }

    @Override
    public  Stream slidingMap(BiFunction mapper) {
        return slidingMap(mapper, 1);
    }

    @Override
    public  Stream slidingMap(BiFunction mapper, int increment) {
        return slidingMap(mapper, increment, false);
    }

    @Override
    public  Stream slidingMap(TriFunction mapper) {
        return slidingMap(mapper, 1);
    }

    @Override
    public  Stream slidingMap(TriFunction mapper, int increment) {
        return slidingMap(mapper, increment, false);
    }

    @Override
    public  Stream rangeMap(final BiPredicate sameRange, final BiFunction mapper) {
        final Iterator iter = iterator();

        return newStream(new ObjIteratorEx() {
            private final T NULL = (T) Stream.NONE;
            private T left = NULL;
            private T right = null;
            private T next = null;

            @Override
            public boolean hasNext() {
                return left != NULL || iter.hasNext();
            }

            @Override
            public U next() {
                if (left == NULL) {
                    left = iter.next();
                }

                right = left;
                boolean hasNext = false;

                while (iter.hasNext()) {
                    next = iter.next();

                    if (sameRange.test(left, next)) {
                        right = next;
                    } else {
                        hasNext = true;
                        break;
                    }
                }

                final U res = mapper.apply(left, right);

                left = hasNext ? next : NULL;

                return res;
            }
        }, false, null);
    }

    @Override
    public  EntryStream mapToEntry(final Function> mapper) {
        final Function mapper2 = Fn.identity();

        if (mapper == mapper2) {
            return EntryStream.of((Stream>) this);
        }

        return EntryStream.of(map(mapper));
    }

    @Override
    public  EntryStream mapToEntry(final Function keyMapper, final Function valueMapper) {
        final Function> mapper = new Function>() {
            @Override
            public Entry apply(T t) {
                return new SimpleImmutableEntry<>(keyMapper.apply(t), valueMapper.apply(t));
            }
        };

        return mapToEntry(mapper);
    }

    @Override
    public  Stream flatMap(final U seed, final BiFunction> mapper) {
        return flatMap(new Function>() {
            @Override
            public Stream apply(T t) {
                return mapper.apply(t, seed);
            }
        });
    }

    @Override
    public  Stream flattMap(final Function> mapper) {
        return flatMap(new Function>() {
            @Override
            public Stream apply(T t) {
                return Stream.of(mapper.apply(t));
            }
        });
    }

    @Override
    public  Stream flattMap(final U seed, final BiFunction> mapper) {
        return flatMap(new Function>() {
            @Override
            public Stream apply(T t) {
                return Stream.of(mapper.apply(t, seed));
            }
        });
    }

    @Override
    public  Stream flatMapp(final Function mapper) {
        return flatMap(new Function>() {
            @Override
            public Stream apply(T t) {
                return Stream.of(mapper.apply(t));
            }
        });
    }

    @Override
    public  Stream flatMapp(final U seed, final BiFunction mapper) {
        return flatMap(new Function>() {
            @Override
            public Stream apply(T t) {
                return Stream.of(mapper.apply(t, seed));
            }
        });
    }

    @Override
    public  EntryStream flatMapToEntry(final Function>> mapper) {
        return EntryStream.of(flatMap(mapper));
    }

    @Override
    @ParallelSupported
    public  EntryStream flattMapToEntry(final Function> mapper) {
        final Function>> mapper2 = new Function>>() {
            @Override
            public Stream> apply(T t) {
                return Stream.of(mapper.apply(t));
            }
        };

        return flatMapToEntry(mapper2);
    }

    @Override
    @ParallelSupported
    public  EntryStream flatMappToEntry(final Function> mapper) {
        final Function>> mapper2 = new Function>>() {
            @Override
            public Stream> apply(T t) {
                return mapper.apply(t).entries();
            }
        };

        return flatMapToEntry(mapper2);
    }

    //    @Override
    //    public  EntryStream flattMapToEntry(final Function> flatValueMapper) {
    //        final Function>> flatEntryMapper = new Function>>() {
    //            @Override
    //            public Stream> apply(final T t) {
    //                final Function> entryMapper = new Function>() {
    //                    @Override
    //                    public Entry apply(V v) {
    //                        return new SimpleImmutableEntry<>(t, v);
    //                    }
    //                };
    //
    //                return Stream.of(flatValueMapper.apply(t)).map(entryMapper);
    //            }
    //        };
    //
    //        return flatMapToEntry(flatEntryMapper);
    //    }

    @Override
    @SuppressWarnings("rawtypes")
    public Stream sortedBy(final Function keyExtractor) {
        final Comparator comparator = new Comparator() {
            @Override
            public int compare(T o1, T o2) {
                return N.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));
            }
        };

        return sorted(comparator);
    }

    @Override
    public Stream sortedByInt(final ToIntFunction keyExtractor) {
        final Comparator comparator = Comparators.comparingInt(keyExtractor);

        return sorted(comparator);
    }

    @Override
    public Stream sortedByLong(final ToLongFunction keyExtractor) {
        final Comparator comparator = Comparators.comparingLong(keyExtractor);

        return sorted(comparator);
    }

    @Override
    public Stream sortedByDouble(final ToDoubleFunction keyExtractor) {
        final Comparator comparator = Comparators.comparingDouble(keyExtractor);

        return sorted(comparator);
    }

    @Override
    public Stream> split(final int size) {
        return splitToList(size).map(new Function, Stream>() {
            @Override
            public Stream apply(List t) {
                return new ArrayStream<>(toArray(t), 0, t.size(), sorted, cmp, null);
            }
        });
    }

    @Override
    public Stream> splitToList(final int size) {
        return split(size, Fn.Factory. ofList());
    }

    @Override
    public Stream> splitToSet(final int size) {
        return split(size, Fn.Factory. ofSet());
    }

    @Override
    public Stream> split(final Predicate predicate) {
        return splitToList(predicate).map(new Function, Stream>() {
            @Override
            public Stream apply(List t) {
                return new ArrayStream<>(toArray(t), 0, t.size(), sorted, cmp, null);
            }
        });
    }

    @Override
    public Stream> splitToList(final Predicate predicate) {
        return split(predicate, Suppliers. ofList());
    }

    @Override
    public Stream> splitToSet(final Predicate predicate) {
        return split(predicate, Suppliers. ofSet());
    }

    @Override
    public > Stream split(final Predicate predicate, final Supplier collectionSupplier) {
        final BiPredicate predicate2 = new BiPredicate() {

            @Override
            public boolean test(T t, Object u) {
                return predicate.test(t);
            }
        };

        return split(null, predicate2, null, collectionSupplier);
    }

    @Override
    public  Stream> split(final U seed, final BiPredicate predicate, final Consumer seedUpdate) {
        return splitToList(seed, predicate, seedUpdate).map(new Function, Stream>() {
            @Override
            public Stream apply(List t) {
                return new ArrayStream<>(toArray(t), 0, t.size(), sorted, cmp, null);
            }
        });
    }

    @Override
    public  Stream> splitToList(final U seed, final BiPredicate predicate, final Consumer seedUpdate) {
        return split(seed, predicate, seedUpdate, Suppliers. ofList());
    }

    @Override
    public  Stream> splitToSet(final U seed, final BiPredicate predicate, final Consumer seedUpdate) {
        return split(seed, predicate, seedUpdate, Suppliers. ofSet());
    }

    @Override
    public Stream> sliding(final int windowSize, final int increment) {
        return slidingToList(windowSize, increment).map(new Function, Stream>() {
            @Override
            public Stream apply(List t) {
                return new ArrayStream<>(toArray(t), 0, t.size(), sorted, cmp, null);
            }
        });
    }

    @Override
    public Stream> slidingToList(final int windowSize, final int increment) {
        return sliding(windowSize, increment, Fn.Factory. ofList());
    }

    @Override
    public > Stream sliding(final int windowSize, IntFunction collectionSupplier) {
        return sliding(windowSize, 1, collectionSupplier);
    }

    @Override
    public Stream collapse(final BiPredicate collapsible, final BiFunction mergeFunction) {
        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() {
            private boolean hasNext = false;
            private T next = null;

            @Override
            public boolean hasNext() {
                return hasNext || iter.hasNext();
            }

            @Override
            public T next() {
                T res = hasNext ? next : (next = iter.next());

                while ((hasNext = iter.hasNext())) {
                    if (collapsible.test(next, (next = iter.next()))) {
                        res = mergeFunction.apply(res, next);
                    } else {
                        break;
                    }
                }

                return res;
            }
        }, false, null);
    }

    @Override
    public  Stream collapse(final BiPredicate collapsible, final Supplier supplier, final BiConsumer accumulator) {
        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() {
            private boolean hasNext = false;
            private T next = null;

            @Override
            public boolean hasNext() {
                return hasNext || iter.hasNext();
            }

            @Override
            public R next() {
                final R res = supplier.get();
                accumulator.accept(res, hasNext ? next : (next = iter.next()));

                while ((hasNext = iter.hasNext())) {
                    if (collapsible.test(next, (next = iter.next()))) {
                        accumulator.accept(res, next);
                    } else {
                        break;
                    }
                }

                return res;
            }
        }, false, null);
    }

    @Override
    public  Stream collapse(final BiPredicate collapsible, final Collector collector) {
        final Supplier supplier = collector.supplier();
        final BiConsumer accumulator = collector.accumulator();
        final Function finisher = collector.finisher();
        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() {
            private boolean hasNext = false;
            private T next = null;

            @Override
            public boolean hasNext() {
                return hasNext || iter.hasNext();
            }

            @Override
            public R next() {
                final A c = supplier.get();
                accumulator.accept(c, hasNext ? next : (next = iter.next()));

                while ((hasNext = iter.hasNext())) {
                    if (collapsible.test(next, (next = iter.next()))) {
                        accumulator.accept(c, next);
                    } else {
                        break;
                    }
                }

                return finisher.apply(c);
            }
        }, false, null);
    }

    @Override
    public Stream scan(final BiFunction accumulator) {
        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() {
            private T res = null;
            private boolean isFirst = true;

            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public T next() {
                if (isFirst) {
                    isFirst = false;
                    return (res = iter.next());
                } else {
                    return (res = accumulator.apply(res, iter.next()));
                }
            }
        }, false, null);
    }

    @Override
    public  Stream scan(final R seed, final BiFunction accumulator) {
        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() {
            private R res = seed;

            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public R next() {
                return (res = accumulator.apply(res, iter.next()));
            }
        }, false, null);
    }

    @Override
    public Stream intersperse(final T delimiter) {
        return newStream(new ObjIteratorEx() {
            private final Iterator iter = iterator();
            private boolean toInsert = false;

            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

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

                if (toInsert) {
                    toInsert = false;
                    return delimiter;
                } else {
                    final T res = iter.next();
                    toInsert = true;
                    return res;
                }
            }
        }, false, null);
    }

    @Override
    public  void forEachPair(final Try.BiConsumer action) throws E {
        forEachPair(action, 1);
    }

    @Override
    public  void forEachTriple(final Try.TriConsumer action) throws E {
        forEachTriple(action, 1);
    }

    @Override
    public  Stream>> groupBy(final Function classifier) {
        return groupBy(classifier, Fn. identity());
    }

    @Override
    public  Stream>> groupBy(final Function classifier, Supplier>> mapFactory) {
        return groupBy(classifier, Fn. identity(), mapFactory);
    }

    @Override
    public  Stream>> groupBy(Function classifier, Function valueMapper) {
        final Collector> downstream = Collectors.mapping(valueMapper, Collectors. toList());

        return groupBy(classifier, downstream);
    }

    @Override
    public  Stream>> groupBy(Function classifier, Function valueMapper,
            Supplier>> mapFactory) {
        final Collector> downstream = Collectors.mapping(valueMapper, Collectors. toList());

        return groupBy(classifier, downstream, mapFactory);
    }

    @Override
    public  Stream> groupBy(final Function classifier, Collector downstream) {
        return groupBy(classifier, downstream, Fn.Suppliers. ofMap());
    }

    @Override
    public  Stream> groupBy(final Function classifier, final Collector downstream,
            final Supplier> mapFactory) {
        return newStream(new ObjIteratorEx>() {
            private Iterator> iter = null;

            @Override
            public boolean hasNext() {
                init();
                return iter.hasNext();
            }

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

            private void init() {
                if (iter == null) {
                    iter = AbstractStream.this.toMap(classifier, downstream, mapFactory).entrySet().iterator();
                }
            }
        }, false, null);
    }

    @Override
    public  Stream> groupBy(final Function classifier, final Function valueMapper,
            Collector downstream) {
        return groupBy(classifier, valueMapper, downstream, Fn.Suppliers. ofMap());
    }

    @Override
    public  Stream> groupBy(final Function classifier, final Function valueMapper,
            Collector downstream, Supplier> mapFactory) {
        final Collector downstream2 = Collectors.mapping(valueMapper, downstream);

        return groupBy(classifier, downstream2, mapFactory);
    }

    @Override
    public  Stream> groupBy(final Function classifier, final Function valueMapper,
            BinaryOperator mergeFunction) {
        return groupBy(classifier, valueMapper, mergeFunction, Fn.Suppliers. ofMap());
    }

    @Override
    public  Stream> groupBy(final Function classifier, final Function valueMapper,
            final BinaryOperator mergeFunction, final Supplier> mapFactory) {
        return newStream(new ObjIteratorEx>() {
            private Iterator> iter = null;

            @Override
            public boolean hasNext() {
                init();
                return iter.hasNext();
            }

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

            private void init() {
                if (iter == null) {
                    iter = AbstractStream.this.toMap(classifier, valueMapper, mergeFunction, mapFactory).entrySet().iterator();
                }
            }
        }, false, null);
    }

    @Override
    public Stream>> partitionBy(Predicate predicate) {
        return partitionBy(predicate, Collectors. toList());
    }

    @Override
    public  Stream> partitionBy(final Predicate predicate, final Collector downstream) {
        return newStream(new ObjIteratorEx>() {
            private Iterator> iter = null;

            @Override
            public boolean hasNext() {
                init();
                return iter.hasNext();
            }

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

            private void init() {
                if (iter == null) {
                    iter = AbstractStream.this.partitionTo(predicate, downstream).entrySet().iterator();
                }
            }
        }, false, null);
    }

    @Override
    public EntryStream> partitionByToEntry(Predicate predicate) {
        return partitionByToEntry(predicate, Collectors. toList());
    }

    @Override
    public  EntryStream partitionByToEntry(Predicate predicate, Collector downstream) {
        final Function, Map.Entry> mapper = Fn.identity();

        return partitionBy(predicate, downstream).mapToEntry(mapper);
    }

    @Override
    public  EntryStream> groupByToEntry(Function classifier) {
        return groupByToEntry(classifier, Fn. identity());
    }

    @Override
    public  EntryStream> groupByToEntry(Function classifier, Supplier>> mapFactory) {
        return groupByToEntry(classifier, Fn. identity(), mapFactory);
    }

    @Override
    public  EntryStream> groupByToEntry(Function classifier, Function valueMapper) {
        return groupByToEntry(classifier, valueMapper, Fn.Suppliers.> ofMap());
    }

    @Override
    public  EntryStream> groupByToEntry(Function classifier, Function valueMapper,
            Supplier>> mapFactory) {
        final Function>, Map.Entry>> mapper = Fn.identity();

        return groupBy(classifier, valueMapper, mapFactory).mapToEntry(mapper);
    }

    @Override
    public  EntryStream groupByToEntry(Function classifier, Collector downstream) {
        return groupByToEntry(classifier, downstream, Fn.Suppliers. ofMap());
    }

    @Override
    public  EntryStream groupByToEntry(Function classifier, Collector downstream,
            Supplier> mapFactory) {
        final Function, Map.Entry> mapper = Fn.identity();

        return groupBy(classifier, downstream, mapFactory).mapToEntry(mapper);
    }

    @Override
    public  EntryStream groupByToEntry(final Function classifier, final Function valueMapper,
            Collector downstream) {
        return groupByToEntry(classifier, valueMapper, downstream, Fn.Suppliers. ofMap());
    }

    @Override
    public  EntryStream groupByToEntry(final Function classifier, final Function valueMapper,
            Collector downstream, Supplier> mapFactory) {
        final Function, Map.Entry> mapper = Fn.identity();

        return groupBy(classifier, valueMapper, downstream, mapFactory).mapToEntry(mapper);
    }

    @Override
    public  EntryStream groupByToEntry(Function classifier, Function valueMapper,
            BinaryOperator mergeFunction) {
        return groupByToEntry(classifier, valueMapper, mergeFunction, Fn.Suppliers. ofMap());
    }

    @Override
    public  EntryStream groupByToEntry(Function classifier, Function valueMapper,
            BinaryOperator mergeFunction, Supplier> mapFactory) {
        final Function, Map.Entry> mapper = Fn.identity();

        return groupBy(classifier, valueMapper, mergeFunction, mapFactory).mapToEntry(mapper);
    }

    @Override
    public  Map toMap(Function keyExtractor, Function valueMapper) {
        final Supplier> mapFactory = Fn.Suppliers.ofMap();

        return toMap(keyExtractor, valueMapper, mapFactory);
    }

    @Override
    public > M toMap(Function keyExtractor, Function valueMapper,
            Supplier mapFactory) {
        final BinaryOperator mergeFunction = Fn.throwingMerger();

        return toMap(keyExtractor, valueMapper, mergeFunction, mapFactory);
    }

    @Override
    public  Map toMap(Function keyExtractor, Function valueMapper,
            BinaryOperator mergeFunction) {
        final Supplier> mapFactory = Fn.Suppliers.ofMap();

        return toMap(keyExtractor, valueMapper, mergeFunction, mapFactory);
    }

    @Override
    public  Map toMap(Function classifier, Collector downstream) {
        final Supplier> mapFactory = Fn.Suppliers.ofMap();

        return toMap(classifier, downstream, mapFactory);
    }

    @Override
    public  Map toMap(Function classifier, Function valueMapper,
            Collector downstream) {
        final Supplier> mapFactory = Fn.Suppliers.ofMap();

        return toMap(classifier, valueMapper, downstream, mapFactory);
    }

    @Override
    public > M toMap(final Function classifier, final Function valueMapper,
            final Collector downstream, final Supplier mapFactory) {
        return toMap(classifier, Collectors.mapping(valueMapper, downstream), mapFactory);
    }

    @Override
    public  Map> groupTo(Function classifier) {
        final Supplier>> mapFactory = Fn.Suppliers.ofMap();

        return groupTo(classifier, mapFactory);
    }

    @Override
    public >> M groupTo(Function classifier, Supplier mapFactory) {
        final Collector> downstream = Collectors.toList();

        return toMap(classifier, downstream, mapFactory);
    }

    @Override
    public  Map> groupTo(Function keyExtractor, Function valueMapper) {
        return toMap(keyExtractor, (Collector>) (Collector) Collectors.mapping(valueMapper, Collectors.toList()));
    }

    @SuppressWarnings("rawtypes")
    @Override
    public >> M groupTo(Function keyExtractor, Function valueMapper,
            Supplier mapFactory) {
        return toMap(keyExtractor, (Collector>) (Collector) Collectors.mapping(valueMapper, Collectors.toList()), mapFactory);
    }

    @Override
    public Map> partitionTo(Predicate predicate) {
        final Collector> downstream = Collectors.toList();

        return partitionTo(predicate, downstream);
    }

    @Override
    public  Map partitionTo(Predicate predicate, Collector downstream) {
        return collect(Collectors.partitioningBy(predicate, downstream));
    }

    @Override
    public  ListMultimap toMultimap(Function keyExtractor) {
        return toMultimap(keyExtractor, Fn. identity());
    }

    @Override
    public , M extends Multimap> M toMultimap(Function keyExtractor, Supplier mapFactory) {
        return toMultimap(keyExtractor, Fn. identity(), mapFactory);
    }

    @Override
    public  ListMultimap toMultimap(Function keyExtractor, Function valueMapper) {
        return toMultimap(keyExtractor, valueMapper, Fn.Suppliers. ofListMultimap());
    }

    @Override
    public Matrix toMatrix(final Class type) {
        final T[] a = toArray(new IntFunction() {
            @Override
            public T[] apply(int value) {
                return (T[]) N.newArray(type, 0);
            }
        });

        return Matrix.of(a);
    }

    @Override
    public int sumInt(ToIntFunction mapper) {
        return collect(Collectors.summingInt(mapper));
    }

    @Override
    public long sumLong(ToLongFunction mapper) {
        return collect(Collectors.summingLong(mapper));
    }

    @Override
    public double sumDouble(ToDoubleFunction mapper) {
        return collect(Collectors.summingDouble(mapper));
    }

    @Override
    public OptionalDouble averageInt(ToIntFunction mapper) {
        return collect(Collectors.averagingIntt(mapper));
    }

    @Override
    public OptionalDouble averageLong(ToLongFunction mapper) {
        return collect(Collectors.averagingLongg(mapper));
    }

    @Override
    public OptionalDouble averageDouble(ToDoubleFunction mapper) {
        return collect(Collectors.averagingDoubble(mapper));
    }

    @Override
    public  Stream> innerJoin(final Collection b, final Function leftKeyMapper, final Function rightKeyMapper) {
        final ListMultimap rightKeyMap = ListMultimap.from(b, rightKeyMapper);
        final Map> rightKeyStreamMap = new HashMap<>(N.initHashCapacity(rightKeyMap.size()));

        for (Map.Entry> entry : rightKeyMap.entrySet()) {
            rightKeyStreamMap.put(entry.getKey(), Stream.of(entry.getValue()).cached());
        }

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                final Stream s = rightKeyStreamMap.get(leftKeyMapper.apply(t));

                return s == null ? Stream.> empty() : s.map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        return Pair.of(t, u);
                    }
                });
            }
        });
    }

    @Override
    public  Stream> innerJoin(final Collection b, final BiPredicate predicate) {
        final Stream s = Stream.of(b).cached();

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                return s.filter(new Predicate() {
                    @Override
                    public boolean test(final U u) {
                        return predicate.test(t, u);
                    }
                }).map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        return Pair.of(t, u);
                    }
                });
            }
        });
    }

    @Override
    public  Stream> fullJoin(final Collection b, final Function leftKeyMapper, final Function rightKeyMapper) {
        final ListMultimap rightKeyMap = ListMultimap.from(b, rightKeyMapper);
        final Map> rightKeyStreamMap = new HashMap<>(N.initHashCapacity(rightKeyMap.size()));
        final Map joinedRights = new IdentityHashMap<>();
        final boolean isParallelStream = this.isParallel();

        for (Map.Entry> entry : rightKeyMap.entrySet()) {
            rightKeyStreamMap.put(entry.getKey(), Stream.of(entry.getValue()).cached());
        }

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                final Stream s = rightKeyStreamMap.get(leftKeyMapper.apply(t));

                return s == null ? Stream.of(Pair.of(t, (U) null)) : s.map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        if (isParallelStream) {
                            synchronized (joinedRights) {
                                joinedRights.put(u, u);
                            }
                        } else {
                            joinedRights.put(u, u);
                        }

                        return Pair.of(t, u);
                    }
                });
            }
        }).append(Stream.of(b).filter(new Predicate() {
            @Override
            public boolean test(U u) {
                return joinedRights.containsKey(u) == false;
            }
        }).map(new Function>() {
            @Override
            public Pair apply(U u) {
                return Pair.of((T) null, u);
            }
        }));
    }

    @Override
    public  Stream> fullJoin(final Collection b, final BiPredicate predicate) {
        final Stream s = Stream.of(b).cached();
        final Map joinedRights = new IdentityHashMap<>();
        final boolean isParallelStream = this.isParallel();

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                final MutableBoolean joined = MutableBoolean.of(false);

                return s.filter(new Predicate() {
                    @Override
                    public boolean test(final U u) {
                        return predicate.test(t, u);
                    }
                }).map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        joined.setTrue();

                        if (isParallelStream) {
                            synchronized (joinedRights) {
                                joinedRights.put(u, u);
                            }
                        } else {
                            joinedRights.put(u, u);
                        }

                        return Pair.of(t, u);
                    }
                }).append(Stream.iterate(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() {
                        return joined.isFalse();
                    }
                }, new Supplier>() {
                    @Override
                    public Pair get() {
                        joined.setTrue();
                        return Pair.of(t, (U) null);
                    }
                }));
            }
        }).append(Stream.of(b).filter(new Predicate() {
            @Override
            public boolean test(U u) {
                return joinedRights.containsKey(u) == false;
            }
        }).map(new Function>() {
            @Override
            public Pair apply(U u) {
                return Pair.of((T) null, u);
            }
        }));
    }

    @Override
    public  Stream> leftJoin(final Collection b, final Function leftKeyMapper, final Function rightKeyMapper) {
        final ListMultimap rightKeyMap = ListMultimap.from(b, rightKeyMapper);
        final Map> rightKeyStreamMap = new HashMap<>(N.initHashCapacity(rightKeyMap.size()));

        for (Map.Entry> entry : rightKeyMap.entrySet()) {
            rightKeyStreamMap.put(entry.getKey(), Stream.of(entry.getValue()).cached());
        }

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                final Stream s = rightKeyStreamMap.get(leftKeyMapper.apply(t));

                return s == null ? Stream.of(Pair.of(t, (U) null)) : s.map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        return Pair.of(t, u);
                    }
                });
            }
        });
    }

    @Override
    public  Stream> leftJoin(final Collection b, final BiPredicate predicate) {
        final Stream s = Stream.of(b).cached();

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                final MutableBoolean joined = MutableBoolean.of(false);

                return s.filter(new Predicate() {
                    @Override
                    public boolean test(final U u) {
                        return predicate.test(t, u);
                    }
                }).map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        joined.setTrue();

                        return Pair.of(t, u);
                    }
                }).append(Stream.iterate(new BooleanSupplier() {
                    @Override
                    public boolean getAsBoolean() {
                        return joined.isFalse();
                    }
                }, new Supplier>() {
                    @Override
                    public Pair get() {
                        joined.setTrue();
                        return Pair.of(t, (U) null);
                    }
                }));
            }
        });
    }

    @Override
    public  Stream> rightJoin(final Collection b, final Function leftKeyMapper, final Function rightKeyMapper) {
        final ListMultimap rightKeyMap = ListMultimap.from(b, rightKeyMapper);
        final Map> rightKeyStreamMap = new HashMap<>(N.initHashCapacity(rightKeyMap.size()));
        final Map joinedRights = new IdentityHashMap<>();
        final boolean isParallelStream = this.isParallel();

        for (Map.Entry> entry : rightKeyMap.entrySet()) {
            rightKeyStreamMap.put(entry.getKey(), Stream.of(entry.getValue()).cached());
        }

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                final Stream s = rightKeyStreamMap.get(leftKeyMapper.apply(t));

                return s == null ? Stream.> empty() : s.map(new Function>() {
                    @Override
                    public Pair apply(U u) {
                        if (isParallelStream) {
                            synchronized (joinedRights) {
                                joinedRights.put(u, u);
                            }
                        } else {
                            joinedRights.put(u, u);
                        }

                        return Pair.of(t, u);
                    }
                });
            }
        }).append(Stream.of(b).filter(new Predicate() {
            @Override
            public boolean test(U u) {
                return joinedRights.containsKey(u) == false;
            }
        }).map(new Function>() {
            @Override
            public Pair apply(U u) {
                return Pair.of((T) null, u);
            }
        }));
    }

    @Override
    public  Stream> rightJoin(final Collection b, final BiPredicate predicate) {
        final Stream s = Stream.of(b).cached();
        final Map joinedRights = new IdentityHashMap<>();
        final boolean isParallelStream = this.isParallel();

        return flatMap(new Function>>() {
            @Override
            public Stream> apply(final T t) {
                return s.filter(new Predicate() {
                    @Override
                    public boolean test(final U u) {
                        return predicate.test(t, u);
                    }
                }).map(new Function>() {
                    @Override
                    public Pair apply(U u) {

                        if (isParallelStream) {
                            synchronized (joinedRights) {
                                joinedRights.put(u, u);
                            }
                        } else {
                            joinedRights.put(u, u);
                        }

                        return Pair.of(t, u);
                    }
                });
            }
        }).append(Stream.of(b).filter(new Predicate() {
            @Override
            public boolean test(U u) {
                return joinedRights.containsKey(u) == false;
            }
        }).map(new Function>() {
            @Override
            public Pair apply(U u) {
                return Pair.of((T) null, u);
            }
        }));
    }

    @Override
    public  Optional findAny(final Try.Predicate predicate) throws E {
        return findFirst(predicate);
    }

    @Override
    public  Optional findFirst(final U seed, final Try.BiPredicate predicate) throws E {
        return findFirst(Fn.pp(seed, predicate));
    }

    @Override
    public  Optional findLast(final U seed, final Try.BiPredicate predicate) throws E {
        return findLast(Fn.pp(seed, predicate));
    }

    @Override
    public  Optional findFirstOrLast(final Try.Predicate predicateForFirst,
            final Try.Predicate predicateForLast) throws E, E2 {
        final ObjIteratorEx iter = iteratorEx();
        T last = (T) NONE;
        T next = null;

        while (iter.hasNext()) {
            next = iter.next();

            if (predicateForFirst.test(next)) {
                return Optional.of(next);
            } else if (predicateForLast.test(next)) {
                last = next;
            }
        }

        return last == NONE ? (Optional) Optional.empty() : Optional.of(last);
    }

    @Override
    public  Optional findFirstOrLast(final U seed,
            final Try.BiPredicate predicateForFirst, final Try.BiPredicate predicateForLast) throws E, E2 {
        final ObjIteratorEx iter = iteratorEx();
        T last = (T) NONE;
        T next = null;

        while (iter.hasNext()) {
            next = iter.next();

            if (predicateForFirst.test(next, seed)) {
                return Optional.of(next);
            } else if (predicateForLast.test(next, seed)) {
                last = next;
            }
        }

        return last == NONE ? (Optional) Optional.empty() : Optional.of(last);
    }

    @Override
    public  Optional findFirstOrLast(final Function preFunc,
            final Try.BiPredicate predicateForFirst, final Try.BiPredicate predicateForLast) throws E, E2 {
        final ObjIteratorEx iter = iteratorEx();
        U seed = null;
        T last = (T) NONE;
        T next = null;

        while (iter.hasNext()) {
            next = iter.next();
            seed = preFunc.apply(next);

            if (predicateForFirst.test(next, seed)) {
                return Optional.of(next);
            } else if (predicateForLast.test(next, seed)) {
                last = next;
            }
        }

        return last == NONE ? (Optional) Optional.empty() : Optional.of(last);
    }

    @Override
    public  Optional findAny(final U seed, final Try.BiPredicate predicate) throws E {
        return findAny(Fn.pp(seed, predicate));
    }

    @Override
    public  boolean anyMatch(final U seed, final Try.BiPredicate predicate) throws E {
        return anyMatch(Fn.pp(seed, predicate));
    }

    @Override
    public  boolean allMatch(final U seed, final Try.BiPredicate predicate) throws E {
        return allMatch(Fn.pp(seed, predicate));
    }

    @Override
    public  boolean noneMatch(final U seed, final Try.BiPredicate predicate) throws E {
        return noneMatch(Fn.pp(seed, predicate));
    }

    @Override
    @SafeVarargs
    public final boolean containsAll(final T... a) {
        if (N.isNullOrEmpty(a)) {
            return true;
        } else if (a.length == 1) {
            return anyMatch(Fn.equal(a[0]));
        } else if (a.length == 2) {
            return filter(new Predicate() {
                private final T val1 = a[0];
                private final T val2 = a[1];

                @Override
                public boolean test(T t) {
                    return N.equals(t, val1) || N.equals(t, val2);
                }
            }).distinct().limit(2).count() == 2;
        } else {
            return containsAll(N.asSet(a));
        }
    }

    @Override
    public boolean containsAll(final Collection c) {
        if (N.isNullOrEmpty(c)) {
            return true;
        } else if (c.size() == 1) {
            final T val = c instanceof List ? ((List) c).get(0) : c.iterator().next();
            return anyMatch(Fn.equal(val));
        } else {
            final Set set = c instanceof Set ? (Set) c : N.newHashSet(c);
            return filter(new Predicate() {
                @Override
                public boolean test(T t) {
                    return set.contains(t);
                }
            }).distinct().limit(set.size()).count() == set.size();
        }
    }

    @Override
    public Optional first() {
        final Iterator iter = this.iterator();

        if (iter.hasNext() == false) {
            return Optional.empty();
        }

        return Optional.of(iter.next());
    }

    @Override
    public Optional last() {
        final Iterator iter = this.iterator();

        if (iter.hasNext() == false) {
            return Optional.empty();
        }

        T next = iter.next();

        while (iter.hasNext()) {
            next = iter.next();
        }

        return Optional.of(next);
    }

    @Override
    public Stream skipNull() {
        return filter(Fn.notNull());
    }

    @Override
    public Stream> slidingToList(int windowSize) {
        return slidingToList(windowSize, 1);
    }

    @Override
    public Stream intersection(final Collection c) {
        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return multiset.getAndRemove(value) > 0;
            }
        }).iterator(), sorted, cmp);
    }

    @Override
    public Stream intersection(final Function mapper, final Collection c) {
        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return multiset.getAndRemove(mapper.apply(value)) > 0;
            }
        }).iterator(), sorted, cmp);
    }

    @Override
    public Stream difference(final Collection c) {
        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return multiset.getAndRemove(value) < 1;
            }
        }).iterator(), sorted, cmp);
    }

    @Override
    public Stream difference(final Function mapper, final Collection c) {
        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return multiset.getAndRemove(mapper.apply(value)) < 1;
            }
        }).iterator(), sorted, cmp);
    }

    @Override
    public Stream symmetricDifference(final Collection c) {
        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return multiset.getAndRemove(value) < 1;
            }
        }).append(Stream.of(c).filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return multiset.getAndRemove(value) > 0;
            }
        })).iterator(), false, null);
    }

    @Override
    public Stream> splitAt(final int n) {
        N.checkArgNotNegative(n, "n");

        return newStream(new ObjIteratorEx>() {
            private Stream[] a = null;
            private int cursor = 0;

            @Override
            public boolean hasNext() {
                init();

                return cursor < 2;
            }

            @Override
            public Stream next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                return a[cursor++];
            }

            private void init() {
                if (a == null) {
                    final Iterator iter = AbstractStream.this.iteratorEx();
                    final List list = new ArrayList<>();

                    while (list.size() < n && iter.hasNext()) {
                        list.add(iter.next());
                    }

                    a = new Stream[] { new ArrayStream((T[]) list.toArray(), 0, list.size(), sorted, cmp, null),
                            new IteratorStream(iter, sorted, cmp, null) };
                }
            }

        }, false, null);
    }

    @Override
    public Stream> splitBy(final Predicate where) {
        N.checkArgNotNull(where);

        return newStream(new ObjIteratorEx>() {
            private Stream[] a = null;
            private int cursor = 0;

            @Override
            public boolean hasNext() {
                init();

                return cursor < 2;
            }

            @Override
            public Stream next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                return a[cursor++];
            }

            private void init() {
                if (a == null) {
                    final Iterator iter = AbstractStream.this.iteratorEx();
                    final List list = new ArrayList<>();
                    T next = null;
                    Stream s = null;

                    while (iter.hasNext()) {
                        next = iter.next();

                        if (where.test(next)) {
                            list.add(next);
                        } else {
                            s = Stream.of(next);

                            break;
                        }
                    }

                    a = new Stream[] { new ArrayStream((T[]) list.toArray(), 0, list.size(), sorted, cmp, null),
                            new IteratorStream(iter, sorted, cmp, null) };

                    if (s != null) {
                        if (sorted) {
                            a[1] = new IteratorStream(a[1].prepend(s).iteratorEx(), sorted, cmp, null);
                        } else {
                            a[1] = a[1].prepend(s);
                        }
                    }
                }
            }

        }, false, null);
    }

    @Override
    public Stream reversed() {
        return newStream(new ObjIteratorEx() {
            private boolean initialized = false;
            private T[] aar;
            private int cursor;

            @Override
            public boolean hasNext() {
                if (initialized == false) {
                    init();
                }

                return cursor > 0;
            }

            @Override
            public T next() {
                if (initialized == false) {
                    init();
                }

                if (cursor <= 0) {
                    throw new NoSuchElementException();
                }

                return aar[--cursor];
            }

            @Override
            public long count() {
                if (initialized == false) {
                    init();
                }

                return cursor;
            }

            @Override
            public void skip(long n) {
                if (initialized == false) {
                    init();
                }

                cursor = n < cursor ? cursor - (int) n : 0;
            }

            @Override
            public  A[] toArray(A[] a) {
                if (initialized == false) {
                    init();
                }

                a = a.length >= cursor ? a : (A[]) N.newArray(a.getClass().getComponentType(), cursor);

                for (int i = 0; i < cursor; i++) {
                    a[i] = (A) aar[cursor - i - 1];
                }

                return a;
            }

            private void init() {
                if (initialized == false) {
                    initialized = true;
                    aar = (T[]) AbstractStream.this.toArray();
                    cursor = aar.length;
                }
            }
        }, false, null);
    }

    @Override
    public Stream shuffled(final Random rnd) {
        return lazyLoad(new Function() {
            @Override
            public Object[] apply(final Object[] a) {
                N.shuffle(a, rnd);
                return a;
            }
        }, false, null);
    }

    @Override
    public Stream rotated(final int distance) {
        return newStream(new ObjIteratorEx() {
            private boolean initialized = false;
            private T[] aar;
            private int len;
            private int start;
            private int cnt = 0;

            @Override
            public boolean hasNext() {
                if (initialized == false) {
                    init();
                }

                return cnt < len;
            }

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

                return aar[(start + cnt++) % len];
            }

            @Override
            public long count() {
                if (initialized == false) {
                    init();
                }

                return len - cnt;
            }

            @Override
            public void skip(long n) {
                if (initialized == false) {
                    init();
                }

                cnt = n < len - cnt ? cnt + (int) n : len;
            }

            @Override
            public  A[] toArray(A[] a) {
                if (initialized == false) {
                    init();
                }

                a = a.length >= len - cnt ? a : (A[]) N.newArray(a.getClass().getComponentType(), len - cnt);

                for (int i = cnt; i < len; i++) {
                    a[i - cnt] = (A) aar[(start + i) % len];
                }

                return a;
            }

            private void init() {
                if (initialized == false) {
                    initialized = true;
                    aar = (T[]) AbstractStream.this.toArray();
                    len = aar.length;

                    if (len > 0) {
                        start = distance % len;

                        if (start < 0) {
                            start += len;
                        }

                        start = len - start;
                    }
                }
            }
        }, distance == 0 && sorted, distance == 0 ? cmp : null);
    }

    @Override
    public Stream sorted() {
        return sorted(NATURAL_COMPARATOR);
    }

    @Override
    public Stream reverseSorted() {
        return sorted(REVERSED_COMPARATOR);
    }

    @Override
    public Stream sorted(final Comparator comparator) {
        final Comparator cmp = comparator == null ? NATURAL_COMPARATOR : comparator;

        if (sorted && cmp == this.cmp) {
            return this;
        }

        return lazyLoad(new Function() {
            @Override
            public Object[] apply(final Object[] a) {
                if (isParallel()) {
                    N.parallelSort((T[]) a, cmp);
                } else {
                    N.sort((T[]) a, cmp);
                }

                return a;
            }
        }, true, cmp);
    }

    private Stream lazyLoad(final Function op, final boolean sorted, final Comparator cmp) {
        return newStream(new ObjIteratorEx() {
            private boolean initialized = false;
            private T[] aar;
            private int cursor = 0;
            private int len;

            @Override
            public boolean hasNext() {
                if (initialized == false) {
                    init();
                }

                return cursor < len;
            }

            @Override
            public T next() {
                if (initialized == false) {
                    init();
                }

                if (cursor >= len) {
                    throw new NoSuchElementException();
                }

                return aar[cursor++];
            }

            @Override
            public long count() {
                if (initialized == false) {
                    init();
                }

                return len - cursor;
            }

            @Override
            public void skip(long n) {
                if (initialized == false) {
                    init();
                }

                cursor = n > len - cursor ? len : cursor + (int) n;
            }

            @Override
            public  A[] toArray(A[] a) {
                if (initialized == false) {
                    init();
                }

                a = a.length >= (len - cursor) ? a : (A[]) N.newArray(a.getClass().getComponentType(), (len - cursor));

                for (int i = cursor; i < len; i++) {
                    a[i - cursor] = (A) aar[i];
                }

                return a;
            }

            private void init() {
                if (initialized == false) {
                    initialized = true;
                    aar = (T[]) op.apply(AbstractStream.this.toArray());
                    len = aar.length;
                }
            }
        }, sorted, cmp);
    }

    @Override
    public Stream distinct() {
        final Set set = new HashSet<>();

        return newStream(this.sequential().filter(new Predicate() {
            @Override
            public boolean test(T value) {
                return set.add(hashKey(value));
            }
        }).iterator(), sorted, cmp);
    }

    @Override
    public Stream distinctBy(final Function keyExtractor) {
        //    final Set set = new HashSet<>();
        //
        //    final Predicate predicate = isParallel() ? new Predicate() {
        //        @Override
        //        public boolean test(T value) {
        //            final Object key = hashKey(keyExtractor.apply(value));
        //
        //            synchronized (set) {
        //                return set.add(key);
        //            }
        //        }
        //    } : new Predicate() {
        //        @Override
        //        public boolean test(T value) {
        //            return set.add(hashKey(keyExtractor.apply(value)));
        //        }
        //    };

        final Predicate predicate = isParallel() ? new Predicate() {
            private final ConcurrentHashMap map = new ConcurrentHashMap<>();

            @Override
            public boolean test(T value) {
                Object key = hashKey(keyExtractor.apply(value));
                key = key == null ? NONE : key;

                return map.put(key, Stream.NONE) == null;
            }
        } : new Predicate() {
            private final Set set = new HashSet<>();

            @Override
            public boolean test(T value) {
                return set.add(hashKey(keyExtractor.apply(value)));
            }
        };

        return filter(predicate);
    }

    @Override
    public Stream top(int n) {
        return top(n, NATURAL_COMPARATOR);
    }

    @Override
    public Optional> percentiles() {
        final Object[] a = sorted().toArray();

        if (N.isNullOrEmpty(a)) {
            return Optional.empty();
        }

        return Optional.of((Map) N.percentiles(a));
    }

    @Override
    public Optional> percentiles(Comparator comparator) {
        final Object[] a = sorted(comparator).toArray();

        if (N.isNullOrEmpty(a)) {
            return Optional.empty();
        }

        return Optional.of((Map) N.percentiles(a));
    }

    //    @Override
    //    public Stream> powerSet() {
    //        final Set set = toSet(new Supplier>() {
    //            @Override
    //            public Set get() {
    //                return new LinkedHashSet<>();
    //            }
    //        });
    //
    //        return newStream(Seq.powerSet(set).iterator(), false, null);
    //    }

    @Override
    public Stream> combinations() {
        if (this instanceof ArrayStream) {
            return newStream(IntStream.rangeClosed(0, (int) count()).flatMapToObj(new IntFunction>>() {
                @Override
                public Stream> apply(int value) {
                    return combinations(value);
                }
            }).iterator(), false, null);
        } else {
            return newStream((T[]) toArray(), false, null).combinations();
        }
    }

    @Override
    public Stream> combinations(final int len) {
        if (this instanceof ArrayStream) {
            N.checkFromIndexSize(0, len, (int) count());

            if (len == 0) {
                return newStream(N.asArray(N. emptyList()), false, null);
            } else if (len == 1) {
                return map(new Function>() {
                    @Override
                    public List apply(T t) {
                        return N.asList(t);
                    }
                });
            } else if (len == count()) {
                return newStream(N.asArray(toList()), false, null);
            } else {
                final T[] a = ((ArrayStream) this).elements;
                final int fromIndex = ((ArrayStream) this).fromIndex;
                final int toIndex = ((ArrayStream) this).toIndex;

                return newStream(new ObjIteratorEx>() {
                    private final int[] indices = Array.range(fromIndex, fromIndex + len);

                    @Override
                    public boolean hasNext() {
                        return indices[0] <= toIndex - len;
                    }

                    @Override
                    public List next() {
                        final List result = new ArrayList<>(len);

                        for (int idx : indices) {
                            result.add(a[idx]);
                        }

                        if (++indices[len - 1] == toIndex) {
                            for (int i = len - 1; i > 0; i--) {
                                if (indices[i] > toIndex - (len - i)) {
                                    indices[i - 1]++;

                                    for (int j = i; j < len; j++) {
                                        indices[j] = indices[j - 1] + 1;
                                    }
                                }
                            }
                        }

                        return result;
                    }

                }, false, null);
            }
        } else {
            return newStream((T[]) toArray(), false, null).combinations(len);
        }
    }

    @Override
    public Stream> combinations(final int len, final boolean repeat) {
        if (repeat == false) {
            return combinations(len);
        } else {
            return newStream(new ObjIteratorEx>() {
                private List> list = null;
                private int size = 0;
                private int cursor = 0;

                @Override
                public boolean hasNext() {
                    init();
                    return cursor < size;
                }

                @Override
                public List next() {
                    if (hasNext() == false) {
                        throw new NoSuchElementException();
                    }

                    return list.get(cursor++);
                }

                @Override
                public void skip(long n) {
                    cursor = n <= size - cursor ? cursor + (int) n : size;
                }

                @Override
                public long count() {
                    return size - cursor;
                }

                private void init() {
                    if (list == null) {
                        list = N.cartesianProduct(N.repeat(AbstractStream.this.toList(), len));
                        size = list.size();
                    }
                }
            }, false, null);
        }
    }

    @Override
    public Stream> permutations() {
        return newStream(PermutationIterator.of(toList()), false, null);
    }

    @Override
    public Stream> orderedPermutations() {
        return orderedPermutations(NATURAL_COMPARATOR);
    }

    @Override
    public Stream> orderedPermutations(Comparator comparator) {
        final Iterator> iter = PermutationIterator.ordered(toList(), comparator == null ? NATURAL_COMPARATOR : comparator);

        return newStream(iter, false, null);
    }

    @Override
    public Stream> cartesianProduct(final Collection> cs) {
        final List> cList = new ArrayList<>(cs.size() + 1);
        cList.add(this.toList());
        cList.addAll(cs);

        return newStream(new ObjIteratorEx>() {
            private List> list = null;
            private int size = 0;
            private int cursor = 0;

            @Override
            public boolean hasNext() {
                init();
                return cursor < size;
            }

            @Override
            public List next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                return list.get(cursor++);
            }

            @Override
            public void skip(long n) {
                cursor = n <= size - cursor ? cursor + (int) n : size;
            }

            @Override
            public long count() {
                return size - cursor;
            }

            private void init() {
                if (list == null) {
                    list = N.cartesianProduct(cList);
                    size = list.size();
                }
            }

        }, false, null);
    }

    @Override
    public  A[] toArray(IntFunction generator) {
        final Object[] src = toArray();
        final A[] res = generator.apply(src.length);
        System.arraycopy(src, 0, res, 0, src.length);
        return res;
    }

    @Override
    public DataSet toDataSet() {
        return toDataSet(null);
    }

    @Override
    public DataSet toDataSet(boolean isFirstHeader) {
        if (isFirstHeader) {
            final ObjIterator iter = this.iterator();

            if (iter.hasNext() == false) {
                return N.newDataSet(new ArrayList(0), new ArrayList>(0));
            }

            final T header = iter.next();
            final Type type = N.typeOf(header.getClass());
            List columnNames = null;

            if (type.isArray()) {
                final Object[] a = (Object[]) header;
                columnNames = new ArrayList<>(a.length);

                for (Object e : a) {
                    columnNames.add(N.stringOf(e));
                }
            } else {
                final Collection c = (Collection) header;
                columnNames = new ArrayList<>(c.size());

                for (Object e : c) {
                    columnNames.add(N.stringOf(e));
                }
            }

            return N.newDataSet(columnNames, Iterators.toList(iter));
        } else {
            return toDataSet(null);
        }
    }

    @Override
    public DataSet toDataSet(List columnNames) {
        return N.newDataSet(columnNames, toList());
    }

    @Override
    public String join(CharSequence delimiter) {
        return join(delimiter, "", "");
    }

    @Override
    public String join(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
        final Joiner joiner = Joiner.with(delimiter, prefix, suffix).reuseStringBuilder(true);
        final IteratorEx iter = this.iteratorEx();

        while (iter.hasNext()) {
            joiner.append(iter.next());
        }

        return joiner.toString();
    }

    @Override
    public boolean hasDuplicates() {
        final Set set = new HashSet<>();
        final Iterator iter = iterator();

        while (iter.hasNext()) {
            if (set.add(iter.next()) == false) {
                return true;
            }
        }

        return false;
    }

    @Override
    public  U reduce(U identity, BiFunction accumulator) {
        final BinaryOperator combiner = reducingCombiner;

        return reduce(identity, accumulator, combiner);
    }

    @Override
    public  R collect(Supplier supplier, BiConsumer accumulator) {
        final BiConsumer combiner = collectingCombiner;

        return collect(supplier, accumulator, combiner);
    }

    @Override
    public  R collect(java.util.stream.Collector collector) {
        return collect(Collector.of(collector));
    }

    @Override
    public  RR collectAndThen(Collector downstream, Function finisher) {
        return finisher.apply(collect(downstream));
    }

    @Override
    public  RR collectAndThen(java.util.stream.Collector downstream, Function finisher) {
        return finisher.apply(collect(downstream));
    }

    @Override
    public  R toListAndThen(final Function, R> func) {
        return func.apply(toList());
    }

    @Override
    public  R toSetAndThen(final Function, R> func) {
        return func.apply(toSet());
    }

    @Override
    public Pair, Stream> headAndTail() {
        return Pair.of(head(), tail());
    }

    //    @SuppressWarnings("deprecation")
    //    @Override
    //    public Pair, Optional> headAndTaill() {
    //        return Pair.of(headd(), taill());
    //    }

    @Override
    public Stream> indexed() {
        final MutableLong idx = MutableLong.of(0);

        return newStream(this.sequential().map(new Function>() {
            @Override
            public Indexed apply(T t) {
                return Indexed.of(t, idx.getAndIncrement());
            }
        }).iterator(), true, INDEXED_COMPARATOR);
    }

    @Override
    public Stream queued() {
        return queued(DEFAULT_QUEUE_SIZE_PER_ITERATOR);
    }

    @Override
    public Stream append(Stream stream) {
        return Stream.concat(this, stream);
    }

    @Override
    public Stream append(Collection c) {
        if (N.isNullOrEmpty(c)) {
            return this;
        }

        return append(Stream.of(c));
    }

    @Override
    public Stream prepend(Stream stream) {
        return Stream.concat(stream, this);
    }

    @Override
    public Stream prepend(Collection c) {
        if (N.isNullOrEmpty(c)) {
            return this;
        }

        return prepend(Stream.of(c));
    }

    @Override
    public Stream merge(final Stream b, final BiFunction nextSelector) {
        return Stream.merge(this, b, nextSelector);
    }

    @Override
    public  Stream zipWith(Stream b, BiFunction zipFunction) {
        return Stream.zip(this, b, zipFunction);
    }

    @Override
    public  Stream zipWith(Stream b, Stream c, TriFunction zipFunction) {
        return Stream.zip(this, b, c, zipFunction);
    }

    @Override
    public  Stream zipWith(Stream b, T valueForNoneA, T2 valueForNoneB, BiFunction zipFunction) {
        return Stream.zip(this, b, valueForNoneA, valueForNoneB, zipFunction);
    }

    @Override
    public  Stream zipWith(Stream b, Stream c, T valueForNoneA, T2 valueForNoneB, T3 valueForNoneC,
            TriFunction zipFunction) {
        return Stream.zip(this, b, c, valueForNoneA, valueForNoneB, valueForNoneC, zipFunction);
    }

    @Override
    public Stream cached() {
        return newStream((T[]) toArray(), sorted, cmp);
    }

    @Override
    public Stream cached(IntFunction generator) {
        return newStream(toArray(generator), sorted, cmp);
    }

    @Override
    public  long persist(File file, Try.Function toLine) throws E {
        Writer writer = null;

        try {
            writer = new FileWriter(file);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

        try {
            return persist(writer, toLine);
        } finally {
            IOUtil.close(writer);
        }
    }

    @Override
    public  long persist(OutputStream os, Try.Function toLine) throws E {
        final BufferedWriter bw = ObjectFactory.createBufferedWriter(os);

        try {
            return persist(bw, toLine);
        } finally {
            ObjectFactory.recycle(bw);
        }
    }

    @Override
    public  long persist(Writer writer, Try.Function toLine) throws E {
        final Iterator iter = iterator();
        final BufferedWriter bw = writer instanceof BufferedWriter ? (BufferedWriter) writer : ObjectFactory.createBufferedWriter(writer);
        long cnt = 0;

        try {
            while (iter.hasNext()) {
                bw.write(toLine.apply(iter.next()));
                bw.write(IOUtil.LINE_SEPARATOR);
                cnt++;
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            if (bw != writer) {
                ObjectFactory.recycle(bw);
            }
        }

        return cnt;
    }

    @Override
    public long persist(final Connection conn, final String insertSQL, final int batchSize, final int batchInterval,
            final Try.BiConsumer stmtSetter) {
        PreparedStatement stmt = null;

        try {
            stmt = conn.prepareStatement(insertSQL);

            return persist(stmt, batchSize, batchInterval, stmtSetter);
        } catch (SQLException e) {
            throw new UncheckedSQLException(e);
        } finally {
            closeQuietly(stmt);
        }
    }

    @Override
    public long persist(final PreparedStatement stmt, final int batchSize, final int batchInterval,
            final Try.BiConsumer stmtSetter) {
        N.checkArgument(batchSize > 0 && batchInterval >= 0, "'batchSize'=%s must be greater than 0 and 'batchInterval'=%s can't be negative", batchSize,
                batchInterval);

        final Iterator iter = iterator();

        long cnt = 0;
        try {
            while (iter.hasNext()) {
                stmtSetter.accept(stmt, iter.next());

                stmt.addBatch();

                if ((++cnt % batchSize) == 0) {
                    stmt.executeBatch();
                    stmt.clearBatch();

                    if (batchInterval > 0) {
                        N.sleep(batchInterval);
                    }
                }
            }

            if ((cnt % batchSize) > 0) {
                stmt.executeBatch();
                stmt.clearBatch();
            }
        } catch (SQLException e) {
            throw new UncheckedSQLException(e);
        }

        return cnt;
    }

    private static void closeQuietly(final Statement stmt) {
        if (stmt != null) {
            if (stmt instanceof PreparedStatement) {
                try {
                    ((PreparedStatement) stmt).clearParameters();
                } catch (Exception e) {
                    logger.error("Failed to clear parameters", e);
                }
            }

            try {
                stmt.close();
            } catch (Exception e) {
                logger.error("Failed to close Statement", e);
            }
        }
    }

    @Override
    public  EntryStream mapToEntryER(final Function keyMapper, final Function valueMapper) {
        N.checkState(isParallel() == false, "mapToEntryER can't be applied to parallel stream");

        final Function> mapper = new Function>() {
            private final EntryStream.ReusableEntry entry = new EntryStream.ReusableEntry<>();

            @Override
            public Entry apply(T t) {
                entry.set(keyMapper.apply(t), valueMapper.apply(t));

                return entry;
            }
        };

        return mapToEntry(mapper);
    }

    //    @Override
    //    public  EntryStream flatMapToEntryER(final Function> flatValueMapper) {
    //        N.checkState(isParallel() == false, "flatMapToEntryER can't be applied to parallel stream");
    //
    //        final Function>> flatEntryMapper = new Function>>() {
    //            private final EntryStream.ReusableEntry entry = new EntryStream.ReusableEntry<>();
    //
    //            @Override
    //            public Stream> apply(final T t) {
    //                final Function> entryMapper = new Function>() {
    //                    @Override
    //                    public Entry apply(V v) {
    //                        entry.set(t, v);
    //
    //                        return entry;
    //                    }
    //                };
    //
    //                return Stream.of(flatValueMapper.apply(t)).map(entryMapper);
    //            }
    //        };
    //
    //        return flatMapToEntry(flatEntryMapper);
    //    }
}