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

panda.std.stream.PandaStream Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021 dzikoysk
 *
 * 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 panda.std.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiFunction;
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.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import panda.std.Option;
import panda.std.Pair;
import panda.std.Result;

/**
 * Simple wrapper to combine standard {@link java.util.stream.Stream} API with wrappers like
 * {@link panda.std.Option} or {@link panda.std.Result}, and some extra features.
 * Most methods are lazy evaluated as in Stream API, but some of them are not!
 * In most cases it shouldn't be a problem, but for huge sets or performance sensitive use-cases
 * you should be aware of methods you use, especially these based on {@link #duplicate()} feature.
 *
 * @param 
 */
public class PandaStream implements AutoCloseable {

    private Stream stream;

    private PandaStream(Stream stream) {
        this.stream = stream;
    }

    @Override
    public void close() {
        stream.close();
    }

    public  PandaStream stream(Function, Stream> function) {
        return new PandaStream<>(function.apply(stream));
    }

    public PandaStream concat(Stream stream) {
        this.stream = Stream.concat(this.stream, stream);
        return this;
    }

    public PandaStream concat(PandaStream pandaStream) {
        this.stream = Stream.concat(this.stream, pandaStream.stream);
        return this;
    }

    public PandaStream concat(Iterable iterable) {
        return concat(PandaStream.of(iterable));
    }

    @SafeVarargs
    public final PandaStream concat(T... elements) {
        return concat(Stream.of(elements));
    }

    public  PandaStream> associateWith(R value) {
        return map(element -> Pair.of(element, value));
    }

    public  PandaStream transform(Function, Stream> function) {
        return stream(function);
    }

    public  PandaStream map(Function function) {
        return new PandaStream<>(stream.map(function));
    }

    public  PandaStream mapWith(A with, BiFunction function) {
        return map(element -> function.apply(with, element));
    }

    public  PandaStream mapOpt(Function> function) {
        return map(function)
                .filter(Option::isDefined)
                .map(Option::get);
    }

    public  PandaStream flatMap(Function> function) {
        return new PandaStream<>(stream.flatMap(value -> StreamSupport.stream(function.apply(value).spliterator(), false)));
    }

    public  PandaStream flatMapWith(A with, BiFunction> function) {
        return flatMap(element -> function.apply(with, element));
    }

    public  PandaStream flatMapStream(Function> function) {
        return new PandaStream<>(stream.flatMap(function));
    }

    public  PandaStream is(Class type) {
        if (type.isPrimitive()) {
            type = StreamUtils.convertPrimitiveToWrapper(type);
        }

        return this
                .filter(type::isInstance)
                .map(type::cast);
    }

    public PandaStream isNot(Class type) {
        if (type.isPrimitive()) {
            type = StreamUtils.convertPrimitiveToWrapper(type);
        }

        return this.filterNot(type::isInstance);
    }

    public PandaStream filter(Predicate predicate) {
        return with(stream.filter(predicate));
    }

    public PandaStream filterNot(Predicate predicate) {
        return with(stream.filter(obj -> !predicate.test(obj)));
    }

    public  Result, E> filterToResult(Function> predicate) {
        return findIterating(predicate)
                .map(Result::, E> error)
                .orElseGet(Result.ok(this));
    }

    /**
     * Find first element in stream or return all failures.
     * The size of list with errors may be equal to number of all elements in stream,
     * so it shouldn't be used with large datasets.
     *
     * @param searchFunction search function may return success (matched element, terminates stream) or failure (to continue searching).
     * @param  type of matched element
     * @param  type of failures
     * @return result with matched element or list of failures
     */
    public  Result> search(Function> searchFunction) {
        List errors = new ArrayList<>();

        return this
                .map(value -> searchFunction.apply(value).onError(errors::add))
                .filter(Result::isOk)
                .head()
                .map(Result::> projectToValue)
                .orElseGet(() -> Result.error(errors));
    }

    public PandaStream distinct() {
        return with(stream.distinct());
    }

    public PandaStream sorted() {
        return with(stream.sorted());
    }

    public PandaStream sorted(Comparator comparator) {
        return with(stream.sorted(comparator));
    }

    public PandaStream shuffle() {
        return PandaStream.of(this.toShuffledList());
    }

    public PandaStream skip(long n) {
        return with(stream.skip(n));
    }

    public Option find(Predicate predicate) {
        return filter(predicate).head();
    }

    public Option head() {
        return Option.ofOptional(stream.findFirst());
    }

    public Option last() {
        return Option.ofOptional(stream.reduce((first, second) -> second));
    }

    public Option any() {
        return Option.ofOptional(stream.findAny());
    }

    public long count(Predicate predicate) {
        return filter(predicate).count();
    }

    public long count() {
        return stream.count();
    }

    private PandaStream with(Stream stream) {
        this.stream = stream;
        return this;
    }

    public  R collect(Collector collector) {
        return stream.collect(collector);
    }

    public  PandaStream throwIfNot(Predicate condition, Function exception) {
        return with(stream.peek(element -> {
            if (!condition.test(element)) {
                throwException(exception.apply(element));
            }
        }));
    }

    @SuppressWarnings({ "unchecked", "UnusedReturnValue" })
    private static  R throwException(Throwable throwable) throws E {
        throw (E) throwable;
    }

    public PandaStream takeWhile(Predicate condition) {
        return new PandaStream<>(StreamSupport.stream(new TakeWhileSpliterator<>(stream.spliterator(), condition), false));
    }

    public PandaStream forEach(Consumer consumer) {
        stream.forEach(consumer);
        return this;
    }

    public  Result, E> forEachByResult(Function> predicate) {
        return findIterating(predicate)
                .map(Result::, E> error)
                .orElseGet(Result.ok(this));
    }

    public  Option findIterating(Function> predicate) {
        Iterator iterator = duplicate().iterator();

        while (iterator.hasNext()) {
            T element = iterator.next();
            Option result = predicate.apply(element);

            if (result.isDefined()) {
                return result;
            }
        }

        return Option.none();
    }

    /**
     * Simulates the stream duplication mechanism.
     * The method transforms current stream into buffered list of elements and then recreates current stream and the duplicated one on top of that.
     * This method should not be used to handle huge sets and may be a bottleneck if called often.
     *
     * @return duplicated stream
     */
    public PandaStream duplicate() {
        List buffer = toList();
        stream = buffer.stream();
        return of(buffer);
    }

    public T[] toArray(IntFunction function) {
        return stream.toArray(function);
    }

    public List toList() {
        return stream.collect(Collectors.toList());
    }

    public List toShuffledList(Random random) {
        return stream.collect(PandaCollectors.shufflingCollector(random));
    }

    public List toShuffledList() {
        return toShuffledList(ThreadLocalRandom.current());
    }

    public Set toSet() {
        return stream.collect(Collectors.toSet());
    }

    public  Map toMap(Function keyMapper, Function valueMapper) {
        return toMap(HashMap::new, keyMapper, valueMapper);
    }

    public  Map toMap(Supplier> mapSupplier, Function keyMapper, Function valueMapper) {
        return stream.collect(Collectors.toMap(keyMapper, valueMapper, PandaCollectors.throwingMerger(), mapSupplier));
    }

    public  Map toMapByPair(Supplier> mapSupplier, Function> mapper) {
        return toMap(mapSupplier, key -> mapper.apply(key).getFirst(), value -> mapper.apply(value).getSecond());
    }

    public  Map toMapByPair(Function> mapper) {
        return toMapByPair(HashMap::new, mapper);
    }

    public Stream toStream() {
        return stream;
    }

    public Iterator iterator() {
        return stream.iterator();
    }

    public static  PandaStream of(Stream stream) {
        return new PandaStream<>(stream);
    }

    public static  PandaStream of(Collection collection) {
        return of(collection.stream());
    }

    public static  PandaStream of(Iterable iterable) {
        return of(StreamSupport.stream(iterable.spliterator(), false));
    }

    @SafeVarargs
    public static  PandaStream of(T... array) {
        return of(Arrays.stream(array));
    }

    public static  PandaStream flatOf(Iterable> iterable) {
        return of(iterable).flatMap(it -> it);
    }

    @SafeVarargs
    public static  PandaStream flatOf(Iterable... array) {
        return flatOf(Arrays.asList(array));
    }

    public static  PandaStream empty() {
        return new PandaStream<>(Stream.empty());
    }

}