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 super T, Option> 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 super T> 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 super T, A, R> 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 super T> 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 super T, Option> 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy