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

functionalj.stream.StreamPlus Maven / Gradle / Ivy

There is a newer version: 1.0.17
Show newest version
// ============================================================================
// Copyright (c) 2017-2019 Nawapunth Manusitthipol (NawaMan - http://nawaman.net).
// ----------------------------------------------------------------------------
// MIT License
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ============================================================================
package functionalj.stream;

import static functionalj.function.Func.f;
import static functionalj.function.Func.themAll;
import static functionalj.functions.ObjFuncs.notEqual;
import static functionalj.stream.ZipWithOption.AllowUnpaired;
import static java.lang.Boolean.TRUE;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
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.function.UnaryOperator;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import functionalj.function.Func;
import functionalj.function.Func1;
import functionalj.function.Func2;
import functionalj.function.Func3;
import functionalj.function.Func4;
import functionalj.function.Func5;
import functionalj.function.Func6;
import functionalj.function.FuncUnit1;
import functionalj.functions.StrFuncs;
import functionalj.functions.ThrowFuncs;
import functionalj.lens.core.WriteLens;
import functionalj.lens.lenses.AnyLens;
import functionalj.list.FuncList;
import functionalj.list.ImmutableList;
import functionalj.map.FuncMap;
import functionalj.map.ImmutableMap;
import functionalj.pipeable.Pipeable;
import functionalj.promise.DeferAction;
import functionalj.promise.UncompleteAction;
import functionalj.result.NoMoreResultException;
import functionalj.result.Result;
import functionalj.tuple.Tuple;
import functionalj.tuple.Tuple2;
import functionalj.tuple.Tuple3;
import functionalj.tuple.Tuple4;
import functionalj.tuple.Tuple5;
import functionalj.tuple.Tuple6;
import lombok.val;

class StreamPlusMapAddOnHelper {
    
    @SafeVarargs
    public static final  StreamPlus mapFirst(
            StreamPlus              stream,
            Function ... mappers) {
        return stream.map(f(d -> {
            Exception exception = null;
            boolean hasNull = false;
            for(val mapper : mappers) {
                try {
                    val res = mapper.apply(d);
                    if (res == null)
                         hasNull = true;
                    else return (T)res;
                } catch (Exception e) {
                    if (exception == null)
                        exception = e;
                }
            }
            if (hasNull)
                return (T)null;
            
            throw exception;
        }));
    }
}

// TODO - Intersect

@SuppressWarnings("javadoc")
@FunctionalInterface
public interface StreamPlus 
        extends Iterable, Stream, StreamPlusWithGet {
    
    public static  D noMoreElement() throws NoMoreResultException {
        ThrowFuncs.doThrowFrom(()->new NoMoreResultException());
        return (D)null;
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static  StreamPlus from(Stream stream) {
        if (stream == null)
            return StreamPlus.empty();
        
        return (stream instanceof StreamPlus)
                ? (StreamPlus)stream
                : (StreamPlus)(()->stream);
    }
    
    public static  StreamPlus from(Iterator iterator) {
        return IteratorPlus.of(iterator).stream();
    }
    public static  StreamPlus from(Enumeration enumeration) {
        Iterable iterable = new Iterable() {
            public Iterator iterator() {
                return new Iterator() {
                    private D next;
                    @Override
                    public boolean hasNext() {
                        try {
                            next = enumeration.nextElement();
                            return true;
                        } catch (NoSuchElementException e) {
                            return false;
                        }
                    }
                    @Override
                    public D next() {
                        return next;
                    }
                };
            }
        };
        return StreamPlus.from(StreamSupport.stream(iterable.spliterator(), false));
    }
    
    @SafeVarargs
    public static  StreamPlus of(D ... data) {
        return ArrayBackedStream.from(data);
    }
    
    @SafeVarargs
    public static  StreamPlus cycle(D ... data) {
        return StreamPlus.from(IntStream.iterate(0, i -> i + 1).mapToObj(i -> data[i % data.length]));
    }
    
    public static StreamPlus loop(int time) {
        return StreamPlus.infiniteInt().limit(time);
    }
    public static StreamPlus loop() {
        return StreamPlus.infiniteInt();
    }
    
    public static StreamPlus infiniteInt() {
        return IntStreamPlus.from(IntStream.iterate(0, i -> i + 1)).mapToObj(i -> i);
    }
    public static StreamPlus range(int startInclusive, int endExclusive) {
        return IntStreamPlus.range(startInclusive, endExclusive).mapToObj(i -> i);
    }
    
    public static  StreamPlus empty() {
        return StreamPlus.from(Stream.empty());
    }
    
    // Because people know this.
    @SafeVarargs
    public static  StreamPlus concat(Stream ... streams) {
        return StreamPlus.of(streams).flatMap(Func.themAll());
    }
    @SafeVarargs
    public static  StreamPlus concat(Supplier> ... streams) {
        return StreamPlus.of(streams).map(Supplier::get).flatMap(Func.themAll());
    }
    public static  StreamPlus generate(Supplier supplier) {
        return StreamPlus.from(Stream.generate(supplier));
    }
    public static  StreamPlus generateBy(Supplier supplier) {
        Iterable iterable = new Iterable() {
            public Iterator iterator() {
                return new Iterator() {
                    private D next;
                    @Override
                    public boolean hasNext() {
                        try {
                            next = supplier.get();
                            return true;
                        } catch (NoMoreResultException e) {
                            return false;
                        }
                    }
                    @Override
                    public D next() {
                        return next;
                    }
                };
            }
        };
        return StreamPlus.from(StreamSupport.stream(iterable.spliterator(), false));
    }
    
    public static  StreamPlus iterate(D seed, UnaryOperator f) {
        return StreamPlus.from(Stream.iterate(seed, f));
    }
    
    public static  StreamPlus compound(D seed, UnaryOperator f) {
        return iterate(seed, f);
    }
    
    public static  StreamPlus iterate(D seed1, D seed2, BinaryOperator f) {
        AtomicInteger      counter = new AtomicInteger(0);
        AtomicReference d1      = new AtomicReference(seed1);
        AtomicReference d2      = new AtomicReference(seed2);
        return StreamPlus.generate(()->{
            if (counter.getAndIncrement() == 0)
                return seed1;
            if (counter.getAndIncrement() == 2)
                return seed2;
            
            D i2 = d2.get();
            D i1 = d1.getAndSet(i2);
            D i  = f.apply(i1, i2);
            d2.set(i);
            return i;
        });
    }
    
    public static  StreamPlus> zipOf(StreamPlus stream1, StreamPlus stream2) {
        return stream1.zipWith(stream2);
    }
    
    public static  StreamPlus zipOf(StreamPlus stream1, StreamPlus stream2, Func2 merger) {
        return stream1.zipWith(stream2, merger);
    }
    
    //== Stream ==
    
    public Stream stream();
    
    public default  TARGET terminate(Func1, TARGET> action) {
        val stream = stream();
        try {
            val result = action.apply(stream);
            return result;
        } finally {
            stream.close();
        }
    }
    
    public default void terminate(FuncUnit1> action) {
        val stream = stream();
        try {
            action.accept(stream);
        } finally {
            stream.close();
        }
    }
    
    public default  StreamPlus deriveWith(Function, Stream> action) {
        return StreamPlus.from(
                action.apply(
                        this.stream()));
    }
    
    @SuppressWarnings("unchecked")
    public default StreamPlus concatWith(Stream ... tails) {
        return concat(
                StreamPlus.of(this), 
                StreamPlus.of(tails)
               )
               .flatMap(themAll());
    }
    
    //== Functionalities ==
    
    @Override
    public default IntStreamPlus mapToInt(ToIntFunction mapper) {
        val intStreamPlus = IntStreamPlus.from(stream().mapToInt(mapper));
        intStreamPlus.onClose(()->{
            close();
        });
        return intStreamPlus;
    }
    
    @Override
    public default LongStreamPlus mapToLong(ToLongFunction mapper) {
        return LongStreamPlus.from(stream().mapToLong(mapper));
    }
    
    @Override
    public default DoubleStreamPlus mapToDouble(ToDoubleFunction mapper) {
        return DoubleStreamPlus.from(stream().mapToDouble(mapper));
    }
    
    @Override
    public default IntStreamPlus flatMapToInt(Function mapper) {
        return IntStreamPlus.from(stream().flatMapToInt(mapper));
    }
    
    @Override
    public default LongStreamPlus flatMapToLong(Function mapper) {
        return LongStreamPlus.from(stream().flatMapToLong(mapper));
    }
    
    @Override
    public default DoubleStreamPlus flatMapToDouble(Function mapper) {
        return DoubleStreamPlus.from(stream().flatMapToDouble(mapper));
    }
    
    public default void forEach(Consumer action) {
        terminate(stream -> {
            if (action == null)
                return;
            
            stream
            .forEach(action);
        });
    }
    
    public default void forEachWithIndex(BiConsumer action) {
        terminate(stream -> {
            if (action == null)
                return;
            
            val index = new AtomicInteger();
            stream
            .forEach(each -> action.accept(index.getAndIncrement(), each));
        });
    }
    
    public default void forEachOrdered(Consumer action) {
        terminate(stream -> {
            if (action == null)
                return;
            
            stream
            .forEachOrdered(action);
        });
    }
    
    public default DATA reduce(DATA identity, BinaryOperator accumulator) {
        return terminate(stream -> {
            return stream.reduce(identity, accumulator);
        });
    }
    
    public default Optional reduce(BinaryOperator accumulator) {
        return terminate(stream -> {
            return stream.reduce(accumulator);
        });
    }
    
    public default  U reduce(
                    U                              identity,
                    BiFunction accumulator,
                    BinaryOperator              combiner) {
        return terminate(stream -> {
            return stream.reduce(identity, accumulator, combiner);
        });
    }
    
    public default  R collect(
                    Supplier                 supplier,
                    BiConsumer accumulator,
                    BiConsumer            combiner) {
        return terminate(stream -> {
            return stream.collect(supplier, accumulator, combiner);
        });
    }
    
    public default  R collect(Collector collector) {
        return terminate(stream -> {
            return stream.collect(collector);
        });
    }
    
    public default FuncMap histogram() {
        return histogram(Func.itself());
    }
    
    public default  FuncMap histogram(Func1 mapper) {
        return groupingBy(mapper)
                .mapValue(FuncList::size)
                .sortedByValue((a, b)->Integer.compare(b, a));
    }
    
    public default Optional> mostFrequence() {
        return histogram()
                .entries()
                .findFirst();
    }
    
    public default  StreamPlus> spawn(Func1> mapToAction) {
        val results = new ArrayList>();
        val index   = new AtomicInteger(0);
        
        val actions 
            = stream()
            .map (mapToAction)
            .peek(action -> results.add(DeferAction.createNew()))
            .peek(action -> action.getPromise().onComplete(result -> {
                val thisIndex  = index.getAndIncrement();
                val thisAction = results.get(thisIndex);
                if (result.isValue())
                     thisAction.complete(result.value());
                else thisAction.fail    (result.exception());
            }))
            .peek(action -> action.start())
            .collect(Collectors.toList())
            ;
        
        val stream 
            = StreamPlus
            .from(results.stream().map(action -> action.getResult()))
            ;
        stream
            .onClose(()->actions.forEach(action -> action.abort("Stream closed!")));
        
        return stream;
    }
    
    public default Optional min(Comparator comparator) {
        return terminate(stream -> {
            return stream.min(comparator);
        });
    }
    
    public default Optional max(Comparator comparator) {
        return terminate(stream -> {
            return stream.max(comparator);
        });
    }
    
    @SuppressWarnings("unchecked")
    public default Tuple2, Optional> minMax(Comparator comparator) {
        return terminate(stream -> {
            val minRef = new AtomicReference(Helper.dummy);
            val maxRef = new AtomicReference(Helper.dummy);
            stream
                .sorted(comparator)
                .forEach(each -> {
                    minRef.compareAndSet(Helper.dummy, each);
                    maxRef.set(each);
                });
            val min = minRef.get();
            val max = maxRef.get();
            return Tuple2.of(
                    Helper.dummy.equals(min) ? Optional.empty() : Optional.ofNullable((DATA)min),
                    Helper.dummy.equals(max) ? Optional.empty() : Optional.ofNullable((DATA)max));
        });
    }
    
    public default > Optional minBy(Func1 mapper) {
        return terminate(stream -> {
            return stream.min((a,b)->mapper.apply(a).compareTo(mapper.apply(b)));
        });
    }
    
    public default > Optional maxBy(Func1 mapper) {
        return terminate(stream -> {
            return stream.max((a,b)->mapper.apply(a).compareTo(mapper.apply(b)));
        });
    }
    
    public default  Optional minBy(Func1 mapper, Comparator comparator) {
        return terminate(stream -> {
            return stream.min((a,b)->comparator.compare(mapper.apply(a), mapper.apply(b)));
        });
    }
    
    public default  Optional maxBy(Func1 mapper, Comparator comparator) {
        return terminate(stream -> {
            return stream.max((a,b)->comparator.compare(mapper.apply(a), mapper.apply(b)));
        });
    }
    
    @SuppressWarnings("unchecked")
    public default > Tuple2, Optional> minMaxBy(Func1 mapper) {
        return terminate(stream -> {
            val minRef = new AtomicReference(Helper.dummy);
            val maxRef = new AtomicReference(Helper.dummy);
            StreamPlus.from(stream)
                .sortedBy(mapper)
                .forEach(each -> {
                    minRef.compareAndSet(Helper.dummy, each);
                    maxRef.set(each);
                });
            val min = minRef.get();
            val max = maxRef.get();
            return Tuple2.of(
                    Helper.dummy.equals(min) ? Optional.empty() : Optional.ofNullable((DATA)min),
                    Helper.dummy.equals(max) ? Optional.empty() : Optional.ofNullable((DATA)max));
        });
    }
    
    @SuppressWarnings("unchecked")
    public default  Tuple2, Optional> minMaxBy(
            Func1        mapper, 
            Comparator comparator) {
        return terminate(stream -> {
            val minRef = new AtomicReference(Helper.dummy);
            val maxRef = new AtomicReference(Helper.dummy);
            StreamPlus.from(stream)
                .sortedBy(mapper, (i1, i2)->comparator.compare(i1, i2))
                .forEach(each -> {
                    minRef.compareAndSet(Helper.dummy, each);
                    maxRef.set(each);
                });
            val min = minRef.get();
            val max = maxRef.get();
            return Tuple2.of(
                    Helper.dummy.equals(min) ? Optional.empty() : Optional.ofNullable((DATA)min),
                    Helper.dummy.equals(max) ? Optional.empty() : Optional.ofNullable((DATA)max));
        });
    }
    
    public default Optional sumToBigDecimal(Function toBigDecimal) {
        return map(toBigDecimal)
                .reduce(BigDecimal::add);
    }
    
    public default Optional minToBigDecimal(Function toBigDecimal) {
        return map(toBigDecimal)
                .reduce((a, b) -> a.compareTo(b) <= 0 ? a : b);
    }
    
    public default Optional maxToBigDecimal(Function toBigDecimal) {
        return map(toBigDecimal)
                .reduce((a, b) -> a.compareTo(b) <= 0 ? b : a);
    }
    
    public default Optional averageToBigDecimal(Function toBigDecimal) {
        return map(each -> Tuple.of(1, toBigDecimal.apply(each)))
               .reduce((a, b)->Tuple.of(a._1 + b._1, a._2.add(b._2)))
               .map(t -> t._2.divide(new BigDecimal(t._1)));
    }
    
    public default Optional sum(Function toBigDecimal) {
        return sumToBigDecimal(toBigDecimal);
    }
    
    public default Optional min(Function toBigDecimal) {
        return minToBigDecimal(toBigDecimal);
    }
    
    public default Optional max(Function toBigDecimal) {
        return maxToBigDecimal(toBigDecimal);
    }
    
    public default Optional average(Function toBigDecimal) {
        return averageToBigDecimal(toBigDecimal);
    }
    
    public default long count() {
        return terminate(stream -> {
            return stream.count();
        });
    }
    
    public default int size() {
        return terminate(stream -> {
            return (int)stream.count();
        });
    }
    
    public default boolean anyMatch(Predicate predicate) {
        return terminate(stream -> {
            return stream.anyMatch(predicate);
        });
    }
    
    public default boolean allMatch(Predicate predicate) {
        return terminate(stream -> {
            return stream.allMatch(predicate);
        });
    }
    
    public default boolean noneMatch(Predicate predicate) {
        return terminate(stream -> {
            return stream.noneMatch(predicate);
        });
    }
    
    public default Optional findFirst(Predicate predicate) {
        return terminate(stream -> {
            return stream.filter(predicate).findFirst();
        });
    }
    
    public default Optional findAny(Predicate predicate) {
        return terminate(stream -> {
            return stream.filter(predicate).findAny();
        });
    }
    
    public default  Optional findFirst(Function mapper, Predicate theCondition) {
        return filter(mapper, theCondition).findFirst();
    }
    
    public default   Optional findAny(Function mapper, Predicate theCondition) {
        return filter(mapper, theCondition).findAny();
    }
    
    public default Optional findFirst() {
        return terminate(stream -> {
            return stream.findFirst();
        });
    }
    
    public default Optional findAny() {
        return terminate(stream -> {
            return stream.findAny();
        });
    }
    
    @Override
    public default boolean isParallel() {
        return stream().isParallel();
    }
    
    @Override
    public default void close() {
        stream().close();
    }
    
    //== toXXX ===
    
    @Override
    public default Object[] toArray() {
        return terminate(stream -> {
            return stream.toArray();
        });
    }
    
    @Override
    public default  A[] toArray(IntFunction generator) {
        return terminate(stream -> {
            return stream.toArray(generator);
        });
    }
    
    public default List toJavaList() {
        return terminate(stream -> {
            return stream.collect(Collectors.toList());
        });
    }
    
    public default byte[] toByteArray(Func1 toByte) {
        return terminate(stream -> {
            val byteArray = new ByteArrayOutputStream();
            stream.forEach(d -> byteArray.write(toByte.apply(d)));
            return byteArray.toByteArray();
        });
    }
    
    public default int[] toIntArray(ToIntFunction toInt) {
        return mapToInt(toInt)
                .toArray();
    }
    
    public default long[] toLongArray(ToLongFunction toLong) {
        return mapToLong(toLong)
                .toArray();
    }
    
    public default double[] toDoubleArray(ToDoubleFunction toDouble) {
        return mapToDouble(toDouble)
                .toArray();
    }
    
    public default FuncList toList() {
        return toImmutableList();
    }
    
    public default String toListString() {
        return "[" + map(String::valueOf).collect(Collectors.joining(", ")) + "]";
    }
    
    public default ImmutableList toImmutableList() {
        return ImmutableList.from(this);
    }
    
    public default List toMutableList() {
        return toArrayList();
    }
    
    public default ArrayList toArrayList() {
        return new ArrayList(toJavaList());
    }
    
    public default Set toSet() {
        return new HashSet(this.collect(Collectors.toSet()));
    }
    
    public default IteratorPlus iterator() {
        // TODO - Make sure close is handled properly.
        return IteratorPlus.from(stream());
    }
    
    public default Spliterator spliterator() {
        // TODO - Make sure close is handled properly.
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
    
    public default  FuncMap> groupingBy(
            Function classifier) {
        return terminate(stream -> {
            val theMap = new HashMap>();
            stream
                .collect(Collectors.groupingBy(classifier))
                .forEach((key,list)->theMap.put(key, ImmutableList.from(list)));
            return ImmutableMap.from(theMap);
        });
    }
    
    public default  FuncMap groupingBy(
            Function   classifier,
            Function, VALUE> aggregate) {
        return terminate(stream -> {
            val theMap = new HashMap();
            stream
                .collect(Collectors.groupingBy(classifier))
                .forEach((key,list) -> {
                    val valueList      = ImmutableList.from(list);
                    val aggregateValue = aggregate.apply(valueList);
                    theMap.put(key, aggregateValue);
                });
            return ImmutableMap.from(theMap);
        });
    }
    
    @SuppressWarnings("unchecked")
    public default  FuncMap toMap(Function keyMapper) {
        return terminate(stream -> {
            val theMap = stream.collect(Collectors.toMap(keyMapper, data -> data));
            return (FuncMap)ImmutableMap.from(theMap);
        });
    }
    
    @SuppressWarnings("unchecked")
    public default  FuncMap toMap(
                Function  keyMapper,
                Function valueMapper) {
        return terminate(stream -> {
            val theMap = stream.collect(Collectors.toMap(keyMapper, valueMapper));
            return (FuncMap) ImmutableMap.from(theMap);
        });
    }
    
    @SuppressWarnings("unchecked")
    public default  FuncMap toMap(
                Function   keyMapper,
                Function valueMapper,
                BinaryOperator                   mergeFunction) {
        return terminate(stream -> {
            val theMap = stream.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction));
            return (FuncMap) ImmutableMap.from(theMap);
        });
    }
    
    @SuppressWarnings("unchecked")
    public default  FuncMap toMap(
                Function keyMapper,
                BinaryOperator                  mergeFunction) {
        return terminate(stream -> {
            val theMap = stream.collect(Collectors.toMap(keyMapper, value -> value, mergeFunction));
            return (FuncMap) ImmutableMap.from(theMap);
        });
    }
    
    //== Plus ==
    
    public default String joinToString() {
        return terminate(stream -> {
            return stream
                    .map(StrFuncs::toStr)
                    .collect(Collectors.joining());
        });
    }
    public default String joinToString(String delimiter) {
        return terminate(stream -> {
            return stream
                    .map(StrFuncs::toStr)
                    .collect(Collectors.joining(delimiter));
        });
    }
    
    //-- Split --
    
    public default Tuple2, FuncList> split(
            Predicate predicate) {
        val temp = this.mapTuple(
                it -> predicate.test(it) ? 0 : 1,
                it -> it
        ).toList();
        val list1 = temp.filter(it -> it._1() == 0).map(it -> it._2());
        val list2 = temp.filter(it -> it._1() == 1).map(it -> it._2());
        return Tuple.of(
                list1,
                list2
        );
    }
    
    public default Tuple3, FuncList, FuncList> split(
            Predicate predicate1,
            Predicate predicate2) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    :                       2,
                it -> it
        ).toImmutableList();
        val list1 = temp.filter(it -> it._1() == 0).map(it -> it._2());
        val list2 = temp.filter(it -> it._1() == 1).map(it -> it._2());
        val list3 = temp.filter(it -> it._1() == 2).map(it -> it._2());
        return Tuple.of(
                list1,
                list2,
                list3
        );
    }
    
    public default Tuple4, FuncList, FuncList, FuncList> split(
            Predicate predicate1,
            Predicate predicate2,
            Predicate predicate3) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    : predicate3.test(it) ? 2
                    :                       3,
                it -> it
        ).toImmutableList();
        val list1 = temp.filter(it -> it._1() == 0).map(it -> it._2());
        val list2 = temp.filter(it -> it._1() == 1).map(it -> it._2());
        val list3 = temp.filter(it -> it._1() == 2).map(it -> it._2());
        val list4 = temp.filter(it -> it._1() == 3).map(it -> it._2());
        return Tuple.of(
                list1,
                list2,
                list3,
                list4
        );
    }
    
    public default Tuple5, FuncList, FuncList, FuncList, FuncList> split(
            Predicate predicate1,
            Predicate predicate2,
            Predicate predicate3,
            Predicate predicate4) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    : predicate3.test(it) ? 2
                    : predicate4.test(it) ? 3
                    :                       4,
                it -> it
        ).toImmutableList();
        val list1 = temp.filter(it -> it._1() == 0).map(it -> it._2());
        val list2 = temp.filter(it -> it._1() == 1).map(it -> it._2());
        val list3 = temp.filter(it -> it._1() == 2).map(it -> it._2());
        val list4 = temp.filter(it -> it._1() == 3).map(it -> it._2());
        val list5 = temp.filter(it -> it._1() == 4).map(it -> it._2());
        return Tuple.of(
                list1,
                list2,
                list3,
                list4,
                list5
        );
    }
    
    public default Tuple6, FuncList, FuncList, FuncList, FuncList, FuncList> split(
            Predicate predicate1,
            Predicate predicate2,
            Predicate predicate3,
            Predicate predicate4,
            Predicate predicate5) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    : predicate3.test(it) ? 2
                    : predicate4.test(it) ? 3
                    : predicate5.test(it) ? 4
                    :                       5,
                it -> it
        ).toImmutableList();
        val list1 = temp.filter(it -> it._1() == 0).map(it -> it._2());
        val list2 = temp.filter(it -> it._1() == 1).map(it -> it._2());
        val list3 = temp.filter(it -> it._1() == 2).map(it -> it._2());
        val list4 = temp.filter(it -> it._1() == 3).map(it -> it._2());
        val list5 = temp.filter(it -> it._1() == 4).map(it -> it._2());
        val list6 = temp.filter(it -> it._1() == 5).map(it -> it._2());
        return Tuple.of(
                list1,
                list2,
                list3,
                list4,
                list5,
                list6
        );
    }
    
    //-- SplitToMap --
    
    public default  FuncMap> split(
            KEY key1, Predicate predicate,
            KEY key2) {
        val temp = this.mapTuple(
                it -> predicate.test(it) ? 0 : 1,
                it -> it
        ).toList();
        val list1 = (key1 != null) ? temp.filter(it -> it._1() == 0).map(it -> it._2()) : FuncList.empty();
        val list2 = (key2 != null) ? temp.filter(it -> it._1() == 1).map(it -> it._2()) : FuncList.empty();
        return FuncMap.of(
                key1, list1, 
                key2, list2);
    }
    
    public default  FuncMap> split(
            KEY key1, Predicate predicate1,
            KEY key2, Predicate predicate2,
            KEY key3) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    :                       2,
                it -> it
        ).toImmutableList();
        val list1 = (key1 != null) ? temp.filter(it -> it._1() == 0).map(it -> it._2()) : FuncList.empty();
        val list2 = (key2 != null) ? temp.filter(it -> it._1() == 1).map(it -> it._2()) : FuncList.empty();
        val list3 = (key3 != null) ? temp.filter(it -> it._1() == 2).map(it -> it._2()) : FuncList.empty();
        return FuncMap.of(
                key1, list1, 
                key2, list2, 
                key3, list3);
    }
    
    public default  FuncMap> split(
            KEY key1, Predicate predicate1,
            KEY key2, Predicate predicate2,
            KEY key3, Predicate predicate3,
            KEY key4) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    : predicate3.test(it) ? 2
                    :                       3,
                it -> it
        ).toImmutableList();
        val list1 = (key1 != null) ? temp.filter(it -> it._1() == 0).map(it -> it._2()) : FuncList.empty();
        val list2 = (key2 != null) ? temp.filter(it -> it._1() == 1).map(it -> it._2()) : FuncList.empty();
        val list3 = (key3 != null) ? temp.filter(it -> it._1() == 2).map(it -> it._2()) : FuncList.empty();
        val list4 = (key4 != null) ? temp.filter(it -> it._1() == 3).map(it -> it._2()) : FuncList.empty();
        return FuncMap.of(
                key1, list1, 
                key2, list2, 
                key3, list3, 
                key4, list4);
    }
    
    public default  FuncMap> split(
            KEY key1, Predicate predicate1,
            KEY key2, Predicate predicate2,
            KEY key3, Predicate predicate3,
            KEY key4, Predicate predicate4,
            KEY key5) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    : predicate3.test(it) ? 2
                    : predicate4.test(it) ? 3
                    :                       4,
                it -> it
        ).toImmutableList();
        val list1 = (key1 != null) ? temp.filter(it -> it._1() == 0).map(it -> it._2()) : FuncList.empty();
        val list2 = (key2 != null) ? temp.filter(it -> it._1() == 1).map(it -> it._2()) : FuncList.empty();
        val list3 = (key3 != null) ? temp.filter(it -> it._1() == 2).map(it -> it._2()) : FuncList.empty();
        val list4 = (key4 != null) ? temp.filter(it -> it._1() == 3).map(it -> it._2()) : FuncList.empty();
        val list5 = (key5 != null) ? temp.filter(it -> it._1() == 4).map(it -> it._2()) : FuncList.empty();
        return FuncMap.of(
                key1, list1, 
                key2, list2, 
                key3, list3, 
                key4, list4, 
                key5, list5);
    }
    
    public default  FuncMap> split(
            KEY key1, Predicate predicate1,
            KEY key2, Predicate predicate2,
            KEY key3, Predicate predicate3,
            KEY key4, Predicate predicate4,
            KEY key5, Predicate predicate5,
            KEY key6) {
        val temp = this.mapTuple(
                it -> predicate1.test(it) ? 0
                    : predicate2.test(it) ? 1
                    : predicate3.test(it) ? 2
                    : predicate4.test(it) ? 3
                    : predicate5.test(it) ? 4
                    :                       5,
                it -> it
        ).toImmutableList();
        val list1 = (key1 != null) ? temp.filter(it -> it._1() == 0).map(it -> it._2()) : FuncList.empty();
        val list2 = (key2 != null) ? temp.filter(it -> it._1() == 1).map(it -> it._2()) : FuncList.empty();
        val list3 = (key3 != null) ? temp.filter(it -> it._1() == 2).map(it -> it._2()) : FuncList.empty();
        val list4 = (key4 != null) ? temp.filter(it -> it._1() == 3).map(it -> it._2()) : FuncList.empty();
        val list5 = (key5 != null) ? temp.filter(it -> it._1() == 4).map(it -> it._2()) : FuncList.empty();
        val list6 = (key6 != null) ? temp.filter(it -> it._1() == 5).map(it -> it._2()) : FuncList.empty();
        return FuncMap.of(
                key1, list1, 
                key2, list2, 
                key3, list3, 
                key4, list4, 
                key5, list5,
                key6, list6);
    }
    
    //++ Plus w/ Self ++
    
    @Override
    public default StreamPlus sequential() {
        return deriveWith(stream -> { 
            return stream.sequential();
        });
    }
    
    @Override
    public default StreamPlus parallel() {
        return deriveWith(stream -> { 
            return stream.parallel();
        });
    } 
    
    @Override
    public default StreamPlus unordered() {
        return deriveWith(stream -> { 
            return stream.unordered();
        });
    }
    
    @Override
    public default StreamPlus onClose(Runnable closeHandler) {
        return deriveWith(stream -> { 
            return stream.onClose(closeHandler);
        });
    }
    
    public default  Pipeable> pipable() {
        return Pipeable.of(this);
    }
    
    public default  T pipe(Function, T> piper) {
        return piper.apply(this);
    }
    
    public default  StreamPlus map(Function mapper) {
        return deriveWith(stream -> {
            return stream.map(mapper);
        });
    }
    
    //-- Limit/Skip --
    
    @Override
    public default StreamPlus limit(long maxSize) {
        return deriveWith(stream -> {
            return stream.limit(maxSize);
        });
    }
    
    @Override
    public default StreamPlus skip(long n) {
        return deriveWith(stream -> {
            return stream.skip(n);
        });
    }
    
    public default StreamPlus skipWhile(Predicate condition) {
        val isStillTrue = new AtomicBoolean(true);
        return deriveWith(stream -> {
            return stream.filter(e -> {
                if (!isStillTrue.get())
                    return true;
                if (!condition.test(e))
                    isStillTrue.set(false);
                return !isStillTrue.get();
            });
        });
    }
    
    public default StreamPlus skipUntil(Predicate condition) {
        val isStillTrue = new AtomicBoolean(true);
        return deriveWith(stream -> {
            return stream.filter(e -> {
                if (!isStillTrue.get())
                    return true;
                if (condition.test(e))
                    isStillTrue.set(false);
                return !isStillTrue.get();
            });
        });
    }
    
    public default StreamPlus takeWhile(Predicate condition) {
        // https://stackoverflow.com/questions/32290278/picking-elements-of-a-list-until-condition-is-met-with-java-8-lambdas
        return deriveWith(stream -> {
            val splitr = stream.spliterator();
            return StreamSupport.stream(new Spliterators.AbstractSpliterator(splitr.estimateSize(), 0) {
                boolean stillGoing = true;
                
                @Override
                public boolean tryAdvance(final Consumer consumer) {
                    if (stillGoing) {
                        final boolean hadNext = splitr.tryAdvance(elem -> {
                            if (condition.test(elem)) {
                                consumer.accept(elem);
                            } else {
                                stillGoing = false;
                            }
                        });
                        return hadNext && stillGoing;
                    }
                    return false;
                }
            }, false);
        });
    }
    
    public default StreamPlus takeUntil(Predicate condition) {
        return deriveWith(stream -> {
            val splitr = stream.spliterator();
            return StreamSupport.stream(new Spliterators.AbstractSpliterator(splitr.estimateSize(), 0) {
                boolean stillGoing = true;
                
                @Override
                public boolean tryAdvance(final Consumer consumer) {
                    if (stillGoing) {
                        final boolean hadNext = splitr.tryAdvance(elem -> {
                            if (!condition.test(elem)) {
                                consumer.accept(elem);
                            } else {
                                stillGoing = false;
                            }
                        });
                        return hadNext && stillGoing;
                    }
                    return false;
                }
            }, false);
        });
    }
    
    @Override
    public default StreamPlus distinct() {
        return deriveWith(stream -> {
            return stream.distinct();
        });
    }
    
    @Override
    public default StreamPlus sorted() {
        return deriveWith(stream -> {
            return stream.sorted();
        });
    }
    
    @Override
    public default StreamPlus sorted(Comparator comparator) {
        return deriveWith(stream -> {
            return (comparator == null)
                    ? stream.sorted()
                    : stream.sorted(comparator);
        });
    }
    
    public default StreamPlus limit(Long maxSize) {
        return deriveWith(stream -> {
            return ((maxSize == null) || (maxSize.longValue() < 0))
                    ? stream
                    : stream.limit(maxSize);
        });
    }
    
    public default StreamPlus skip(Long startAt) {
        return deriveWith(stream -> {
            return ((startAt == null) || (startAt.longValue() < 0))
                    ? stream
                    : stream.skip(startAt);
        });
    }
    
    //-- Sorted --
    
    public default > StreamPlus sortedBy(Function mapper) {
        return deriveWith(stream -> {
            return stream.sorted((a, b) -> {
                        T vA = mapper.apply(a);
                        T vB = mapper.apply(b);
                        return vA.compareTo(vB);
                    });
        });
    }
    
    public default  StreamPlus sortedBy(Function mapper, Comparator comparator) {
        return deriveWith(stream -> {
            return stream.sorted((a, b) -> {
                    T vA = mapper.apply(a);
                    T vB = mapper.apply(b);
                    return Objects.compare(vA,  vB, comparator);
                });
        });
    }
    
    // -- fillNull --
    
    public default  StreamPlus fillNull(AnyLens lens, VALUE replacement) {
        return fillNull(
                (Func1)lens, 
                ((WriteLens)lens)::apply, 
                replacement);
    }
    
    public default  StreamPlus fillNull(
            Func1       get, 
            Func2 set, 
            VALUE                    replacement) {
        return deriveWith(stream -> {
            return (Stream)stream.map(orgElmt -> {
                val value   = get.apply(orgElmt);
                if (value == null) {
                    val newElmt = set.apply(orgElmt, replacement);
                    return (DATA)newElmt;
                }
                return orgElmt;
            });
        });
    }
    
    public default  StreamPlus fillNull(
            AnyLens lens, 
            Supplier      replacementSupplier) {
        return fillNull(
                (Func1)lens, 
                ((WriteLens)lens)::apply, 
                replacementSupplier);
    }
    
    public default  StreamPlus fillNull(
            Func1       get, 
            Func2 set, 
            Supplier          replacementSupplier) {
        return deriveWith(stream -> {
            return (Stream)stream.map(orgElmt -> {
                val value   = get.apply(orgElmt);
                if (value == null) {
                    val replacement = replacementSupplier.get();
                    val newElmt     = set.apply(orgElmt, replacement);
                    return (DATA)newElmt;
                }
                return orgElmt;
            });
        });
    }
    
    public default  StreamPlus fillNull(
            AnyLens lens, 
            Func1   replacementSupplier) {
        return fillNull(
                (Func1)lens, 
                ((WriteLens)lens)::apply, 
                replacementSupplier);
    }
    
    public default  StreamPlus fillNull(
            Func1       get, 
            Func2 set, 
            Func1       replacementFunction) {
        return deriveWith(stream -> {
            return (Stream)stream.map(orgElmt -> {
                val value   = get.apply(orgElmt);
                if (value == null) {
                    val replacement = replacementFunction.apply(orgElmt);
                    val newElmt     = set.apply(orgElmt, replacement);
                    return (DATA)newElmt;
                }
                return orgElmt;
            });
        });
    }
    
    public default  StreamPlus flatMap(Function> mapper) {
        return deriveWith(stream -> {
            return stream.flatMap(mapper);
        });
    }
    
    public default StreamPlus filter(Predicate predicate) {
        return deriveWith(stream -> {
            return (predicate == null)
                ? stream
                : stream.filter(predicate);
        });
    }
    public default StreamPlus peek(Consumer action) {
        return deriveWith(stream -> {
            return (action == null)
                    ? stream
                    : stream.peek(action);
        });
    }
    
    //--map with condition --
    
    public default StreamPlus mapOnly(Predicate checker, Function mapper) {
        return map(d -> checker.test(d) ? mapper.apply(d) : d);
    }
    public default  StreamPlus mapIf(
            Predicate   checker, 
            Function mapper, 
            Function elseMapper) {
        return map(d -> {
            return checker.test(d) ? mapper.apply(d) : elseMapper.apply(d);
        });
    }
    
    public default  StreamPlus mapFirst(
            Function mapper1,
            Function mapper2) {
        return StreamPlusMapAddOnHelper.mapFirst(this, mapper1, mapper2);
    }
    
    public default  StreamPlus mapFirst(
            Function mapper1,
            Function mapper2,
            Function mapper3) {
        return StreamPlusMapAddOnHelper.mapFirst(this, mapper1, mapper2, mapper3);
    }
    
    public default  StreamPlus mapFirst(
            Function mapper1,
            Function mapper2,
            Function mapper3,
            Function mapper4) {
        return StreamPlusMapAddOnHelper.mapFirst(this, mapper1, mapper2, mapper3, mapper4);
    }
    
    public default  StreamPlus mapFirst(
            Function mapper1,
            Function mapper2,
            Function mapper3,
            Function mapper4,
            Function mapper5) {
        return StreamPlusMapAddOnHelper.mapFirst(this, mapper1, mapper2, mapper3, mapper4, mapper5);
    }
    
    public default  StreamPlus mapFirst(
            Function mapper1,
            Function mapper2,
            Function mapper3,
            Function mapper4,
            Function mapper5,
            Function mapper6) {
        return StreamPlusMapAddOnHelper.mapFirst(this, mapper1, mapper2, mapper3, mapper4, mapper5, mapper6);
    }
    
    //-- mapWithIndex --
    
    public default StreamPlus> mapWithIndex() {
        val index = new AtomicInteger();
        return map(each -> Tuple2.of(index.getAndIncrement(), each));
    }
    
    public default  StreamPlus mapWithIndex(BiFunction mapper) {
        val index = new AtomicInteger();
        return map(each -> mapper.apply(index.getAndIncrement(), each));
    }
    
    public default  StreamPlus mapWithIndex(
                Function       mapper1,
                BiFunction mapper) {
        val index = new AtomicInteger();
        return map(each -> mapper.apply(
                                index.getAndIncrement(),
                                mapper1.apply(each)));
    }
    
    //-- mapWithPrev --
    
    public default  StreamPlus mapWithPrev(BiFunction, ? super DATA, ? extends TARGET> mapper) {
        val prev = new AtomicReference>(Result.ofNotExist());
        return map(element -> {
            val newValue = mapper.apply(prev.get(), element);
            prev.set(Result.valueOf(element));
            return newValue;
        });
    }
    
    // -- accumulate --
    
    public default StreamPlus accumulate(BiFunction accumulator) {
        val iterator = this.iterator();
        if (!iterator.hasNext())
            return StreamPlus.empty();
        
        val prev = new AtomicReference(iterator.next());
        return StreamPlus.concat(
                    StreamPlus.of(prev.get()),
                    iterator.stream().map(n -> {
                        val next = accumulator.apply(n, prev.get());
                        prev.set(next);
                        return next;
                    })
                );
    }
    
    public default StreamPlus restate(BiFunction, StreamPlus> restater) {
        val func = (UnaryOperator>>)((Tuple2> pair) -> {
            val stream   = pair._2();
            val iterator = stream.iterator();
            if (!iterator.hasNext())
                return null;
            
            val head = iterator.next();
            val tail = restater.apply(head, iterator.stream());
            return Tuple2.of(head, tail);
        });
        val seed = Tuple2.of((DATA)null, this);
        val endStream = StreamPlus.iterate(seed, func).takeUntil(t -> t == null).skip(1).map(t -> t._1());
        return endStream;
    }
    
    //== Map to tuple. ==
    // ++ Generated with: GeneratorFunctorMapToTupleToObject ++
    
    public default  
        StreamPlus> mapTuple(
                Function mapper1,
                Function mapper2) {
        return mapThen(mapper1, mapper2,
                   (v1, v2) -> Tuple2.of(v1, v2));
    }
    
    public default  
        StreamPlus> mapTuple(
                Function mapper1,
                Function mapper2,
                Function mapper3) {
        return mapThen(mapper1, mapper2, mapper3,
                   (v1, v2, v3) -> Tuple3.of(v1, v2, v3));
    }
    
    public default  
        StreamPlus> mapTuple(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Function mapper4) {
        return mapThen(mapper1, mapper2, mapper3, mapper4,
                   (v1, v2, v3, v4) -> Tuple4.of(v1, v2, v3, v4));
    }
    
    public default  
        StreamPlus> mapTuple(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Function mapper4,
                Function mapper5) {
        return mapThen(mapper1, mapper2, mapper3, mapper4, mapper5,
                   (v1, v2, v3, v4, v5) -> Tuple5.of(v1, v2, v3, v4, v5));
    }
    public default  
        StreamPlus> mapTuple(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Function mapper4,
                Function mapper5,
                Function mapper6) {
        return mapThen(mapper1, mapper2, mapper3, mapper4, mapper5, mapper6,
                   (v1, v2, v3, v4, v5, v6) -> Tuple6.of(v1, v2, v3, v4, v5, v6));
    }
    
    //-- Map and combine --
    
    public default  
        StreamPlus mapThen(
                Function mapper1,
                Function mapper2,
                BiFunction function) {
        return map(each -> {
            val v1 = mapper1.apply(each);
            val v2 = mapper2.apply(each);
            val v  = function.apply(v1, v2);
            return v;
        });
    }
    public default  
        StreamPlus mapThen(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Func3 function) {
        return map(each -> {
            val v1 = mapper1.apply(each);
            val v2 = mapper2.apply(each);
            val v3 = mapper3.apply(each);
            val v  = function.apply(v1, v2, v3);
            return v;
        });
    }
    public default  
        StreamPlus mapThen(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Function mapper4,
                Func4 function) {
        return map(each -> {
            val v1 = mapper1.apply(each);
            val v2 = mapper2.apply(each);
            val v3 = mapper3.apply(each);
            val v4 = mapper4.apply(each);
            val v  = function.apply(v1, v2, v3, v4);
            return v;
        });
    }
    public default  
        StreamPlus mapThen(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Function mapper4,
                Function mapper5,
                Func5 function) {
        return map(each -> {
            val v1 = mapper1.apply(each);
            val v2 = mapper2.apply(each);
            val v3 = mapper3.apply(each);
            val v4 = mapper4.apply(each);
            val v5 = mapper5.apply(each);
            val v  = function.apply(v1, v2, v3, v4, v5);
            return v;
        });
    }
    public default  
        StreamPlus mapThen(
                Function mapper1,
                Function mapper2,
                Function mapper3,
                Function mapper4,
                Function mapper5,
                Function mapper6,
                Func6 function) {
        return map(each -> {
            val v1 = mapper1.apply(each);
            val v2 = mapper2.apply(each);
            val v3 = mapper3.apply(each);
            val v4 = mapper4.apply(each);
            val v5 = mapper5.apply(each);
            val v6 = mapper6.apply(each);
            val v  = function.apply(v1, v2, v3, v4, v5, v6);
            return v;
        });
    }
    
    // -- Generated with: GeneratorFunctorMapToTupleToObject --
    
    public default  StreamPlus> mapToMap(
            KEY key, Function mapper) {
        return map(data -> ImmutableMap.of(key, mapper.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4,
            KEY key5, Function mapper5) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data),
                key5, mapper5.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4,
            KEY key5, Function mapper5,
            KEY key6, Function mapper6) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data),
                key5, mapper5.apply(data),
                key6, mapper6.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4,
            KEY key5, Function mapper5,
            KEY key6, Function mapper6,
            KEY key7, Function mapper7) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data),
                key5, mapper5.apply(data),
                key6, mapper6.apply(data),
                key7, mapper7.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4,
            KEY key5, Function mapper5,
            KEY key6, Function mapper6,
            KEY key7, Function mapper7,
            KEY key8, Function mapper8) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data),
                key5, mapper5.apply(data),
                key6, mapper6.apply(data),
                key7, mapper7.apply(data),
                key8, mapper8.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4,
            KEY key5, Function mapper5,
            KEY key6, Function mapper6,
            KEY key7, Function mapper7,
            KEY key8, Function mapper8,
            KEY key9, Function mapper9) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data),
                key5, mapper5.apply(data),
                key6, mapper6.apply(data),
                key7, mapper7.apply(data),
                key8, mapper8.apply(data),
                key9, mapper9.apply(data)));
    }
    
    public default  StreamPlus> mapToMap(
            KEY key1, Function mapper1,
            KEY key2, Function mapper2,
            KEY key3, Function mapper3,
            KEY key4, Function mapper4,
            KEY key5, Function mapper5,
            KEY key6, Function mapper6,
            KEY key7, Function mapper7,
            KEY key8, Function mapper8,
            KEY key9, Function mapper9,
            KEY key10, Function mapper10) {
        return map(data -> ImmutableMap.of(
                key1, mapper1.apply(data),
                key2, mapper2.apply(data),
                key3, mapper3.apply(data),
                key4, mapper4.apply(data),
                key5, mapper5.apply(data),
                key6, mapper6.apply(data),
                key7, mapper7.apply(data),
                key8, mapper8.apply(data),
                key9, mapper9.apply(data),
                key10, mapper10.apply(data)));
    }
    
    //-- Filter --
    
    public default StreamPlus filterNonNull() {
        return deriveWith(stream -> stream.filter(Objects::nonNull));
    }
    
    public default StreamPlus filterIn(Collection collection) {
        return deriveWith(stream -> {
            return (collection == null)
                ? Stream.empty()
                : stream.filter(data -> collection.contains(data));
        });
    }
    
    public default StreamPlus exclude(Predicate predicate) {
        return deriveWith(stream -> {
            return (predicate == null)
                ? stream
                : stream.filter(data -> !predicate.test(data));
        });
    }
    
    public default StreamPlus excludeIn(Collection collection) {
        return deriveWith(stream -> {
            return (collection == null)
                ? stream
                : stream.filter(data -> !collection.contains(data));
        });
    }
    
    public default  StreamPlus filter(Class clzz) {
        return filter(clzz::isInstance);
    }
    
    public default  StreamPlus filter(Class clzz, Predicate theCondition) {
        return filter(value -> {
            if (!clzz.isInstance(value))
                return false;
            
            val target = clzz.cast(value);
            val isPass = theCondition.test(target);
            return isPass;
        });
    }
    
    public default  StreamPlus filter(Function mapper, Predicate theCondition) {
        return filter(value -> {
            val target = mapper.apply(value);
            val isPass = theCondition.test(target);
            return isPass;
        });
    }

    public default StreamPlus filterWithIndex(BiFunction predicate) {
        val index = new AtomicInteger();
        return filter(each -> {
                    return (predicate != null) 
                            && predicate.apply(index.getAndIncrement(), each);
        });
    }
    
    //-- Peek --
    
    public default  StreamPlus peek(Class clzz, Consumer theConsumer) {
        return peek(value -> {
            if (!clzz.isInstance(value))
                return;
            
            val target = clzz.cast(value);
            theConsumer.accept(target);
        });
    }
    public default StreamPlus peek(Predicate selector, Consumer theConsumer) {
        return peek(value -> {
            if (!selector.test(value))
                return;
            
            theConsumer.accept(value);
        });
    }
    public default  StreamPlus peek(Function mapper, Consumer theConsumer) {
        return peek(value -> {
            val target = mapper.apply(value);
            theConsumer.accept(target);
        });
    }
    
    public default  StreamPlus peek(Function mapper, Predicate selector, Consumer theConsumer) {
        return peek(value -> {
            val target = mapper.apply(value);
            if (selector.test(target))
                theConsumer.accept(target);
        });
    }
    
    //-- FlatMap --
    
    public default StreamPlus flatMapOnly(Predicate checker, Function> mapper) {
        return flatMap(d -> checker.test(d) ? mapper.apply(d) : StreamPlus.of(d));
    }
    public default  StreamPlus flatMapIf(
            Predicate checker, 
            Function> mapper, 
            Function> elseMapper) {
        return flatMap(d -> checker.test(d) ? mapper.apply(d) : elseMapper.apply(d));
    }
    
    
    //-- segment --
    
    public default StreamPlus> segment(int count) {
        return segment(count, true);
    }
    public default StreamPlus> segment(int count, boolean includeTail) {
        val index = new AtomicInteger(0);
        return segment(data -> (index.getAndIncrement() % count) == 0, includeTail);
    }
    public default StreamPlus> segment(Predicate startCondition) {
        return segment(startCondition, true);
    }
    public default StreamPlus> segment(Predicate startCondition, boolean includeTail) {
        val list = new AtomicReference<>(new ArrayList());
        val adding = new AtomicBoolean(false);
        
        val streamOrNull = (Function>)((DATA data) ->{
            if (startCondition.test(data)) {
                adding.set(true);
                val retList = list.getAndUpdate(l -> new ArrayList());
                list.get().add(data);
                return retList.isEmpty()
                        ? null
                        : StreamPlus.from(retList.stream());
            }
            if (adding.get()) list.get().add(data);
            return null;
        });
        val mainStream = StreamPlus.from(map(streamOrNull)).filterNonNull();
        if (!includeTail)
            return mainStream;
        
        val mainSupplier = (Supplier>>)()->mainStream;
        val tailSupplier = (Supplier>>)()->{
            return StreamPlus.of(
                    StreamPlus.from(
                            list.get()
                            .stream()));
        };
        val resultStream
                = StreamPlus.of(mainSupplier, tailSupplier)
                .flatMap(Supplier::get);
        return resultStream;
    }
    
    public default StreamPlus> segment(Predicate startCondition, Predicate endCondition) {
        return segment(startCondition, endCondition, true);
    }
    
    public default StreamPlus> segment(Predicate startCondition, Predicate endCondition, boolean includeTail) {
        val list = new AtomicReference<>(new ArrayList());
        val adding = new AtomicBoolean(false);
        
        return StreamPlus.from(
                map(i ->{
                    if (startCondition.test(i)) {
                        adding.set(true);
                    }
                    if (includeTail && adding.get()) list.get().add(i);
                    if (endCondition.test(i)) {
                        adding.set(false);
                        val retList = list.getAndUpdate(l -> new ArrayList());
                        return StreamPlus.from(retList.stream());
                    }
                    
                    if (!includeTail && adding.get()) list.get().add(i);
                    return null;
                }))
            .filterNonNull();
    }
    
    //-- Zip --
    
    public default  StreamPlus combine(Stream anotherStream, Func2 combinator) {
        return zipWith(anotherStream, ZipWithOption.RequireBoth)
                .map(combinator::applyTo);
    }
    public default  StreamPlus combine(Stream anotherStream, ZipWithOption option, Func2 combinator) {
        return zipWith(anotherStream, option)
                .map(combinator::applyTo);
    }
    
    public default  StreamPlus> zipWith(Stream anotherStream) {
        return zipWith(anotherStream, ZipWithOption.RequireBoth, Tuple2::of);
    }
    public default  StreamPlus> zipWith(Stream anotherStream, ZipWithOption option) {
        return zipWith(anotherStream, option, Tuple2::of);
    }
    
    public default  StreamPlus zipWith(Stream anotherStream, Func2 merger) {
        return zipWith(anotherStream, ZipWithOption.RequireBoth, merger);
    }
    // https://stackoverflow.com/questions/24059837/iterate-two-java-8-streams-together?noredirect=1&lq=1
    public default  StreamPlus zipWith(Stream anotherStream, ZipWithOption option, Func2 merger) {
        val iteratorA = this.iterator();
        val iteratorB = anotherStream.iterator();
        val iterable = new Iterable() {
            @Override
            public Iterator iterator() {
                return new Iterator() {
                    private boolean hasNextA;
                    private boolean hasNextB;
                    
                    public boolean hasNext() {
                        hasNextA = iteratorA.hasNext();
                        hasNextB = iteratorB.hasNext();
                        return (option == ZipWithOption.RequireBoth)
                                ? (hasNextA && hasNextB)
                                : (hasNextA || hasNextB);
                    }
                    public C next() {
                        val nextA = hasNextA ? iteratorA.next() : null;
                        val nextB = hasNextB ? iteratorB.next() : null;
                        return merger.apply(nextA, nextB);
                    }
                };
            }
            
        };
        return StreamPlus.from(StreamSupport.stream(iterable.spliterator(), false));
    }
    
    public default StreamPlus choose(Stream anotherStream, Func2 selectThisNotAnother) {
        return zipWith(anotherStream, AllowUnpaired)
                .map(t -> {
                    val _1 = t._1();
                    val _2 = t._2();
                    if ((_1 != null) && _2 == null)
                        return _1;
                    if ((_1 == null) && _2 != null)
                        return _2;
                    if ((_1 == null) && _2 == null)
                        return null;
                    val which = selectThisNotAnother.applyTo(t);
                    return which ? _1 : _2;
                })
                .filterNonNull();
    }
    public default StreamPlus merge(Stream anotherStream) {
        val iteratorA = this.iterator();
        val iteratorB = anotherStream.iterator();
        val iterable = new Iterable() {
            @Override
            public Iterator iterator() {
                return new Iterator() {
                    private boolean isA = true;
                    
                    public boolean hasNext() {
                        if (isA) {
                            if (iteratorA.hasNext()) return true;
                            isA = false;
                            if (iteratorB.hasNext()) return true;
                            return false;
                        }
                        
                        if (iteratorB.hasNext()) return true;
                        isA = true;
                        if (iteratorA.hasNext()) return true;
                        return false;
                    }
                    public DATA next() {
                        val next = isA ? iteratorA.next() : iteratorB.next();
                        isA = !isA;
                        return next;
                    }
                };
            }
            
        };
        return StreamPlus.from(StreamSupport.stream(iterable.spliterator(), false));
    }
    
    // TODO - collapse(IntFunction numbersToCollapse, Func2 concatFunc)
    
    @SuppressWarnings("unchecked")
    public default StreamPlus collapse(Predicate conditionToCollapse, Func2 concatFunc) {
        return terminate(stream -> {
            val iterator = stream.iterator();
            
            DATA first = null;
            try {
                first = iterator.next();
            } catch (NoSuchElementException e) {
                return StreamPlus.empty();
            }
            
            val prev = new AtomicReference(first);
            return StreamPlus.generateBy(()->{
                if (prev.get() == Helper.dummy)
                    throw new NoMoreResultException();
                
                while(true) {
                    DATA next;
                    try {
                        next = iterator.next();
                    } catch (NoSuchElementException e) {
                        val yield = prev.get();
                        prev.set(Helper.dummy);
                        return (DATA)yield;
                    }
                    if (conditionToCollapse.test(next)) {
                        prev.set(concatFunc.apply((DATA)prev.get(), next));
                    } else {
                        val yield = prev.get();
                        prev.set(next);
                        return (DATA)yield;
                    }
                }
            });
        });
    }
    
    //-- Plus w/ Self --
    
    public static class Helper {
        
        private static final Object dummy = new Object();
        
        public static  boolean hasAt(Stream stream, long index) {
            return hasAt(stream, index, null);
        }
        
        public static  boolean hasAt(Stream stream, long index, AtomicReference StreamPlusValue) {
            // Note: It is done this way to avoid interpreting 'null' as no-value
            
            val ref = new AtomicReference(dummy);
            stream
                .skip(index)
                .peek(value -> ref.set(value))
                .findFirst()
                .orElse(null);
            
            @SuppressWarnings("unchecked")
            val value = (T)ref.get();
            val found = (dummy != value);
            
            if (StreamPlusValue != null) {
                StreamPlusValue.set(found ? value : null);
            }
            
            return found;
        }
        
        public static  boolean equals(Stream stream1, Stream stream2) {
            return !from    (stream1)
                    .combine(from(stream2), AllowUnpaired, notEqual())
                    .filter (TRUE::equals)
                    .findAny()
                    .isPresent();
        }
        
        public static  int hashCode(Stream stream) {
            return stream
                    .mapToInt(e -> (e == null) ? 0 : e.hashCode())
                    .reduce(1, (h, eh) -> 31*h + eh);
        }
        
        public static  String toString(Stream stream) {
            return "[" + StreamPlus.from(stream).joinToString(", ") + "]";
        }
        
    }
    
}