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

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

Go to download

A general programming library in Java/Android. It's easy to learn and simple to use with concise and powerful APIs.

There is a newer version: 5.2.4
Show newest version
/*
 * Copyright (C) 2016, 2017, 2018, 2019 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.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
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.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.LongConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;

import com.landawn.abacus.exception.TooManyElementsException;
import com.landawn.abacus.parser.JSONParser;
import com.landawn.abacus.parser.JSONSerializationConfig;
import com.landawn.abacus.parser.JSONSerializationConfig.JSC;
import com.landawn.abacus.parser.ParserFactory;
import com.landawn.abacus.parser.ParserUtil;
import com.landawn.abacus.parser.ParserUtil.BeanInfo;
import com.landawn.abacus.parser.ParserUtil.PropInfo;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.Array;
import com.landawn.abacus.util.BufferedJSONWriter;
import com.landawn.abacus.util.BufferedWriter;
import com.landawn.abacus.util.ClassUtil;
import com.landawn.abacus.util.Comparators;
import com.landawn.abacus.util.DataSet;
import com.landawn.abacus.util.DateTimeFormat;
import com.landawn.abacus.util.Fn;
import com.landawn.abacus.util.Fn.BiFunctions;
import com.landawn.abacus.util.Fn.Factory;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.IOUtil;
import com.landawn.abacus.util.Indexed;
import com.landawn.abacus.util.Iterables;
import com.landawn.abacus.util.Joiner;
import com.landawn.abacus.util.ListMultimap;
import com.landawn.abacus.util.MergeResult;
import com.landawn.abacus.util.Multimap;
import com.landawn.abacus.util.Multiset;
import com.landawn.abacus.util.MutableBoolean;
import com.landawn.abacus.util.MutableInt;
import com.landawn.abacus.util.MutableLong;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Objectory;
import com.landawn.abacus.util.Percentage;
import com.landawn.abacus.util.PermutationIterator;
import com.landawn.abacus.util.Strings.StringUtil;
import com.landawn.abacus.util.Throwables;
import com.landawn.abacus.util.Tuple.Tuple3;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.u.OptionalDouble;
import com.landawn.abacus.util.u.OptionalInt;
import com.landawn.abacus.util.u.OptionalLong;
import com.landawn.abacus.util.function.TriFunction;
import com.landawn.abacus.util.function.TriPredicate;

/**
 *
 * @param 
 */
@SuppressWarnings({ "java:S1192", "java:S1845", "java:S2445", "java:S3077" })
abstract class AbstractStream extends Stream {

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

    @Override
    public Stream skipRange(final int startInclusive, final int endExclusive) {
        assertNotClosed();

        checkArgNotNegative(startInclusive, "startInclusive");
        checkArgNotNegative(endExclusive, "endExclusive");

        if (startInclusive == endExclusive) {
            return skip(0);
        }

        return newStream(new ObjIteratorEx() { //NOSONAR
            private final ObjIteratorEx iter = iteratorEx();
            private final MutableLong idx = MutableLong.of(0);
            private boolean skipped = false;

            @Override
            public boolean hasNext() {
                if (!skipped && idx.value() >= startInclusive) {
                    skipped = true;

                    while (iter.hasNext() && idx.value() < endExclusive) {
                        iter.next();
                        idx.increment();
                    }
                }

                return iter.hasNext();
            }

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

                idx.increment();

                return iter.next();
            }
        }, sorted, cmp);
    }

    @Override
    public Stream skip(final long n, final Consumer action) {
        assertNotClosed();

        checkArgNotNegative(n, "n");
        checkArgNotNull(action, "action");

        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) {
        assertNotClosed();

        final Function mapperForFirst = t -> {
            action.accept(t);
            return t;
        };

        return mapFirst(mapperForFirst);
    }

    @Override
    public Stream peekLast(final Consumer action) {
        assertNotClosed();

        final Function mapperForLast = t -> {
            action.accept(t);
            return t;
        };

        return mapLast(mapperForLast);
    }

    //    @Override
    //    public Stream removeIf(final Predicate predicate) {
    //        assertNotClosed();
    //
    //        return filter(value -> !predicate.test(value));
    //    }
    //
    //    @Override
    //    public Stream removeIf(final Predicate predicate, final Consumer actionOnDroppedItem) {
    //        assertNotClosed();
    //
    //        return filter(value -> {
    //            if (predicate.test(value)) {
    //                actionOnDroppedItem.accept(value);
    //                return false;
    //            }
    //
    //            return true;
    //        });
    //    }

    @Override
    public Stream filter(final Predicate predicate, final Consumer actionOnDroppedItem) {
        assertNotClosed();

        return filter(value -> {
            if (!predicate.test(value)) {
                actionOnDroppedItem.accept(value);
                return false;
            }

            return true;
        });
    }

    @Override
    public Stream dropWhile(final Predicate predicate, final Consumer actionOnDroppedItem) {
        assertNotClosed();

        return dropWhile(value -> {
            if (predicate.test(value)) {
                actionOnDroppedItem.accept(value);
                return true;
            }

            return false;
        });
    }

    @Override
    public Stream step(final long step) {
        assertNotClosed();

        checkArgPositive(step, "step");

        if (step == 1) {
            return skip(0);
        }

        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.advance(skip);
                return next;
            }
        };

        return newStream(iterator, sorted, cmp);
    }

    //    @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) {
        assertNotClosed();

        return slidingMap(mapper, 1);
    }

    @Override
    public  Stream slidingMap(BiFunction mapper, int increment) {
        assertNotClosed();

        return slidingMap(mapper, increment, false);
    }

    @Override
    public  Stream slidingMap(TriFunction mapper) {
        assertNotClosed();

        return slidingMap(mapper, 1);
    }

    @Override
    public  Stream slidingMap(TriFunction mapper, int increment) {
        assertNotClosed();

        return slidingMap(mapper, increment, false);
    }

    @Override
    public  EntryStream mapToEntry(final Function> mapper) {
        assertNotClosed();

        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) {
        assertNotClosed();

        final Function> mapper = t -> new SimpleImmutableEntry<>(keyMapper.apply(t), valueMapper.apply(t));

        return mapToEntry(mapper);
    }

    //    private static final Predicate> IS_PRESENT = new Predicate>() {
    //        @Override
    //        public boolean test(Optional t) {
    //            return t.isPresent();
    //        }
    //    };
    //
    //    private static final Function, Object> OPTIONAL_GET = new Function, Object>() {
    //        @Override
    //        public Object apply(Optional t) {
    //            return t.get();
    //        }
    //    };
    //
    //    @SuppressWarnings("rawtypes")
    //    @Override
    //    public  Stream mapp(Function> mapper) {
    //        final Function, U> optionalGetter = (Function) OPTIONAL_GET;
    //        return map(mapper).filter(IS_PRESENT).map(optionalGetter);
    //    }

    @Override
    public  Stream flatmap(final Function> mapper) {
        assertNotClosed();

        return flatMap(t -> Stream.of(mapper.apply(t)));
    }

    @Override
    public  Stream flattMap(final Function mapper) {
        assertNotClosed();

        return flatMap(t -> Stream.of(mapper.apply(t)));
    }

    @Override
    public CharStream flatmapToChar(final Function mapper) {
        assertNotClosed();

        return flatMapToChar(t -> CharStream.of(mapper.apply(t)));
    }

    @Override
    public ByteStream flatmapToByte(final Function mapper) {
        assertNotClosed();

        return flatMapToByte(t -> ByteStream.of(mapper.apply(t)));
    }

    @Override
    public ShortStream flatmapToShort(final Function mapper) {
        assertNotClosed();

        return flatMapToShort(t -> ShortStream.of(mapper.apply(t)));
    }

    @Override
    public IntStream flatmapToInt(final Function mapper) {
        assertNotClosed();

        return flatMapToInt(t -> IntStream.of(mapper.apply(t)));
    }

    @Override
    public LongStream flatmapToLong(final Function mapper) {
        assertNotClosed();

        return flatMapToLong(t -> LongStream.of(mapper.apply(t)));
    }

    @Override
    public FloatStream flatmapToFloat(final Function mapper) {
        assertNotClosed();

        return flatMapToFloat(t -> FloatStream.of(mapper.apply(t)));
    }

    @Override
    public DoubleStream flatmapToDouble(final Function mapper) {
        assertNotClosed();

        return flatMapToDouble(t -> DoubleStream.of(mapper.apply(t)));
    }

    @Override
    public  EntryStream flatMapToEntry(final Function>> mapper) {
        assertNotClosed();

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

    @Override
    public  EntryStream flatmapToEntry(final Function> mapper) {
        assertNotClosed();

        final Function>> mapper2 = t -> N.nullToEmpty((Map) mapper.apply(t)).entrySet();

        return flatmap(mapper2).mapToEntry(Fn.> identity());
    }

    @Override
    public  EntryStream flattMapToEntry(final Function> mapper) {
        assertNotClosed();

        final Function>> mapper2 = t -> {
            final EntryStream s = mapper.apply(t);

            return s == null ? Stream.empty() : s.entries();
        };

        return flatMapToEntry(mapper2);
    }

    //    @Override
    //    public  EntryStream flatmapToEntry(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
    public  Stream mapMulti(final BiConsumer> mapper) {
        final boolean isParallel = isParallel();

        if (isParallel) {
            final Function> mapper2 = t -> {
                final SpinedBuffer buffer = new SpinedBuffer<>();

                mapper.accept(t, buffer);

                return buffer;
            };

            return flatmap(mapper2);
        } else {
            final Deque queue = new ArrayDeque<>();

            final Consumer consumer = queue::offer;

            final ObjIteratorEx iter = iteratorEx();

            return newStream(new ObjIteratorEx() { //NOSONAR
                @Override
                public boolean hasNext() {
                    if (queue.size() == 0) {
                        while (iter.hasNext()) {
                            mapper.accept(iter.next(), consumer);

                            if (queue.size() > 0) {
                                break;
                            }
                        }
                    }

                    return queue.size() > 0;
                }

                @Override
                public R next() {
                    if (queue.size() == 0 && !hasNext()) {
                        throw new NoSuchElementException();
                    }

                    return queue.poll();
                }
            }, false, null);
        }
    }

    @Override
    public IntStream mapMultiToInt(BiConsumer mapper) {
        final Function mapper2 = t -> {
            final SpinedBuffer.OfInt buffer = new SpinedBuffer.OfInt();

            mapper.accept(t, buffer);

            return IntStream.of(buffer.iterator());
        };

        return flatMapToInt(mapper2);
    }

    @Override
    public LongStream mapMultiToLong(BiConsumer mapper) {
        final Function mapper2 = t -> {
            final SpinedBuffer.OfLong buffer = new SpinedBuffer.OfLong();

            mapper.accept(t, buffer);

            return LongStream.of(buffer.iterator());
        };

        return flatMapToLong(mapper2);
    }

    @Override
    public DoubleStream mapMultiToDouble(BiConsumer mapper) {
        final Function mapper2 = t -> {
            final SpinedBuffer.OfDouble buffer = new SpinedBuffer.OfDouble();

            mapper.accept(t, buffer);

            return DoubleStream.of(buffer.iterator());
        };

        return flatMapToDouble(mapper2);
    }

    @Override
    public  Stream mapPartial(final Function> mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.isPresent()).map(Fn.getIfPresentOrElseNull()));
        } else {
            return map(mapper).filter(Fn.isPresent()).map(Fn.getIfPresentOrElseNull());
        }
    }

    @Override
    public IntStream mapPartialToInt(final Function mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.IS_PRESENT_INT).mapToInt(Fn.GET_AS_INT));
        } else {
            return map(mapper).filter(Fn.IS_PRESENT_INT).mapToInt(Fn.GET_AS_INT);
        }
    }

    @Override
    public LongStream mapPartialToLong(final Function mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.IS_PRESENT_LONG).mapToLong(Fn.GET_AS_LONG));
        } else {
            return map(mapper).filter(Fn.IS_PRESENT_LONG).mapToLong(Fn.GET_AS_LONG);
        }
    }

    @Override
    public DoubleStream mapPartialToDouble(final Function mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.IS_PRESENT_DOUBLE).mapToDouble(Fn.GET_AS_DOUBLE));
        } else {
            return map(mapper).filter(Fn.IS_PRESENT_DOUBLE).mapToDouble(Fn.GET_AS_DOUBLE);
        }
    }

    @Override
    public  Stream mapPartialJdk(final Function> mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.isPresentJdk()).map(Fn.getIfPresentOrElseNullJdk()));
        } else {
            return map(mapper).filter(Fn.isPresentJdk()).map(Fn.getIfPresentOrElseNullJdk());
        }
    }

    @Override
    public IntStream mapPartialToIntJdk(final Function mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.IS_PRESENT_INT_JDK).mapToInt(Fn.GET_AS_INT_JDK));
        } else {
            return map(mapper).filter(Fn.IS_PRESENT_INT_JDK).mapToInt(Fn.GET_AS_INT_JDK);
        }
    }

    @Override
    public LongStream mapPartialToLongJdk(final Function mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.IS_PRESENT_LONG_JDK).mapToLong(Fn.GET_AS_LONG_JDK));
        } else {
            return map(mapper).filter(Fn.IS_PRESENT_LONG_JDK).mapToLong(Fn.GET_AS_LONG_JDK);
        }
    }

    @Override
    public DoubleStream mapPartialToDoubleJdk(final Function mapper) {
        if (isParallel()) {
            return map(mapper).psp(s -> s.filter(Fn.IS_PRESENT_DOUBLE_JDK).mapToDouble(Fn.GET_AS_DOUBLE_JDK));
        } else {
            return map(mapper).filter(Fn.IS_PRESENT_DOUBLE_JDK).mapToDouble(Fn.GET_AS_DOUBLE_JDK);
        }
    }

    @Override
    public  Stream rangeMap(final BiPredicate sameRange, final BiFunction mapper) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private T left = null, right = null, next = null;
            private boolean hasNext = false;

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

            @Override
            public U next() {
                left = hasNext ? next : iter.next();
                right = left;

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

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

                return mapper.apply(left, right);
            }
        }, false, null);
    }

    @Override
    public Stream> collapse(final BiPredicate collapsible) {
        assertNotClosed();

        return collapse(collapsible, Suppliers. ofList()).map(listToStreamMapper());
    }

    @Override
    public > Stream collapse(final BiPredicate collapsible, final Supplier supplier) {
        assertNotClosed();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private final ObjIteratorEx iter = iteratorEx();
            private boolean hasNext = false;
            private T next = null;

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

            @Override
            public C next() {
                final C c = supplier.get();
                c.add(hasNext ? next : (next = iter.next()));

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

                return c;
            }
        }, false, null);
    }

    @Override
    public Stream collapse(final BiPredicate collapsible, final BiFunction mergeFunction) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            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 U init, final BiFunction op) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

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

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

            @Override
            public U next() {
                U res = op.apply(init, hasNext ? next : (next = iter.next()));

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

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

    @Override
    public  Stream collapse(final BiPredicate collapsible, final Supplier supplier,
            final BiConsumer accumulator) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            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) {
        assertNotClosed();

        final java.util.function.Supplier supplier = collector.supplier();
        final java.util.function.BiConsumer accumulator = collector.accumulator();
        final java.util.function.Function finisher = collector.finisher();
        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            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> collapse(final TriPredicate collapsible) {
        assertNotClosed();

        return collapse(collapsible, Suppliers. ofList()).map(listToStreamMapper());
    }

    @Override
    public > Stream collapse(final TriPredicate collapsible, final Supplier supplier) {
        assertNotClosed();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private final ObjIteratorEx iter = iteratorEx();
            private boolean hasNext = false;
            private T next = null;

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

            @Override
            public C next() {
                final T first = hasNext ? next : (next = iter.next());
                final C c = supplier.get();
                c.add(first);

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

                return c;
            }
        }, false, null);
    }

    @Override
    public Stream collapse(final TriPredicate collapsible, final BiFunction mergeFunction) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

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

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

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

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

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

    @Override
    public  Stream collapse(final TriPredicate collapsible, final U init, final BiFunction op) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

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

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

            @Override
            public U next() {
                final T first = hasNext ? next : (next = iter.next());
                U res = op.apply(init, first);

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

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

    @Override
    public  Stream collapse(final TriPredicate collapsible, final Supplier supplier,
            final BiConsumer accumulator) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

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

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

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

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

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

    @Override
    public  Stream collapse(final TriPredicate collapsible, final Collector collector) {
        assertNotClosed();

        final java.util.function.Supplier supplier = collector.supplier();
        final java.util.function.BiConsumer accumulator = collector.accumulator();
        final java.util.function.Function finisher = collector.finisher();
        final ObjIteratorEx iter = iteratorEx();

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

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

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

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

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

    @Override
    public Stream scan(final BiFunction accumulator) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            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 U init, final BiFunction accumulator) {
        assertNotClosed();

        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private U res = init;

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

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

    @Override
    public  Stream scan(final U init, final BiFunction accumulator, boolean initIncluded) {
        assertNotClosed();

        if (!initIncluded) {
            return scan(init, accumulator);
        }

        final ObjIteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private boolean isFirst = true;
            private U res = init;

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

            @Override
            public U next() {
                if (isFirst) {
                    isFirst = false;
                    return init;
                }

                return (res = accumulator.apply(res, iter.next()));
            }
        }, false, null);
    }

    @Override
    @SuppressWarnings("rawtypes")
    public Stream sortedBy(final Function keyMapper) {
        assertNotClosed();

        final Comparator comparator = (o1, o2) -> N.compare(keyMapper.apply(o1), keyMapper.apply(o2));

        return sorted(comparator);
    }

    @Override
    public Stream sortedByInt(final ToIntFunction keyMapper) {
        assertNotClosed();

        final Comparator comparator = Comparators.comparingInt(keyMapper);

        return sorted(comparator);
    }

    @Override
    public Stream sortedByLong(final ToLongFunction keyMapper) {
        assertNotClosed();

        final Comparator comparator = Comparators.comparingLong(keyMapper);

        return sorted(comparator);
    }

    @Override
    public Stream sortedByDouble(final ToDoubleFunction keyMapper) {
        assertNotClosed();

        final Comparator comparator = Comparators.comparingDouble(keyMapper);

        return sorted(comparator);
    }

    @Override
    public Stream> split(final int chunkSize) {
        assertNotClosed();

        return splitToList(chunkSize).map(listToStreamMapper());
    }

    @Override
    public Stream> splitToList(final int chunkSize) {
        assertNotClosed();

        return split(chunkSize, Factory. ofList());
    }

    @Override
    public Stream> splitToSet(final int chunkSize) {
        assertNotClosed();

        return split(chunkSize, Factory. ofSet());
    }

    @Override
    public Stream> split(final Predicate predicate) {
        assertNotClosed();

        return splitToList(predicate).map(listToStreamMapper());
    }

    @Override
    public Stream> splitToList(final Predicate predicate) {
        assertNotClosed();

        return split(predicate, Suppliers. ofList());
    }

    @Override
    public Stream> splitToSet(final Predicate predicate) {
        assertNotClosed();

        return split(predicate, Suppliers. ofSet());
    }

    @Override
    public Stream> splitAt(final Predicate where) {
        assertNotClosed();

        final IteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx>() { //NOSONAR
            private int cursor = 0;
            private T next = null;
            private boolean hasNext = false;

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

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

                Stream result = null;

                if (cursor == 0) {
                    final List list = new ArrayList<>();

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

                        if (!where.test(next)) {
                            list.add(next);
                        } else {
                            hasNext = true;
                            break;
                        }
                    }

                    result = new ArrayStream<>(Stream.toArray(list), 0, list.size(), sorted, cmp, null);
                } else {
                    IteratorEx iterEx = iter;

                    if (hasNext) {
                        iterEx = new ObjIteratorEx<>() {
                            private boolean isFirst = true;

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

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

                                if (isFirst) {
                                    isFirst = false;
                                    return next;
                                } else {
                                    return iter.next();
                                }
                            }
                        };
                    }

                    result = new IteratorStream<>(iterEx, sorted, cmp, null);
                }

                cursor++;

                return result;
            }

            @Override
            public long count() {
                iter.count();

                return 2 - cursor; //NOSONAR
            }

            @Override
            public void advance(long n) {
                if (n == 0) {
                    return;
                } else if (n == 1) {
                    if (cursor == 0) {
                        while (iter.hasNext()) {
                            next = iter.next();

                            if (!where.test(next)) {
                                hasNext = true;
                                break;
                            }
                        }
                    } else {
                        iter.advance(Long.MAX_VALUE);
                    }
                } else {
                    iter.advance(Long.MAX_VALUE);
                }

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

        }, false, null);
    }

    @Override
    public  Stream splitAt(final Predicate where, Collector collector) {
        assertNotClosed();

        final java.util.function.Supplier supplier = collector.supplier();
        final java.util.function.BiConsumer accumulator = collector.accumulator();
        final java.util.function.Function finisher = collector.finisher();

        final IteratorEx iter = iteratorEx();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private int cursor = 0;
            private T next = null;
            private boolean hasNext = false;

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

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

                final A container = supplier.get();

                if (cursor == 0) {
                    while (iter.hasNext()) {
                        next = iter.next();

                        if (!where.test(next)) {
                            accumulator.accept(container, next);
                        } else {
                            hasNext = true;
                            break;
                        }
                    }
                } else {
                    if (hasNext) {
                        accumulator.accept(container, next);
                    }

                    while (iter.hasNext()) {
                        accumulator.accept(container, iter.next());
                    }
                }

                cursor++;

                return finisher.apply(container);
            }

            @Override
            public long count() {
                iter.count();

                return 2 - cursor; //NOSONAR
            }

            @Override
            public void advance(long n) {
                if (n == 0) {
                    return;
                } else if (n == 1) {
                    if (cursor == 0) {
                        while (iter.hasNext()) {
                            next = iter.next();

                            if (!where.test(next)) {
                                hasNext = true;
                                break;
                            }
                        }
                    } else {
                        iter.advance(Long.MAX_VALUE);
                    }
                } else {
                    iter.advance(Long.MAX_VALUE);
                }

                cursor = n >= 2 ? 2 : cursor + (int) n;
            }
        }, false, null);
    }

    @Override
    public Stream> sliding(final int windowSize, final int increment) {
        assertNotClosed();

        return slidingToList(windowSize, increment).map(listToStreamMapper());
    }

    @Override
    public Stream> slidingToList(final int windowSize, final int increment) {
        assertNotClosed();

        return sliding(windowSize, increment, Factory. ofList());
    }

    @Override
    public Stream> slidingToSet(final int windowSize, final int increment) {
        assertNotClosed();

        return sliding(windowSize, increment, Factory. ofSet());
    }

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

        return sliding(windowSize, 1, collectionSupplier);
    }

    @Override
    public  Stream sliding(int windowSize, Collector collector) {
        assertNotClosed();

        return sliding(windowSize, 1, collector);
    }

    @Override
    public Stream intersperse(final T delimiter) {
        assertNotClosed();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private final Iterator iter = iteratorEx();
            private boolean toInsert = false;

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

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

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

    //    @Override
    //    public Stream onErrorContinue(final Consumer errorConsumer) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorContinue is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    while (true) {
    //                        try {
    //                            if (iter.hasNext()) {
    //                                next = iter.next();
    //                            }
    //
    //                            break;
    //                        } catch (Throwable e) {
    //                            logger.warn("ignoring error in onErrorContinue", e);
    //
    //                            errorConsumer.accept(e);
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, sorted, cmp);
    //    }
    //
    //    @Override
    //    public Stream onErrorContinue(final Class type, final Consumer errorConsumer) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorContinue is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    while (true) {
    //                        try {
    //                            if (iter.hasNext()) {
    //                                next = iter.next();
    //                            }
    //
    //                            break;
    //                        } catch (Throwable e) {
    //                            if (type.isAssignableFrom(e.getClass())) {
    //                                logger.warn("ignoring error in onErrorContinue", e);
    //
    //                                errorConsumer.accept(e);
    //                            } else {
    //                                throwThrowable(e);
    //                            }
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, sorted, cmp);
    //    }
    //
    //    @Override
    //    public Stream onErrorContinue(final Predicate errorPredicate, final Consumer errorConsumer) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorContinue is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    while (true) {
    //                        try {
    //                            if (iter.hasNext()) {
    //                                next = iter.next();
    //                            }
    //
    //                            break;
    //                        } catch (Throwable e) {
    //                            if (errorPredicate.test(e)) {
    //                                logger.warn("ignoring error in onErrorContinue", e);
    //
    //                                errorConsumer.accept(e);
    //                            } else {
    //                                throwThrowable(e);
    //                            }
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, sorted, cmp);
    //    }
    //
    //    @Override
    //    public Stream onErrorContinue(final Predicate errorPredicate, final Consumer errorConsumer,
    //            final int maxErrorCountToStop) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorContinue is not supported in parallel stream");
    //
    //        checkArgNotNegative(maxErrorCountToStop, "maxErrorCountToStop");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final AtomicInteger errorCounter = new AtomicInteger(maxErrorCountToStop);
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    while (true) {
    //                        try {
    //                            if (iter.hasNext()) {
    //                                next = iter.next();
    //                            }
    //
    //                            break;
    //                        } catch (Throwable e) {
    //                            if (errorCounter.decrementAndGet() >= 0) {
    //                                if (errorPredicate.test(e)) {
    //                                    logger.warn("ignoring error in onErrorContinue", e);
    //
    //                                    errorConsumer.accept(e);
    //                                } else {
    //                                    throwThrowable(e);
    //                                }
    //                            } else {
    //                                break;
    //                            }
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, sorted, cmp);
    //    }
    //
    //    @Override
    //    public Stream onErrorReturn(final T fallbackValue) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorReturn is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    try {
    //                        if (iter.hasNext()) {
    //                            next = iter.next();
    //                        }
    //                    } catch (Throwable e) {
    //                        logger.warn("ignoring error in onErrorReturn", e);
    //
    //                        next = fallbackValue;
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public Stream onErrorReturn(final Class type, final T fallbackValue) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorReturn is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    try {
    //                        if (iter.hasNext()) {
    //                            next = iter.next();
    //                        }
    //                    } catch (Throwable e) {
    //                        if (type.isAssignableFrom(e.getClass())) {
    //                            logger.warn("ignoring error in onErrorReturn", e);
    //
    //                            next = fallbackValue;
    //                        } else {
    //                            throwThrowable(e);
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public Stream onErrorReturn(final Predicate predicate, final T fallbackValue) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorReturn is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    try {
    //                        if (iter.hasNext()) {
    //                            next = iter.next();
    //                        }
    //                    } catch (Throwable e) {
    //                        if (predicate.test(e)) {
    //                            logger.warn("ignoring error in onErrorReturn", e);
    //
    //                            next = fallbackValue;
    //                        } else {
    //                            throwThrowable(e);
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public Stream onErrorReturn(final Predicate predicate, final Supplier supplierForFallbackValue) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorReturn is not supported in parallel stream");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    try {
    //                        if (iter.hasNext()) {
    //                            next = iter.next();
    //                        }
    //                    } catch (Throwable e) {
    //                        if (predicate.test(e)) {
    //                            logger.warn("ignoring error in onErrorReturn", e);
    //
    //                            next = supplierForFallbackValue.get();
    //                        } else {
    //                            throwThrowable(e);
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public Stream onErrorReturn(final Predicate predicate, final Function mapperForFallbackValue,
    //            final int maxErrorCountToStop) {
    //        assertNotClosed();
    //
    //        checkState(!this.isParallel(), "onErrorReturn is not supported in parallel stream");
    //
    //        checkArgNotNegative(maxErrorCountToStop, "maxErrorCountToStop");
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final AtomicInteger errorCounter = new AtomicInteger(maxErrorCountToStop);
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    try {
    //                        if (iter.hasNext()) {
    //                            next = iter.next();
    //                        }
    //                    } catch (Throwable e) {
    //                        if (errorCounter.decrementAndGet() >= 0) {
    //                            if (predicate.test(e)) {
    //                                logger.warn("ignoring error in onErrorReturn", e);
    //
    //                                next = mapperForFallbackValue.apply(e);
    //                            } else {
    //                                throwThrowable(e);
    //                            }
    //                        } else {
    //                            // break;
    //                        }
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public Stream onErrorStop() {
    //        assertNotClosed();
    //
    //        return newStream(new ObjIteratorEx() {
    //            private final Iterator iter = iteratorEx();
    //            private final T none = (T) NONE;
    //            private T next = none;
    //            private T ret = null;
    //
    //            @Override
    //            public boolean hasNext() {
    //                if (next == none) {
    //                    try {
    //                        if (iter.hasNext()) {
    //                            next = iter.next();
    //                        }
    //                    } catch (Throwable e) {
    //                        logger.warn("ignoring error in onErrorStop", e);
    //                    }
    //                }
    //
    //                return next != none;
    //            }
    //
    //            @Override
    //            public T next() {
    //                if (!hasNext()) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                ret = next;
    //                next = none;
    //                return ret;
    //            }
    //        }, sorted, cmp);
    //    }
    //
    //    private void throwThrowable(Throwable e) {
    //        if (e instanceof Error) {
    //            throw (Error) e;
    //        } else {
    //            throw toRuntimeException(e);
    //        }
    //    }

    //    @Override
    //    public void foreach(java.util.function.Consumer action) {
    //        assertNotClosed();
    //
    //        forEach(Fnn.from(action));
    //    }

    @Override
    public  void forEach(final Throwables.Consumer action) throws E {
        assertNotClosed();

        forEach(action, Fn.emptyAction());
    }

    @Override
    public  void forEachIndexed(Throwables.IndexedConsumer action) throws E {
        assertNotClosed();

        if (isParallel()) {
            final AtomicInteger idx = new AtomicInteger(0);

            forEach(t -> action.accept(idx.getAndIncrement(), t));
        } else {
            final MutableInt idx = MutableInt.of(0);

            forEach(t -> action.accept(idx.getAndIncrement(), t));
        }
    }

    @Override
    public  void forEachUntil(final Throwables.BiConsumer action) throws E {
        assertNotClosed();

        final MutableBoolean flagToBreak = MutableBoolean.of(false);

        final Throwables.Consumer tmp = t -> action.accept(t, flagToBreak);

        if (this.isParallel()) {
            this.psp(s -> s.takeWhile(value -> flagToBreak.isFalse())).forEach(tmp);
        } else {
            this.takeWhile(value -> flagToBreak.isFalse()).forEach(tmp);
        }
    }

    @Override
    public  void forEachUntil(final MutableBoolean flagToBreak, final Throwables.Consumer action) throws E {
        assertNotClosed();

        if (this.isParallel()) {
            this.psp(s -> s.takeWhile(value -> flagToBreak.isFalse())).forEach(action);
        } else {
            this.takeWhile(value -> flagToBreak.isFalse()).forEach(action);
        }
    }

    @Override
    public  void forEachPair(final Throwables.BiConsumer action) throws E {
        assertNotClosed();

        forEachPair(action, 1);
    }

    @Override
    public  void forEachTriple(final Throwables.TriConsumer action) throws E {
        assertNotClosed();

        forEachTriple(action, 1);
    }

    @Override
    public  Optional reduceUntil(Throwables.BinaryOperator accumulator, Throwables.Predicate conditionToBreak)
            throws E {
        assertNotClosed();

        final MutableBoolean flagToBreak = MutableBoolean.of(false);

        final Throwables.BinaryOperator newAccumulator = (t, u) -> {
            final T ret = accumulator.apply(t, u);

            if (flagToBreak.isFalse() && conditionToBreak.test(ret)) {
                flagToBreak.setValue(true);
            }

            return ret;
        };

        if (this.isParallel()) {
            return psp(s -> s.takeWhile(value -> flagToBreak.isFalse())).reduce(newAccumulator);
        } else {
            return takeWhile(value -> flagToBreak.isFalse()).reduce(newAccumulator);
        }
    }

    @Override
    public  U reduceUntil(U identity, Throwables.BiFunction accumulator, Throwables.BinaryOperator combiner,
            Throwables.Predicate conditionToBreak) throws E {
        assertNotClosed();

        final MutableBoolean flagToBreak = MutableBoolean.of(false);

        final Throwables.BiFunction newAccumulator = (u, t) -> {
            final U ret = accumulator.apply(u, t);

            if (flagToBreak.isFalse() && conditionToBreak.test(ret)) {
                flagToBreak.setValue(true);
            }

            return ret;
        };

        if (this.isParallel()) {
            return psp(s -> s.takeWhile(value -> flagToBreak.isFalse())).reduce(identity, newAccumulator, combiner);
        } else {
            return takeWhile(value -> flagToBreak.isFalse()).reduce(identity, newAccumulator, combiner);
        }
    }

    @Override
    public  Stream>> groupBy(final Function keyMapper) {
        assertNotClosed();

        return groupBy(keyMapper, Suppliers.> ofMap());
    }

    @Override
    public  Stream>> groupBy(final Function keyMapper, Supplier>> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, Fn. identity(), mapFactory);
    }

    @Override
    public  Stream>> groupBy(Function keyMapper, Function valueMapper) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, Suppliers.> ofMap());
    }

    @Override
    public  Stream>> groupBy(Function keyMapper, Function valueMapper,
            Supplier>> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, Collectors. toList(), mapFactory);
    }

    @Override
    public  Stream> groupBy(final Function keyMapper, Collector downstream) {
        assertNotClosed();

        return groupBy(keyMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public  Stream> groupBy(final Function keyMapper, final Collector downstream,
            final Supplier> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, Fn. identity(), downstream, mapFactory);
    }

    @Override
    public  Stream> groupBy(final Function keyMapper, final Function valueMapper,
            Collector downstream) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public  Stream> groupBy(final Function keyMapper, final Function valueMapper,
            final Collector downstream, final Supplier> mapFactory) {
        assertNotClosed();

        return newStream(new ObjIteratorEx>() { //NOSONAR
            private boolean initialized = false;
            private Iterator> iter = null;

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

                return iter.hasNext();
            }

            @Override
            public Entry next() {
                if (!initialized) {
                    init();
                }

                return iter.next();
            }

            private void init() {
                if (!initialized) {
                    initialized = true;

                    iter = AbstractStream.this.groupTo(Fn.from(keyMapper), Fn.from(valueMapper), downstream, mapFactory).entrySet().iterator();
                }
            }
        }, false, null);
    }

    @Override
    public  Stream> groupBy(final Function keyMapper, final Function valueMapper,
            BinaryOperator mergeFunction) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    }

    @Override
    public  Stream> groupBy(final Function keyMapper, final Function valueMapper,
            final BinaryOperator mergeFunction, final Supplier> mapFactory) {
        assertNotClosed();

        return newStream(new ObjIteratorEx>() { //NOSONAR
            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(Fn.from(keyMapper), Fn.from(valueMapper), mergeFunction, mapFactory).entrySet().iterator();
                }
            }
        }, false, null);
    }

    //    @Override
    //    public  Stream>> flatGroupBy(Throwables.Function, E> flatKeyMapper) {
    //        return flatGroupBy(flatKeyMapper, Suppliers.> ofMap());
    //    }
    //
    //    @Override
    //    public  Stream>> flatGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Supplier>> mapFactory) {
    //        return flatGroupBy(flatKeyMapper, BiFunctions. returnSecond(), mapFactory);
    //    }
    //
    //    @Override
    //    public  Stream>> flatGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper) {
    //        return flatGroupBy(flatKeyMapper, valueMapper, Suppliers.> ofMap());
    //    }
    //
    //    @Override
    //    public  Stream>> flatGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Supplier>> mapFactory) {
    //        return flatGroupBy(flatKeyMapper, valueMapper, Collectors. toList(), mapFactory);
    //    }
    //
    //    @Override
    //    public  Stream> flatGroupBy(Throwables.Function, E> flatKeyMapper, Collector downstream) {
    //        return flatGroupBy(flatKeyMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Stream> flatGroupBy(Throwables.Function, E> flatKeyMapper, Collector downstream,
    //            Supplier> mapFactory) {
    //        return flatGroupBy(flatKeyMapper, BiFunctions. returnSecond(), downstream, mapFactory);
    //    }
    //
    //    @Override
    //    public  Stream> flatGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Collector downstream) {
    //        return flatGroupBy(flatKeyMapper, valueMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Stream> flatGroupBy(final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, 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.flatToMap(flatKeyMapper, valueMapper, downstream, mapFactory).entrySet().iterator();
    //                }
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public  Stream> flatGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, BinaryOperator mergeFunction) {
    //        return flatGroupBy(flatKeyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Stream> flatGroupBy(final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction 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.flatToMap(flatKeyMapper, valueMapper, mergeFunction, mapFactory).entrySet().iterator();
    //                }
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public  Stream>> flattGroupBy(Throwables.Function, E> flatKeyMapper) {
    //        return flattGroupBy(flatKeyMapper, Suppliers.> ofMap());
    //    }
    //
    //    @Override
    //    public  Stream>> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Supplier>> mapFactory) {
    //        return flattGroupBy(flatKeyMapper, BiFunctions. returnSecond(), mapFactory);
    //    }
    //
    //    @Override
    //    public  Stream>> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper) {
    //        return flattGroupBy(flatKeyMapper, valueMapper, Suppliers.> ofMap());
    //    }
    //
    //    @Override
    //    public  Stream>> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Supplier>> mapFactory) {
    //        return flattGroupBy(flatKeyMapper, valueMapper, Collectors. toList(), mapFactory);
    //    }
    //
    //    @Override
    //    public  Stream> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Collector downstream) {
    //        return flattGroupBy(flatKeyMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Stream> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Collector downstream, Supplier> mapFactory) {
    //        return flattGroupBy(flatKeyMapper, BiFunctions. returnSecond(), downstream, mapFactory);
    //    }
    //
    //    @Override
    //    public  Stream> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Collector downstream) {
    //        return flattGroupBy(flatKeyMapper, valueMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Stream> flattGroupBy(final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, 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.flattToMap(flatKeyMapper, valueMapper, downstream, mapFactory).entrySet().iterator();
    //                }
    //            }
    //        }, false, null);
    //    }
    //
    //    @Override
    //    public  Stream> flattGroupBy(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, BinaryOperator mergeFunction) {
    //        return flattGroupBy(flatKeyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Stream> flattGroupBy(final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction 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.flattToMap(flatKeyMapper, valueMapper, mergeFunction, mapFactory).entrySet().iterator();
    //                }
    //            }
    //        }, false, null);
    //    }

    @Override
    public Stream>> partitionBy(Predicate predicate) {
        assertNotClosed();

        return partitionBy(predicate, Collectors. toList());
    }

    @Override
    public  Stream> partitionBy(final Predicate predicate, final Collector downstream) {
        assertNotClosed();

        return newStream(new ObjIteratorEx>() { //NOSONAR
            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(Fn.from(predicate), downstream).entrySet().iterator();
                }
            }
        }, false, null);
    }

    @Override
    public EntryStream> partitionByToEntry(Predicate predicate) {
        assertNotClosed();

        return partitionByToEntry(predicate, Collectors. toList());
    }

    @Override
    public  EntryStream partitionByToEntry(Predicate predicate, Collector downstream) {
        assertNotClosed();

        return partitionBy(predicate, downstream).mapToEntry(Fn.> identity());
    }

    @Override
    public  EntryStream> groupByToEntry(Function keyMapper) {
        assertNotClosed();

        return groupByToEntry(keyMapper, Suppliers.> ofMap());
    }

    @Override
    public  EntryStream> groupByToEntry(Function keyMapper, Supplier>> mapFactory) {
        assertNotClosed();

        return groupByToEntry(keyMapper, Fn. identity(), mapFactory);
    }

    @Override
    public  EntryStream> groupByToEntry(Function keyMapper, Function valueMapper) {
        assertNotClosed();

        return groupByToEntry(keyMapper, valueMapper, Suppliers.> ofMap());
    }

    @Override
    public  EntryStream> groupByToEntry(Function keyMapper, Function valueMapper,
            Supplier>> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, mapFactory).mapToEntry(Fn.>> identity());
    }

    @Override
    public  EntryStream groupByToEntry(Function keyMapper, Collector downstream) {
        assertNotClosed();

        return groupByToEntry(keyMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public  EntryStream groupByToEntry(Function keyMapper, Collector downstream,
            Supplier> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, downstream, mapFactory).mapToEntry(Fn.> identity());
    }

    @Override
    public  EntryStream groupByToEntry(final Function keyMapper, final Function valueMapper,
            Collector downstream) {
        assertNotClosed();

        return groupByToEntry(keyMapper, valueMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public  EntryStream groupByToEntry(final Function keyMapper, final Function valueMapper,
            Collector downstream, Supplier> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, downstream, mapFactory).mapToEntry(Fn.> identity());
    }

    @Override
    public  EntryStream groupByToEntry(Function keyMapper, Function valueMapper,
            BinaryOperator mergeFunction) {
        assertNotClosed();

        return groupByToEntry(keyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    }

    @Override
    public  EntryStream groupByToEntry(Function keyMapper, Function valueMapper,
            BinaryOperator mergeFunction, Supplier> mapFactory) {
        assertNotClosed();

        return groupBy(keyMapper, valueMapper, mergeFunction, mapFactory).mapToEntry(Fn.> identity());
    }

    @Override
    public  Map toMap(Throwables.Function keyMapper,
            Throwables.Function valueMapper) throws E, E2 {
        assertNotClosed();

        return toMap(keyMapper, valueMapper, Suppliers. ofMap());
    }

    @Override
    public , E extends Exception, E2 extends Exception> M toMap(Throwables.Function keyMapper,
            Throwables.Function valueMapper, Supplier mapFactory) throws E, E2 {
        assertNotClosed();

        return toMap(keyMapper, valueMapper, Fn. throwingMerger(), mapFactory);
    }

    @Override
    public  Map toMap(Throwables.Function keyMapper,
            Throwables.Function valueMapper, BinaryOperator mergeFunction) throws E, E2 {
        assertNotClosed();

        return toMap(keyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    }

    //    @Override
    //    public  Map flatToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper) {
    //        return flatToMap(flatKeyMapper, valueMapper, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Map flatToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, BinaryOperator mergeFunction) {
    //        return flatToMap(flatKeyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public > M flatToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Supplier mapFactory) {
    //        return flatToMap(flatKeyMapper, valueMapper, Fn. throwingMerger(), mapFactory);
    //    }
    //
    //    @Override
    //    public  Map flatToMap(Throwables.Function, E> flatKeyMapper, Collector downstream) {
    //        return flatToMap(flatKeyMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public > M flatToMap(Throwables.Function, E> flatKeyMapper, Collector downstream,
    //            Supplier mapFactory) {
    //        return flatToMap(flatKeyMapper, BiFunctions. returnSecond(), downstream, mapFactory);
    //    }
    //
    //    @Override
    //    public  Map flatToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Collector downstream) {
    //        return flatToMap(flatKeyMapper, valueMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Map flattToMap(Throwables.Function, E> flatKeyMapper, Collector downstream) {
    //        return flattToMap(flatKeyMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public > M flattToMap(Throwables.Function, E> flatKeyMapper,
    //            Collector downstream, Supplier mapFactory) {
    //        return flattToMap(flatKeyMapper, BiFunctions. returnSecond(), downstream, mapFactory);
    //    }
    //
    //    @Override
    //    public  Map flattToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper) {
    //        return flattToMap(flatKeyMapper, valueMapper, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public  Map flattToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, BinaryOperator mergeFunction) {
    //        return flattToMap(flatKeyMapper, valueMapper, mergeFunction, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public > M flattToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Supplier mapFactory) {
    //        return flattToMap(flatKeyMapper, valueMapper, Fn. throwingMerger(), mapFactory);
    //    }
    //
    //    @Override
    //    public > M flattToMap(final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, final BinaryOperator mergeFunction, final Supplier mapFactory) {
    //        return flatToMap(new Function>() {
    //            @Override
    //            public Stream apply(T t) {
    //                return Stream.of(flatKeyMapper.apply(t));
    //            }
    //        }, valueMapper, mergeFunction, mapFactory);
    //    }
    //
    //    @Override
    //    public  Map flattToMap(Throwables.Function, E> flatKeyMapper,
    //            Throwables.BiFunction valueMapper, Collector downstream) {
    //        return flattToMap(flatKeyMapper, valueMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public > M flattToMap(final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, final Collector downstream,
    //            final Supplier mapFactory) {
    //        return flatToMap(new Function>() {
    //            @Override
    //            public Stream apply(T t) {
    //                return Stream.of(flatKeyMapper.apply(t));
    //            }
    //        }, valueMapper, downstream, mapFactory);
    //    }

    @Override
    public  Map> groupTo(Throwables.Function keyMapper) throws E {
        assertNotClosed();

        return groupTo(keyMapper, Suppliers.> ofMap());
    }

    @Override
    public >, E extends Exception> M groupTo(Throwables.Function keyMapper,
            final Supplier mapFactory) throws E {
        assertNotClosed();

        return groupTo(keyMapper, Collectors. toList(), mapFactory);
    }

    @Override
    public  Map> groupTo(Throwables.Function keyMapper,
            Throwables.Function valueMapper) throws E, E2 {
        assertNotClosed();

        return groupTo(keyMapper, valueMapper, Suppliers.> ofMap());
    }

    @Override
    public >, E extends Exception, E2 extends Exception> M groupTo(Throwables.Function keyMapper,
            Throwables.Function valueMapper, Supplier mapFactory) throws E, E2 {
        assertNotClosed();

        return groupTo(keyMapper, valueMapper, Collectors. toList(), mapFactory);
    }

    @Override
    public  Map groupTo(Throwables.Function keyMapper,
            final Collector downstream) throws E {
        assertNotClosed();

        return groupTo(keyMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public , E extends Exception> M groupTo(Throwables.Function keyMapper,
            final Collector downstream, final Supplier mapFactory) throws E {
        assertNotClosed();

        return groupTo(keyMapper, Fn. identity(), downstream, mapFactory);
    }

    @Override
    public  Map groupTo(Throwables.Function keyMapper,
            Throwables.Function valueMapper, final Collector downstream) throws E, E2 {
        assertNotClosed();

        return groupTo(keyMapper, valueMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public , E extends Exception, E2 extends Exception> M groupTo(Throwables.Function keyMapper,
            Throwables.Function valueMapper, final Collector downstream, final Supplier mapFactory)
            throws E, E2 {
        assertNotClosed();

        try {
            final ObjIteratorEx iter = iteratorEx();
            final M result = mapFactory.get();
            final java.util.function.Supplier downstreamSupplier = downstream.supplier();
            final java.util.function.BiConsumer downstreamAccumulator = downstream.accumulator();
            final Map intermediate = (Map) result;
            K key = null;
            A v = null;
            T next = null;

            while (iter.hasNext()) {
                next = iter.next();
                key = checkArgNotNull(keyMapper.apply(next), "element cannot be mapped to a null key");

                if (((v = intermediate.get(key)) == null) && ((v = downstreamSupplier.get()) != null)) {
                    intermediate.put(key, v);
                }

                downstreamAccumulator.accept(v, valueMapper.apply(next));
            }

            final BiFunction function = (k, v1) -> (A) downstream.finisher().apply(v1);

            Collectors.replaceAll(intermediate, function);

            return result;
        } finally {
            close();
        }
    }

    //    @Override
    //    public  Map> flatGroupTo(final Throwables.Function, E> flatKeyMapper) throws E {
    //        return flatGroupTo(flatKeyMapper, Suppliers.> ofMap());
    //    }
    //
    //    @Override
    //    public >, E extends Exception> M flatGroupTo(
    //            final Throwables.Function, E> flatKeyMapper, final Supplier mapFactory) throws E {
    //        return flatGroupTo(flatKeyMapper, BiFunctions. selectSecond(), mapFactory);
    //    }
    //
    //    @Override
    //    public  Map> flatGroupTo(
    //            final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper) throws E, E2 {
    //        return flatGroupTo(flatKeyMapper, valueMapper, Suppliers.> ofMap());
    //    }
    //
    //    @Override
    //    public >, E extends Exception, E2 extends Exception> M flatGroupTo(
    //            final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, final Supplier mapFactory) throws E, E2 {
    //        return flatGroupTo(flatKeyMapper, valueMapper, Collectors. toList(), mapFactory);
    //    }

    @Override
    public  Map> flatGroupTo(final Throwables.Function, E> flatKeyMapper)
            throws E {
        return flatGroupTo(flatKeyMapper, Suppliers.> ofMap());
    }

    @Override
    public >, E extends Exception> M flatGroupTo(
            final Throwables.Function, E> flatKeyMapper, final Supplier mapFactory) throws E {
        return flatGroupTo(flatKeyMapper, BiFunctions. selectSecond(), mapFactory);
    }

    @Override
    public  Map> flatGroupTo(
            final Throwables.Function, E> flatKeyMapper,
            final Throwables.BiFunction valueMapper) throws E, E2 {
        return flatGroupTo(flatKeyMapper, valueMapper, Suppliers.> ofMap());
    }

    @Override
    public >, E extends Exception, E2 extends Exception> M flatGroupTo(
            final Throwables.Function, E> flatKeyMapper,
            final Throwables.BiFunction valueMapper, final Supplier mapFactory) throws E, E2 {
        return flatGroupTo(flatKeyMapper, valueMapper, Collectors. toList(), mapFactory);
    }

    //    @Override
    //    public  Map flatGroupTo(final Throwables.Function, E> flatKeyMapper,
    //            final Collector downstream) throws E {
    //        return flatGroupTo(flatKeyMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public , E extends Exception> M flatGroupTo(
    //            final Throwables.Function, E> flatKeyMapper, final Collector downstream,
    //            Supplier mapFactory) throws E {
    //        return flatGroupTo(flatKeyMapper, BiFunctions. selectSecond(), downstream, mapFactory);
    //    }
    //
    //    @Override
    //    public  Map flatGroupTo(
    //            final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, final Collector downstream) throws E, E2 {
    //        return flatGroupTo(flatKeyMapper, valueMapper, downstream, Suppliers. ofMap());
    //    }
    //
    //    @Override
    //    public , E extends Exception, E2 extends Exception> M flatGroupTo(
    //            final Throwables.Function, E> flatKeyMapper,
    //            final Throwables.BiFunction valueMapper, final Collector downstream,
    //            Supplier mapFactory) throws E, E2 {
    //        assertNotClosed();
    //
    //        try {
    //            final ObjIteratorEx iter = iteratorEx();
    //            final M result = mapFactory.get();
    //            final Supplier downstreamSupplier = downstream.supplier();
    //            final BiConsumer downstreamAccumulator = downstream.accumulator();
    //            final Function downstreamFinisher = downstream.finisher();
    //            final Map intermediate = (Map) result;
    //
    //            ObjIterator keyIter = null;
    //            K k = null;
    //            A v = null;
    //
    //            T next = null;
    //
    //            while (iter.hasNext()) {
    //                next = iter.next();
    //
    //                try (Stream ks = flatKeyMapper.apply(next)) {
    //                    keyIter = ks.iterator();
    //
    //                    while (keyIter.hasNext()) {
    //                        k = checkArgNotNull(keyIter.next(), "element cannot be mapped to a null key");
    //
    //                        if ((v = intermediate.get(k)) == null) {
    //                            if ((v = downstreamSupplier.get()) != null) {
    //                                intermediate.put(k, v);
    //                            }
    //                        }
    //
    //                        downstreamAccumulator.accept(v, valueMapper.apply(k, next));
    //                    }
    //                }
    //            }
    //
    //            final BiFunction function = new BiFunction() {
    //                @Override
    //                public A apply(K k, A v) {
    //                    return (A) downstreamFinisher.apply(v);
    //                }
    //            };
    //
    //            Collectors.replaceAll(intermediate, function);
    //
    //            return result;
    //        } finally {
    //            close();
    //        }
    //    }

    @Override
    public  Map flatGroupTo(final Throwables.Function, E> flatKeyMapper,
            final Collector downstream) throws E {
        return flatGroupTo(flatKeyMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public , E extends Exception> M flatGroupTo(
            final Throwables.Function, E> flatKeyMapper, final Collector downstream,
            final Supplier mapFactory) throws E {
        return flatGroupTo(flatKeyMapper, BiFunctions. selectSecond(), downstream, mapFactory);
    }

    @Override
    public  Map flatGroupTo(
            final Throwables.Function, E> flatKeyMapper,
            final Throwables.BiFunction valueMapper, final Collector downstream) throws E, E2 {
        return flatGroupTo(flatKeyMapper, valueMapper, downstream, Suppliers. ofMap());
    }

    @Override
    public , E extends Exception, E2 extends Exception> M flatGroupTo(
            final Throwables.Function, E> flatKeyMapper,
            final Throwables.BiFunction valueMapper, final Collector downstream,
            Supplier mapFactory) throws E, E2 {
        assertNotClosed();

        try {
            final ObjIteratorEx iter = iteratorEx();
            final M result = mapFactory.get();
            final java.util.function.Supplier downstreamSupplier = downstream.supplier();
            final java.util.function.BiConsumer downstreamAccumulator = downstream.accumulator();
            final java.util.function.Function downstreamFinisher = downstream.finisher();
            final Map intermediate = (Map) result;

            Collection ks = null;
            A v = null;

            T next = null;

            while (iter.hasNext()) {
                next = iter.next();
                ks = flatKeyMapper.apply(next);

                if (N.notNullOrEmpty(ks)) {
                    for (K k : ks) {
                        checkArgNotNull(k, "element cannot be mapped to a null key");

                        if (((v = intermediate.get(k)) == null) && ((v = downstreamSupplier.get()) != null)) {
                            intermediate.put(k, v);
                        }

                        downstreamAccumulator.accept(v, valueMapper.apply(k, next));
                    }
                }
            }

            final BiFunction function = (k, v1) -> (A) downstreamFinisher.apply(v1);

            Collectors.replaceAll(intermediate, function);

            return result;
        } finally {
            close();
        }
    }

    @Override
    public  Map> partitionTo(final Throwables.Predicate predicate) throws E {
        assertNotClosed();

        return partitionTo(predicate, Collectors. toList());
    }

    @Override
    public  Map partitionTo(final Throwables.Predicate predicate,
            final Collector downstream) throws E {
        assertNotClosed();

        final Throwables.Function keyMapper = predicate::test;

        final Supplier> mapFactory = () -> N. newHashMap(2);

        final Map map = groupTo(keyMapper, downstream, mapFactory);

        if (!map.containsKey(Boolean.TRUE)) {
            map.put(Boolean.TRUE, downstream.finisher().apply(downstream.supplier().get()));
        } else if (!map.containsKey(Boolean.FALSE)) {
            map.put(Boolean.FALSE, downstream.finisher().apply(downstream.supplier().get()));
        }

        return map;
    }

    @Override
    public  ListMultimap toMultimap(Throwables.Function keyMapper) throws E {
        assertNotClosed();

        return toMultimap(keyMapper, Suppliers. ofListMultimap());
    }

    @Override
    public , M extends Multimap, E extends Exception> M toMultimap(Throwables.Function keyMapper,
            Supplier mapFactory) throws E {
        assertNotClosed();

        return toMultimap(keyMapper, Fn. identity(), mapFactory);
    }

    @Override
    public  ListMultimap toMultimap(Throwables.Function keyMapper,
            Throwables.Function valueMapper) throws E, E2 {
        assertNotClosed();

        return toMultimap(keyMapper, valueMapper, Suppliers. ofListMultimap());
    }

    @Override
    public long sumInt(ToIntFunction mapper) {
        assertNotClosed();

        return collect(Collectors.summingInt(mapper));
    }

    @Override
    public long sumLong(ToLongFunction mapper) {
        assertNotClosed();

        return collect(Collectors.summingLong(mapper));
    }

    @Override
    public double sumDouble(ToDoubleFunction mapper) {
        assertNotClosed();

        return collect(Collectors.summingDouble(mapper));
    }

    @Override
    public OptionalDouble averageInt(ToIntFunction mapper) {
        assertNotClosed();

        return collect(Collectors.averagingInt(mapper));
    }

    @Override
    public OptionalDouble averageLong(ToLongFunction mapper) {
        assertNotClosed();

        return collect(Collectors.averagingLong(mapper));
    }

    @Override
    public OptionalDouble averageDouble(ToDoubleFunction mapper) {
        assertNotClosed();

        return collect(Collectors.averagingDouble(mapper));
    }

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

    @Override
    public  Optional findFirstOrAny(Throwables.Predicate predicateForFirst) throws E {
        assertNotClosed();

        final T none = (T) NONE;

        try {
            final ObjIteratorEx iter = iteratorEx();
            T next = null;
            T ret = none;

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

                if (predicateForFirst.test(next)) {
                    return Optional.of(next);
                } else if (ret == none) {
                    ret = next;
                }
            }

            return ret == none ? Optional. empty() : Optional.of(ret);
        } finally {
            close();
        }
    }

    @Override
    public  Optional findFirstOrAny(final Throwables.Predicate predicateForFirst,
            final Throwables.Predicate predicateForAny) throws E, E2 {
        assertNotClosed();

        try {
            final ObjIteratorEx iter = iteratorEx();
            T ret = (T) NONE;
            T next = null;

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

                if (predicateForFirst.test(next)) {
                    return Optional.of(next);
                } else if (ret == NONE && predicateForAny.test(next)) {
                    ret = next;
                }
            }

            return ret == NONE ? Optional. empty() : Optional.of(ret);
        } finally {
            close();
        }
    }

    @Override
    public  Optional findFirstOrLast(Throwables.Predicate predicateForFirst) throws E {
        assertNotClosed();

        final T none = (T) NONE;

        try {
            final ObjIteratorEx iter = iteratorEx();
            T next = null;
            T ret = none;

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

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

            return ret == none ? Optional. empty() : Optional.of(ret);
        } finally {
            close();
        }
    }

    @Override
    public  Optional findFirstOrLast(final Throwables.Predicate predicateForFirst,
            final Throwables.Predicate predicateForLast) throws E, E2 {
        assertNotClosed();

        try {
            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. empty() : Optional.of(last);
        } finally {
            close();
        }
    }

    @Override
    public  Optional findFirstOrLast(final U init,
            final Throwables.BiPredicate predicateForFirst, final Throwables.BiPredicate predicateForLast)
            throws E, E2 {
        assertNotClosed();

        try {
            final ObjIteratorEx iter = iteratorEx();
            T last = (T) NONE;
            T next = null;

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

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

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

    @Override
    public  Optional findFirstOrLast(final Function preFunc,
            final Throwables.BiPredicate predicateForFirst, final Throwables.BiPredicate predicateForLast)
            throws E, E2 {
        assertNotClosed();

        try {
            final ObjIteratorEx iter = iteratorEx();
            U init = null;
            T last = (T) NONE;
            T next = null;

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

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

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

    @Override
    @SafeVarargs
    public final boolean containsAll(final T... a) {
        assertNotClosed();

        try {
            if (N.isNullOrEmpty(a)) {
                return true;
            } else if (a.length == 1 || (a.length == 2 && N.equals(a[0], a[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));
            }
        } finally {
            close();
        }
    }

    @Override
    public boolean containsAll(final Collection c) {
        assertNotClosed();

        try {
            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);
                final int distinctCount = set.size();

                return filter(set::contains).distinct().limit(distinctCount).count() == distinctCount;
            }
        } finally {
            close();
        }
    }

    @Override
    @SafeVarargs
    public final boolean containsAny(final T... a) {
        assertNotClosed();

        try {
            if (N.isNullOrEmpty(a)) {
                return false;
            } else if (a.length == 1 || (a.length == 2 && N.equals(a[0], a[1]))) {
                return anyMatch(Fn.equal(a[0]));
            } else if (a.length == 2) {
                return anyMatch(new com.landawn.abacus.util.function.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);
                    }
                });
            } else {
                final Set set = N.asSet(a);

                return anyMatch(set::contains);
            }
        } finally {
            close();
        }
    }

    @Override
    public boolean containsAny(final Collection c) {
        assertNotClosed();

        try {
            if (N.isNullOrEmpty(c)) {
                return false;
            } 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 anyMatch(set::contains);
            }
        } finally {
            close();
        }
    }

    @Override
    public Optional first() {
        assertNotClosed();

        try {
            final Iterator iter = this.iteratorEx();

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

            return Optional.of(iter.next());
        } finally {
            close();
        }
    }

    @Override
    public Optional last() {
        assertNotClosed();

        try {
            final Iterator iter = this.iteratorEx();

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

            T next = iter.next();

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

            return Optional.of(next);
        } finally {
            close();
        }
    }

    @Override
    public Optional onlyOne() throws TooManyElementsException {
        assertNotClosed();

        try {
            final Iterator iter = this.iteratorEx();

            final Optional result = iter.hasNext() ? Optional.of(iter.next()) : Optional. empty();

            if (result.isPresent() && iter.hasNext()) {
                throw new TooManyElementsException("There are at least two elements: " + StringUtil.concat(result.get(), ", ", iter.next()));
            }

            return result;
        } finally {
            close();
        }
    }

    @Override
    public List minAll(Comparator comparator) {
        assertNotClosed();

        if (isParallel()) {
            return collect(Collectors.minAll(comparator));
        } else {
            try {
                final ObjIteratorEx iter = this.iteratorEx();
                final List result = new ArrayList<>();

                if (!iter.hasNext()) {
                    return result;
                }

                T candicate = iter.next();
                result.add(candicate);

                if (sorted && isSameComparator(cmp, comparator)) {
                    T next = null;

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

                        if (cmp.compare(next, candicate) == 0) {
                            result.add(next);
                        } else {
                            break;
                        }
                    }
                } else {
                    final Comparator cmp = comparator == null ? NULL_MIN_COMPARATOR : comparator;
                    T next = null;
                    int cp = 0;

                    while (iter.hasNext()) {
                        next = iter.next();
                        cp = cmp.compare(next, candicate);

                        if (cp == 0) {
                            result.add(next);
                        } else if (cp < 0) {
                            result.clear();
                            result.add(next);
                            candicate = next;
                        }
                    }
                }

                return result;
            } finally {
                close();
            }
        }
    }

    @Override
    public List maxAll(Comparator comparator) {
        assertNotClosed();

        if (isParallel()) {
            return collect(Collectors.maxAll(comparator));
        } else {
            try {
                final ObjIteratorEx iter = this.iteratorEx();
                final List result = new ArrayList<>();

                if (!iter.hasNext()) {
                    return result;
                }

                T candicate = iter.next();
                result.add(candicate);

                final Comparator cmp = comparator == null ? NULL_MIN_COMPARATOR : comparator;

                T next = null;
                int cp = 0;

                while (iter.hasNext()) {
                    next = iter.next();
                    cp = cmp.compare(next, candicate);

                    if (cp == 0) {
                        result.add(next);
                    } else if (cp > 0) {
                        result.clear();
                        result.add(next);
                        candicate = next;
                    }
                }

                return result;
            } finally {
                close();
            }
        }
    }

    /**
     * @deprecated Use {@link #skipNulls()} instead
     */
    @Override
    public Stream skipNull() {
        return skipNulls();
    }

    @Override
    public Stream skipNulls() {
        if (this.isParallel()) {
            return sequential().filter(Fn.notNull())
                    .parallel(maxThreadNum(), executorNumForVirtualThread(), splitor(), asyncExecutor(), cancelUncompletedThreads());
        } else {
            return filter(Fn.notNull());
        }
    }

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

    @Override
    public Stream intersection(final Collection c) {
        assertNotClosed();

        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(value -> multiset.getAndRemove(value) > 0).iteratorEx(), sorted, cmp);
    }

    @Override
    public  Stream intersection(final Function mapper, final Collection c) {
        assertNotClosed();

        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(value -> multiset.getAndRemove(mapper.apply(value)) > 0).iteratorEx(), sorted, cmp);
    }

    @Override
    public Stream difference(final Collection c) {
        assertNotClosed();

        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(value -> multiset.getAndRemove(value) < 1).iteratorEx(), sorted, cmp);
    }

    @Override
    public  Stream difference(final Function mapper, final Collection c) {
        assertNotClosed();

        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential().filter(value -> multiset.getAndRemove(mapper.apply(value)) < 1).iteratorEx(), sorted, cmp);
    }

    @Override
    public Stream symmetricDifference(final Collection c) {
        assertNotClosed();

        final Multiset multiset = Multiset.from(c);

        return newStream(this.sequential()
                .filter(value -> multiset.getAndRemove(value) < 1)
                .append(Stream.of(c).filter(value -> multiset.getAndRemove(value) > 0))
                .iteratorEx(), false, null);
    }

    @Override
    public Stream reversed() {
        assertNotClosed();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private boolean initialized = false;

            private T[] elements;
            private int fromIndex = -1;
            private int toIndex = -1;

            private int cursor;

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

                return cursor > fromIndex;
            }

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

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

                return elements[--cursor];
            }

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

                return cursor - fromIndex; //NOSONAR
            }

            @Override
            public void advance(long n) {
                if (!initialized) {
                    init();
                }

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

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

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

                for (int i = 0, len = cursor - fromIndex; i < len; i++) {
                    a[i] = (A) elements[cursor - i - 1];
                }

                return a;
            }

            private void init() {
                if (!initialized) {
                    initialized = true;

                    final Tuple3 tp = AbstractStream.this.arrayForIntermediateOp();

                    elements = (T[]) tp._1;
                    fromIndex = tp._2;
                    toIndex = tp._3;

                    cursor = toIndex;
                }
            }
        }, false, null);
    }

    @Override
    public Stream rotated(final int distance) {
        assertNotClosed();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private boolean initialized = false;

            private T[] elements;
            private int fromIndex = -1;
            private int toIndex = -1;

            private int len;
            private int start;
            private int cnt = 0;

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

                return cnt < len;
            }

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

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

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

                return len - cnt; //NOSONAR
            }

            @Override
            public void advance(long n) {
                if (!initialized) {
                    init();
                }

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

            @Override
            public  A[] toArray(A[] a) {
                if (!initialized) {
                    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) elements[((start + i) % len) + fromIndex];
                }

                return a;
            }

            private void init() {
                if (!initialized) {
                    initialized = true;

                    final Tuple3 tp = AbstractStream.this.arrayForIntermediateOp();

                    elements = (T[]) tp._1;
                    fromIndex = tp._2;
                    toIndex = tp._3;

                    len = toIndex - fromIndex;

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

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

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

    @Override
    public Stream shuffled(final Random rnd) {
        assertNotClosed();

        checkArgNotNull(rnd, "random");

        return lazyLoad(a -> {
            N.shuffle(a, rnd);
            return a;
        }, false, null);
    }

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

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

    @Override
    public Stream reverseSorted(final Comparator comparator) {
        final Comparator cmp = comparator == null ? REVERSED_COMPARATOR : comparator.reversed();

        return sorted(cmp);
    }

    @Override
    public Stream sorted(final Comparator comparator) {
        assertNotClosed();

        final Comparator cmp = comparator == null ? NATURAL_COMPARATOR : comparator;

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

        return lazyLoad(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() { //NOSONAR
            private boolean initialized = false;
            private T[] aar;
            private int cursor = 0;
            private int len;

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

                return cursor < len;
            }

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

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

                return aar[cursor++];
            }

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

                return len - cursor; //NOSONAR
            }

            @Override
            public void advance(long n) {
                if (!initialized) {
                    init();
                }

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

            @Override
            public  A[] toArray(A[] a) {
                if (!initialized) {
                    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) {
                    initialized = true;
                    aar = (T[]) op.apply(AbstractStream.this.toArrayForIntermediateOp());
                    len = aar.length;
                }
            }
        }, sorted, cmp);
    }

    @Override
    public Stream distinct() {
        assertNotClosed();

        final Set set = N.newHashSet();

        return newStream(this.sequential().filter(value -> set.add(hashKey(value))).iteratorEx(), sorted, cmp);
    }

    @Override
    public Stream distinctBy(final Function keyMapper) {
        assertNotClosed();

        //    final Set set = N.newHashSet();
        //
        //    final Predicate predicate = isParallel() ? new Predicate() {
        //        @Override
        //        public boolean test(T value) {
        //            final Object key = hashKey(keyMapper.apply(value));
        //
        //            synchronized (set) {
        //                return set.add(key);
        //            }
        //        }
        //    } : new Predicate() {
        //        @Override
        //        public boolean test(T value) {
        //            return set.add(hashKey(keyMapper.apply(value)));
        //        }
        //    };

        final Predicate predicate = new Predicate<>() {
            private final Set set = isParallel() ? ConcurrentHashMap.newKeySet() : N.newHashSet();

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

        return filter(predicate);
    }

    @Override
    public Stream top(int n) {
        assertNotClosed();

        return top(n, NATURAL_COMPARATOR);
    }

    @Override
    public Optional> percentiles() {
        assertNotClosed();

        try {
            final Object[] a = sorted().toArray();

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

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

    @Override
    public Optional> percentiles(Comparator comparator) {
        assertNotClosed();

        try {
            final Object[] a = sorted(comparator).toArray();

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

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

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

    @Override
    public Stream> combinations() {
        assertNotClosed();

        if (this instanceof ArrayStream) {
            @SuppressWarnings("resource")
            final ArrayStream s = ((ArrayStream) this);
            final int count = s.toIndex - s.fromIndex;

            return newStream(IntStream.rangeClosed(0, count).flatMapToObj(this::combinations).iteratorEx(), false, null);
        } else {
            return newStream((T[]) toArray(), false, null).combinations(); //NOSONAR
        }
    }

    @Override
    public Stream> combinations(final int len) {
        assertNotClosed();

        if (this instanceof ArrayStream) {
            @SuppressWarnings("resource")
            final ArrayStream s = ((ArrayStream) this);
            final int count = s.toIndex - s.fromIndex;
            checkFromIndexSize(0, len, count);

            if (len == 0) {
                return newStream(N.asArray(N. emptyList()), false, null);
            } else if (len == 1) {
                return map(N::asList);
            } else if (len == count) {
                return newStream(N.asArray(toList()), false, null);
            } else {
                final T[] a = s.elements;
                final int fromIndex = s.fromIndex;
                final int toIndex = s.toIndex;

                return newStream(new ObjIteratorEx>() { //NOSONAR
                    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); //NOSONAR
        }
    }

    @Override
    public Stream> combinations(final int len, final boolean repeat) {
        assertNotClosed();

        if (!repeat) {
            return combinations(len);
        } else {
            return newStream(new ObjIteratorEx>() { //NOSONAR
                private boolean initialized = false;
                private List> list = null;
                private int size = 0;
                private int cursor = 0;

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

                    return cursor < size;
                }

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

                    return list.get(cursor++);
                }

                @Override
                public void advance(long n) {
                    if (!initialized) {
                        init();
                    }

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

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

                    return size - cursor; //NOSONAR
                }

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

    @Override
    public Stream> permutations() {
        assertNotClosed();

        return newStream(PermutationIterator.of(toList()), false, null);
    }

    @Override
    public Stream> orderedPermutations() {
        assertNotClosed();

        return orderedPermutations(NATURAL_COMPARATOR);
    }

    @Override
    public Stream> orderedPermutations(Comparator comparator) {
        assertNotClosed();

        final Iterator> iter = PermutationIterator.ordered(toList(), comparator == null ? NATURAL_COMPARATOR : comparator);

        return newStream(iter, false, null);
    }

    @Override
    public Stream> cartesianProduct(final Collection> cs) {
        assertNotClosed();

        final List> cList = new ArrayList<>(cs.size() + 1);
        cList.add(this.toList());
        cList.addAll(cs);

        return newStream(new ObjIteratorEx>() { //NOSONAR
            private boolean initialized = false;
            private List> list = null;
            private int size = 0;
            private int cursor = 0;

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

                return cursor < size;
            }

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

                return list.get(cursor++);
            }

            @Override
            public void advance(long n) {
                if (!initialized) {
                    init();
                }

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

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

                return size - cursor; //NOSONAR
            }

            private void init() {
                if (!initialized) {
                    initialized = true;
                    list = Iterables.cartesianProduct(cList);
                    size = list.size();
                }
            }

        }, false, null);
    }

    @Override
    public  A[] toArray(IntFunction generator) {
        assertNotClosed();

        try {
            final Object[] src = toArray();
            final A[] res = generator.apply(src.length);
            System.arraycopy(src, 0, res, 0, src.length);
            return res;
        } finally {
            close();
        }
    }

    @Override
    public DataSet toDataSet() {
        assertNotClosed();

        return N.newDataSet(toList());
    }

    @Override
    public DataSet toDataSet(List columnNames) {
        assertNotClosed();

        return N.newDataSet(columnNames, toList());
    }

    @Override
    public String join(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
        assertNotClosed();

        try {
            final Joiner joiner = Joiner.with(delimiter, prefix, suffix).reuseCachedBuffer();
            final IteratorEx iter = this.iteratorEx();

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

            return joiner.toString();
        } finally {
            close();
        }
    }

    @Override
    public String join(final Joiner joiner) {
        assertNotClosed();

        checkArgNotNull(joiner, "joiner");

        try {
            final IteratorEx iter = this.iteratorEx();

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

            return joiner.toString();
        } finally {
            close();
        }
    }

    @Override
    public boolean hasDuplicates() {
        assertNotClosed();

        try {
            final Set set = N.newHashSet();
            final Iterator iter = iteratorEx();

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

            return false;
        } finally {
            close();
        }
    }

    @Override
    public  R collect(Supplier supplier, BiConsumer accumulator) {
        assertNotClosed();

        final BiConsumer combiner = collectingCombiner;

        return collect(supplier, accumulator, combiner);
    }

    @Override
    public  RR collectAndThen(Collector downstream, Throwables.Function func)
            throws E {
        assertNotClosed();

        return func.apply(collect(downstream));
    }

    @Override
    public  R toListAndThen(final Throwables.Function, ? extends R, E> func) throws E {
        assertNotClosed();

        return func.apply(toList());
    }

    @Override
    public  R toSetAndThen(final Throwables.Function, ? extends R, E> func) throws E {
        assertNotClosed();

        return func.apply(toSet());
    }

    @Override
    public , E extends Exception> R toCollectionAndThen(final Supplier supplier,
            final Throwables.Function func) throws E {
        assertNotClosed();

        return func.apply(toCollection(supplier));
    }

    @Override
    public Stream> indexed() {
        assertNotClosed();

        final MutableLong idx = MutableLong.of(0);

        return newStream(this.sequential().map(t -> Indexed.of(t, idx.getAndIncrement())).iteratorEx(), true, INDEXED_COMPARATOR);
    }

    @Override
    public Stream cycled() {
        assertNotClosed();

        return newStream(new ObjIteratorEx() { //NOSONAR
            private Iterator iter = null;
            private List list = null;
            private T[] a = null;
            private int len = 0;
            private int cursor = -1;
            private T e = null;

            private boolean initialized = false;

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

                if (a == null && !iter.hasNext()) {
                    a = (T[]) list.toArray();
                    len = a.length;
                    cursor = 0;
                }

                return cursor < len || iter.hasNext();
            }

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

                if (len > 0) {
                    if (cursor >= len) {
                        cursor = 0;
                    }

                    return a[cursor++];
                } else {
                    e = iter.next();
                    list.add(e);

                    return e;
                }
            }

            private void init() {
                if (!initialized) {
                    initialized = true;
                    iter = AbstractStream.this.iteratorEx();
                    list = new ArrayList<>();
                }
            }
        }, false, null);
    }

    @Override
    public Stream cycled(final long rounds) {
        assertNotClosed();

        checkArgNotNegative(rounds, "times");

        if (rounds == 0) {
            return limit(0);
        } else if (rounds == 1) {
            return skip(0);
        }

        return newStream(new ObjIteratorEx() { //NOSONAR
            private Iterator iter = null;
            private List list = null;
            private T[] a = null;
            private int len = 0;
            private int cursor = -1;
            private T e = null;
            private long m = 0;

            private boolean initialized = false;

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

                if (a == null && !iter.hasNext()) {
                    a = (T[]) list.toArray();
                    len = a.length;
                    cursor = 0;
                    m = 1;
                }

                return m < rounds && (cursor < len || rounds - m > 1) && (len > 0 || iter.hasNext());
            }

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

                if (len > 0) {
                    if (cursor >= len) {
                        cursor = 0;
                        m++;
                    }

                    return a[cursor++];
                } else {
                    e = iter.next();
                    list.add(e);

                    return e;
                }
            }

            private void init() {
                if (!initialized) {
                    initialized = true;
                    iter = AbstractStream.this.iteratorEx();
                    list = new ArrayList<>();
                }
            }
        }, rounds <= 1 ? this.sorted : false, rounds <= 1 ? this.cmp : null);
    }

    @Override
    public Stream> rollup() {
        assertNotClosed();

        return newStream(new ObjIteratorEx>() { //NOSONAR
            private boolean initialized = false;
            private List elements;
            private int toIndex = -1;
            private int cursor = 0;

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

                return cursor < toIndex;
            }

            @Override
            public List next() {
                if (!initialized) {
                    init();
                }

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

                return elements.subList(0, cursor++);
            }

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

                return toIndex - cursor; //NOSONAR
            }

            @Override
            public void advance(long n) {
                if (!initialized) {
                    init();
                }

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

            private void init() {
                if (!initialized) {
                    initialized = true;

                    final Tuple3 tp = AbstractStream.this.arrayForIntermediateOp();

                    elements = Arrays.asList((T[]) tp._1).subList(tp._2, tp._3);
                    toIndex = elements.size() + 1;
                }
            }
        }, false, null);
    }

    @Override
    public Stream buffered() {
        assertNotClosed();

        return buffered(DEFAULT_BUFFERED_SIZE_PER_ITERATOR);
    }

    @Override
    public Stream append(Stream stream) {
        assertNotClosed();

        return Stream.concat(this, stream);
    }

    @Override
    public Stream append(Collection c) {
        assertNotClosed();

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

    @Override
    public Stream append(final Optional op) {
        assertNotClosed();

        return prepend(op.stream());
    }

    //    @Override
    //    public Stream appendAlll(Collection> cs) {
    //        return append(Stream.of(cs).flatmap(Fn.> identity()));
    //    }

    @Override
    public Stream prepend(Stream stream) {
        assertNotClosed();

        return Stream.concat(stream, this);
    }

    @Override
    public Stream prepend(Collection c) {
        assertNotClosed();

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

    @Override
    public Stream prepend(final Optional op) { //NOSONAR
        assertNotClosed();

        return prepend(op.stream());
    }

    //    @Override
    //    public Stream prependAlll(Collection> cs) {
    //        return prepend(Stream.of(cs).flatmap(Fn.> identity()));
    //    }

    @Override
    public Stream mergeWith(final Collection b, final BiFunction nextSelector) {
        assertNotClosed();

        return Stream.merge(this.iteratorEx(), N.iterate(b), nextSelector).onClose(newCloseHandler(this));
    }

    @Override
    public Stream mergeWith(final Stream b, final BiFunction nextSelector) {
        assertNotClosed();

        return Stream.merge(this, b, nextSelector);
    }

    @Override
    public  Stream zipWith(Collection b, BiFunction zipFunction) {
        assertNotClosed();

        return Stream. zip(this.iteratorEx(), N.iterate(b), zipFunction).onClose(newCloseHandler(this));
    }

    @Override
    public  Stream zipWith(Collection b, T valueForNoneA, T2 valueForNoneB, BiFunction zipFunction) {
        assertNotClosed();

        return Stream. zip(this.iteratorEx(), N.iterate(b), valueForNoneA, valueForNoneB, zipFunction).onClose(newCloseHandler(this));
    }

    @Override
    public  Stream zipWith(Collection b, Collection c, TriFunction zipFunction) {
        assertNotClosed();

        return Stream. zip(this.iteratorEx(), N.iterate(b), N.iterate(c), zipFunction).onClose(newCloseHandler(this));
    }

    @Override
    public  Stream zipWith(Collection b, Collection c, T valueForNoneA, T2 valueForNoneB, T3 valueForNoneC,
            TriFunction zipFunction) {
        assertNotClosed();

        return Stream. zip(this.iteratorEx(), N.iterate(b), N.iterate(c), valueForNoneA, valueForNoneB, valueForNoneC, zipFunction)
                .onClose(newCloseHandler(this));
    }

    @Override
    public  Stream zipWith(Stream b, BiFunction zipFunction) {
        assertNotClosed();

        return Stream.zip(this, b, zipFunction);
    }

    @Override
    public  Stream zipWith(Stream b, T valueForNoneA, T2 valueForNoneB, BiFunction zipFunction) {
        assertNotClosed();

        return Stream.zip(this, b, valueForNoneA, valueForNoneB, zipFunction);
    }

    @Override
    public  Stream zipWith(Stream b, Stream c, TriFunction zipFunction) {
        assertNotClosed();

        return Stream.zip(this, b, c, zipFunction);
    }

    @Override
    public  Stream zipWith(Stream b, Stream c, T valueForNoneA, T2 valueForNoneB, T3 valueForNoneC,
            TriFunction zipFunction) {
        assertNotClosed();

        return Stream.zip(this, b, c, valueForNoneA, valueForNoneB, valueForNoneC, zipFunction);
    }

    private static final Throwables.Function TO_LINE_OF_STRING = N::stringOf;

    @Override
    public long persist(final File file) throws IOException {
        return persist(TO_LINE_OF_STRING, file);
    }

    @Override
    public long persist(final String header, final String tail, final File file) throws IOException {
        return persist(TO_LINE_OF_STRING, header, tail, file);
    }

    @Override
    public long persist(final Throwables.Function toLine, final File file) throws IOException {
        return persist(toLine, null, null, file);
    }

    @Override
    public long persist(final Throwables.Function toLine, final String header, final String tail, final File file)
            throws IOException {
        assertNotClosed();

        final Writer writer = IOUtil.newFileWriter(file);

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

    @Override
    public long persist(final Throwables.Function toLine, final OutputStream os) throws IOException {
        assertNotClosed();

        final BufferedWriter bw = Objectory.createBufferedWriter(os);

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

    @Override
    public long persist(Throwables.Function toLine, Writer writer) throws IOException {
        assertNotClosed();

        return persist(toLine, null, null, writer);
    }

    @Override
    public long persist(Throwables.Function toLine, String header, String tail, Writer writer) throws IOException {
        assertNotClosed();

        try {
            boolean isBufferedWriter = writer instanceof BufferedWriter || writer instanceof java.io.BufferedWriter;
            final Writer bw = isBufferedWriter ? writer : Objectory.createBufferedWriter(writer); //NOSONAR
            final Iterator iter = iteratorEx();
            long cnt = 0;

            try {
                if (header != null) {
                    bw.write(header);
                    bw.write(IOUtil.LINE_SEPARATOR);
                }

                while (iter.hasNext()) {
                    bw.write(toLine.apply(iter.next()));
                    bw.write(IOUtil.LINE_SEPARATOR);
                    cnt++;
                }

                if (tail != null) {
                    bw.write(tail);
                    bw.write(IOUtil.LINE_SEPARATOR);
                }

                bw.flush();
            } finally {
                if (!isBufferedWriter) {
                    Objectory.recycle((BufferedWriter) bw);
                }
            }

            return cnt;
        } finally {
            close();
        }
    }

    @Override
    public long persist(final Throwables.BiConsumer writeLine, final File file) throws IOException {
        return persist(writeLine, null, null, file);
    }

    @Override
    public long persist(final Throwables.BiConsumer writeLine, final String header, final String tail, final File file)
            throws IOException {
        assertNotClosed();

        final Writer writer = IOUtil.newFileWriter(file);

        try {
            return persist(writeLine, header, tail, writer);
        } finally {
            IOUtil.close(writer);
        }
    }

    @Override
    public long persist(final Throwables.BiConsumer writeLine, final Writer writer) throws IOException {
        assertNotClosed();

        return persist(writeLine, null, null, writer);
    }

    @Override
    public long persist(final Throwables.BiConsumer writeLine, final String header, final String tail, final Writer writer)
            throws IOException {
        assertNotClosed();

        try {
            boolean isBufferedWriter = writer instanceof BufferedWriter || writer instanceof java.io.BufferedWriter;
            final Writer bw = isBufferedWriter ? writer : Objectory.createBufferedWriter(writer); //NOSONAR
            final Iterator iter = iteratorEx();
            long cnt = 0;

            try {
                if (header != null) {
                    bw.write(header);
                    bw.write(IOUtil.LINE_SEPARATOR);
                }

                while (iter.hasNext()) {
                    writeLine.accept(iter.next(), bw);
                    bw.write(IOUtil.LINE_SEPARATOR);
                    cnt++;
                }

                if (tail != null) {
                    bw.write(tail);
                    bw.write(IOUtil.LINE_SEPARATOR);
                }

                bw.flush();
            } finally {
                if (!isBufferedWriter) {
                    Objectory.recycle((BufferedWriter) bw);
                }
            }

            return cnt;
        } finally {
            close();
        }
    }

    @Override
    public long persist(final Connection conn, final String insertSQL, final int batchSize, final long batchIntervalInMillis,
            final Throwables.BiConsumer stmtSetter) throws SQLException {
        assertNotClosed();

        PreparedStatement stmt = null;

        try {
            stmt = conn.prepareStatement(insertSQL);

            return persist(stmt, batchSize, batchIntervalInMillis, stmtSetter);
        } finally {
            IOUtil.closeQuietly(stmt);
        }
    }

    @Override
    public long persist(final PreparedStatement stmt, final int batchSize, final long batchIntervalInMillis,
            final Throwables.BiConsumer stmtSetter) throws SQLException {
        assertNotClosed();

        checkArgument(batchSize > 0 && batchIntervalInMillis >= 0, "'batchSize'=%s must be greater than 0 and 'batchIntervalInMillis'=%s can't be negative",
                batchSize, batchIntervalInMillis);

        try {
            final Iterator iter = iteratorEx();

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

                stmt.addBatch();

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

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

            if ((cnt % batchSize) > 0) {
                executeBatch(stmt);
            }

            return cnt;
        } finally {
            close();
        }
    }

    private int[] executeBatch(final PreparedStatement stmt) throws SQLException {
        try {
            return stmt.executeBatch();
        } finally {
            try {
                stmt.clearBatch();
            } catch (SQLException e) {
                logger.error("Failed to clear batch parameters after executeBatch", e);
            }
        }
    }

    @Override
    public long persistToCSV(File file) throws IOException {
        final Writer writer = IOUtil.newFileWriter(file);

        try {
            return persistToCSV(writer);
        } finally {
            IOUtil.close(writer);
        }
    }

    @Override
    public long persistToCSV(Collection headers, File file) throws IOException {
        final Writer writer = IOUtil.newFileWriter(file);

        try {
            return persistToCSV(headers, writer);
        } finally {
            IOUtil.close(writer);
        }
    }

    @Override
    public long persistToCSV(OutputStream os) throws IOException {
        final BufferedWriter bw = Objectory.createBufferedWriter(os);

        try {
            return persistToCSV(bw);
        } finally {
            IOUtil.close(bw);
        }
    }

    @Override
    public long persistToCSV(Collection headers, OutputStream os) throws IOException {
        final BufferedWriter bw = Objectory.createBufferedWriter(os);

        try {
            return persistToCSV(headers, bw);
        } finally {
            IOUtil.close(bw);
        }
    }

    private static final Throwables.TriConsumer, Object, BufferedJSONWriter, IOException> WRITE_CSV_ELEMENT_WITH_TYPE;
    private static final Throwables.BiConsumer WRITE_CSV_ELEMENT;
    private static final Throwables.BiConsumer WRITE_CSV_STRING;
    static {
        final JSONParser jsonParser = ParserFactory.createJSONParser();
        final Type strType = N.typeOf(String.class);
        final JSONSerializationConfig config = JSC.create();
        config.setDateTimeFormat(DateTimeFormat.ISO_8601_TIMESTAMP);
        config.quoteMapKey(true);
        config.quotePropName(true);

        WRITE_CSV_ELEMENT_WITH_TYPE = (type, element, bw) -> {
            if (element == null) {
                bw.write(NULL_CHAR_ARRAY);
            } else {
                if (type.isSerializable()) {
                    type.writeCharacter(bw, element, config);
                } else {
                    strType.writeCharacter(bw, jsonParser.serialize(element, config), config);
                }
            }
        };

        WRITE_CSV_ELEMENT = (element, bw) -> {
            if (element == null) {
                bw.write(NULL_CHAR_ARRAY);
            } else {
                WRITE_CSV_ELEMENT_WITH_TYPE.accept(N.typeOf(element.getClass()), element, bw);
            }
        };

        WRITE_CSV_STRING = (str, bw) -> strType.writeCharacter(bw, str, config);
    }

    @Override
    public long persistToCSV(Writer writer) throws IOException {
        assertNotClosed();

        try {
            final boolean isBufferedWriter = writer instanceof BufferedJSONWriter;
            final BufferedJSONWriter bw = isBufferedWriter ? (BufferedJSONWriter) writer : Objectory.createBufferedJSONWriter(writer);

            final Iterator iter = iteratorEx();
            long cnt = 0;
            T next = null;
            Class cls = null;

            try {
                if (iter.hasNext()) {
                    next = iter.next();
                    cnt++;
                    cls = next.getClass();

                    if (ClassUtil.isBeanClass(cls)) {
                        final List propInfoList = ParserUtil.getBeanInfo(cls).propInfoList;
                        final int headerSize = propInfoList.size();
                        PropInfo propInfo = null;

                        for (int i = 0; i < headerSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_STRING.accept(propInfoList.get(i).name, bw);
                        }

                        bw.write(IOUtil.LINE_SEPARATOR);

                        for (int i = 0; i < headerSize; i++) {
                            propInfo = propInfoList.get(i);

                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT_WITH_TYPE.accept(propInfo.jsonXmlType, propInfo.getPropValue(next), bw);
                        }

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

                            bw.write(IOUtil.LINE_SEPARATOR);

                            for (int i = 0; i < headerSize; i++) {
                                propInfo = propInfoList.get(i);

                                if (i > 0) {
                                    bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                                }

                                WRITE_CSV_ELEMENT_WITH_TYPE.accept(propInfo.jsonXmlType, propInfo.getPropValue(next), bw);
                            }
                        }
                    } else if (next instanceof Map) {
                        Map row = (Map) next;
                        final List keys = new ArrayList<>(row.keySet());
                        final int headerSize = keys.size();

                        for (int i = 0; i < headerSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT.accept(keys.get(i), bw);
                        }

                        bw.write(IOUtil.LINE_SEPARATOR);

                        for (int i = 0; i < headerSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT.accept(row.get(keys.get(i)), bw);
                        }

                        while (iter.hasNext()) {
                            row = (Map) iter.next();
                            cnt++;

                            bw.write(IOUtil.LINE_SEPARATOR);

                            for (int i = 0; i < headerSize; i++) {
                                if (i > 0) {
                                    bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                                }

                                WRITE_CSV_ELEMENT.accept(row.get(keys.get(i)), bw);
                            }
                        }
                    } else {
                        throw new RuntimeException(cls + " is no supported for CSV format. Only bean/Map are supported");
                    }
                }

                bw.flush();
            } finally {
                if (!isBufferedWriter) {
                    Objectory.recycle((BufferedWriter) bw);
                }
            }

            return cnt;
        } finally {
            close();
        }
    }

    @Override
    public long persistToCSV(Collection csvHeaders, Writer writer) throws IOException {
        checkArgNotNullOrEmpty(csvHeaders, "csvHeaders");
        assertNotClosed();

        try {
            List headers = new ArrayList<>(csvHeaders);
            final boolean isBufferedWriter = writer instanceof BufferedJSONWriter;
            final BufferedJSONWriter bw = isBufferedWriter ? (BufferedJSONWriter) writer : Objectory.createBufferedJSONWriter(writer);

            final int headSize = headers.size();
            final Iterator iter = iteratorEx();
            long cnt = 0;
            T next = null;
            Class cls = null;

            try {
                if (iter.hasNext()) {
                    next = iter.next();
                    cnt++;
                    cls = next.getClass();

                    if (ClassUtil.isBeanClass(cls)) {
                        final BeanInfo beanInfo = ParserUtil.getBeanInfo(cls);
                        final PropInfo[] propInfos = new PropInfo[headSize];
                        PropInfo propInfo = null;

                        for (int i = 0; i < headSize; i++) {
                            propInfos[i] = beanInfo.getPropInfo(headers.get(i));

                            if (propInfos[i] == null) {
                                throw new IllegalArgumentException("No property found with name: " + headers.get(i) + " in class: " + cls);
                            }
                        }

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_STRING.accept(headers.get(i), bw);
                        }

                        bw.write(IOUtil.LINE_SEPARATOR);

                        for (int i = 0; i < headSize; i++) {
                            propInfo = propInfos[i];

                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT_WITH_TYPE.accept(propInfo.jsonXmlType, propInfo.getPropValue(next), bw);
                        }

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

                            bw.write(IOUtil.LINE_SEPARATOR);

                            for (int i = 0; i < headSize; i++) {
                                propInfo = propInfos[i];

                                if (i > 0) {
                                    bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                                }

                                WRITE_CSV_ELEMENT_WITH_TYPE.accept(propInfo.jsonXmlType, propInfo.getPropValue(next), bw);
                            }
                        }
                    } else if (next instanceof Map) {
                        Map row = (Map) next;

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_STRING.accept(headers.get(i), bw);
                        }

                        bw.write(IOUtil.LINE_SEPARATOR);

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT.accept(row.get(headers.get(i)), bw);
                        }

                        while (iter.hasNext()) {
                            row = (Map) iter.next();
                            cnt++;

                            bw.write(IOUtil.LINE_SEPARATOR);

                            for (int i = 0; i < headSize; i++) {
                                if (i > 0) {
                                    bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                                }

                                WRITE_CSV_ELEMENT.accept(row.get(headers.get(i)), bw);
                            }
                        }
                    } else if (next instanceof Collection) {
                        Collection row = (Collection) next;

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_STRING.accept(headers.get(i), bw);
                        }

                        bw.write(IOUtil.LINE_SEPARATOR);

                        Iterator rowIter = row.iterator();

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT.accept(rowIter.next(), bw);
                        }

                        while (iter.hasNext()) {
                            row = (Collection) iter.next();
                            rowIter = row.iterator();
                            cnt++;

                            bw.write(IOUtil.LINE_SEPARATOR);

                            for (int i = 0; i < headSize; i++) {
                                if (i > 0) {
                                    bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                                }

                                WRITE_CSV_ELEMENT.accept(rowIter.next(), bw);
                            }
                        }
                    } else if (next instanceof Object[]) {
                        Object[] row = (Object[]) next;

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_STRING.accept(headers.get(i), bw);
                        }

                        bw.write(IOUtil.LINE_SEPARATOR);

                        for (int i = 0; i < headSize; i++) {
                            if (i > 0) {
                                bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                            }

                            WRITE_CSV_ELEMENT.accept(row[i], bw);
                        }

                        while (iter.hasNext()) {
                            row = (Object[]) iter.next();
                            cnt++;

                            bw.write(IOUtil.LINE_SEPARATOR);

                            for (int i = 0; i < headSize; i++) {
                                if (i > 0) {
                                    bw.write(ELEMENT_SEPARATOR_CHAR_ARRAY);
                                }

                                WRITE_CSV_ELEMENT.accept(row[i], bw);
                            }
                        }
                    } else {
                        throw new RuntimeException(cls + " is no supported for CSV format. Only bean/Map are supported");
                    }
                }

                bw.flush();
            } finally {
                if (!isBufferedWriter) {
                    Objectory.recycle((BufferedWriter) bw);
                }
            }

            return cnt;
        } finally {
            close();
        }
    }

    Function, Stream> listToStreamMapper() {
        return t -> new ArrayStream<>(Stream.toArray(t), 0, t.size(), sorted, cmp, null);
    }

    // #################################################################################################################################
    // #################################################################################################################################
}