Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
berlin.yuna.streamline.model.StreamLine Maven / Gradle / Ivy
Go to download
Performant, Concurrent, simplified Stream API leveraging Project Loom's virtual threads for efficient concurrent processing. Optimized for multithreaded environments
package berlin.yuna.streamline.model;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.*;
import java.util.stream.*;
/**
* {@link StreamLine} provides a simplified, high-performance Stream API utilizing exchangeable {@link ExecutorService},
* with a focus on leveragingProject Loom
's virtual and non-blocking threads for efficient concurrent processing ({@link StreamLine#VIRTUAL_EXECUTOR}).
* Unlike the default {@link Stream#parallel()}, {@link StreamLine} is optimized for multithreaded environments showcasing superior performance with a design that avoids the common pitfalls of resource management in stream operations.
* Usage Example:
*
* {@link StreamLine}.of("one", "two", "three")
* .threads(-1) // unlimited threads
* .forEach(System.out::println);
*
*
Performance and Scalability:
* {@link StreamLine} outperforms standard Java {@link Stream} in concurrent scenarios:
* Tested 10 Concurrent Streams with 10 Tasks each (10 Cores CPU).
*
* Loop [for]: 1.86s
* {@link Stream} [Sequential]: 1.29s
* {@link Stream} [Parallel]: 724ms
* {@link StreamLine} [Ordered]: 118ms
* {@link StreamLine} [Unordered]: 109ms
* {@link StreamLine} [2 Threads]: 500ms
*
*
Additional Methods:
* {@link StreamLine} comes with additional methods out of the box to avoid performance loss at {@link IntStream}, {@link LongStream} and {@link DoubleStream}
*
* {@link StreamLine#range(int, int)}: same as {@link IntStream#range(int, int)}
* {@link StreamLine#count()}: same as {@link IntStream#count()}
* {@link StreamLine#sum()}: same as {@link IntStream#sum()}
* {@link StreamLine#max()}: same as {@link IntStream#max()}
* {@link StreamLine#min()}: same as {@link IntStream#min()}
* {@link StreamLine#average()}: same as {@link IntStream#average()}
* {@link StreamLine#statistics()}: same as {@link IntStream#summaryStatistics()}
*
*
Note on Concurrency:
* Be mindful when handling functions like {@link Stream#forEach(Consumer)}; This function calls the consumer concurrently.
* This behaviour is the same as in {@link Stream#parallel()} but its more noticeable due to the performance of {@link StreamLine}.
*
Limitations:
* The concurrent processing does not extend to operations returning type-specific streams like {@link IntStream}, {@link LongStream}, {@link DoubleStream}, {@link OptionalInt}, {@link OptionalLong}, {@link OptionalDouble}, etc.
* {@link StreamLine} has more Terminal operations due its simple design
*
*
* @param the type of the stream elements
*/
@SuppressWarnings({"unchecked", "java:S1905"}) // Suppress SonarLint warning about casting to T
public class StreamLine implements Stream {
private final T[] source;
private final ExecutorService executor;
private final List> operations = new ArrayList<>();
private boolean ordered = true;
private int threads;
private Runnable closeHandler;
public static final ExecutorService VIRTUAL_EXECUTOR = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("virtual-thread-", 0).factory());
/**
* Constructs a stream with a custom executor and source elements.
*
* @param executor [Optional] The executor to handle parallel processing.
* @param values The source elements for the stream.
*/
public StreamLine(final ExecutorService executor, final T... values) {
this.source = values;
this.executor = executor != null ? executor : VIRTUAL_EXECUTOR;
threads = executor == null ? Math.max(2, Runtime.getRuntime().availableProcessors() / 2) : 10;
}
/**
* Creates a {@link StreamLine}. from given elements.
*
* @param values The elements to include in the new stream.
* @return A new {@link StreamLine}.
*/
public static StreamLine of(final T... values) {
return of(null, values);
}
/**
* Creates a {@link StreamLine}. from given elements and a custom executor.
*
* @param executor [Optional] The executor for parallel processing.
* @param values The elements to include in the new stream.
* @return A new {@link StreamLine}..
*/
public static StreamLine of(final ExecutorService executor, final T... values) {
return new StreamLine<>(executor, values);
}
/**
* Creates a {@link StreamLine}. from a single element.
*
* @param value The single element to create the stream from.
* @return A new {@link StreamLine}..
*/
public static StreamLine of(final T value) {
return of(null, (T[]) new Object[]{value});
}
/**
* Creates a {@link StreamLine}. from a single element with a custom executor.
*
* @param executor [Optional] The executor for parallel processing.
* @param value The single element to create the stream from.
* @return A new {@link StreamLine}..
*/
public static StreamLine of(final ExecutorService executor, final T value) {
return of(executor, (T[]) new Object[]{value});
}
/**
* Creates a {@link StreamLine}. representing a range of integers.
*
* @param endExclusive The end (exclusive) of the range.
* @return A new {@link StreamLine}..
*/
public static StreamLine range(final int endExclusive) {
return range(null, 0, endExclusive);
}
/**
* Creates a {@link StreamLine}. representing a range of integers.
*
* @param startInclusive The start (inclusive) of the range.
* @param endExclusive The end (exclusive) of the range.
* @return A new {@link StreamLine}..
*/
public static StreamLine range(final int startInclusive, final int endExclusive) {
return range(null, startInclusive, endExclusive);
}
/**
* Creates a {@link StreamLine}. representing a range of integers with a custom executor.
*
* @param executor [Optional] The executor for parallel processing.
* @param startInclusive The start (inclusive) of the range.
* @param endExclusive The end (exclusive) of the range.
* @return A new {@link StreamLine}..
*/
public static StreamLine range(final ExecutorService executor, final int startInclusive, final int endExclusive) {
final List range = new ArrayList<>();
for (int i = (startInclusive > -1 ? startInclusive : 0); i < endExclusive; i++) {
range.add(i);
}
return of(executor, range.toArray(Integer[]::new));
}
/**
* Returns whether this stream is ordered.
*
* @return True if the stream is ordered, false otherwise.
*/
public boolean ordered() {
return ordered;
}
/**
* Sets the ordered state of the stream.
* Intermediate operation
*
* @param ordered Whether the stream should be ordered.
* @return The current {@link StreamLine}..
*/
public StreamLine ordered(final boolean ordered) {
this.ordered = ordered;
return this;
}
/**
* Gets the limit of threads available for parallel processing. -1 all available executor threads.
*
* @return The number of threads.
*/
public int threads() {
return threads;
}
/**
* Sets the limit of threads for parallel processing. -1 all available executor threads.
* Intermediate operation
*
* @param threads The number of threads to use.
* @return The current {@link StreamLine}..
*/
public StreamLine threads(final int threads) {
this.threads = threads == 0 ? 1 : threads;
return this;
}
/**
* Returns the executor associated with this stream.
*
* @return The executor.
*/
public ExecutorService executor() {
return executor;
}
/**
* Applies a transformation function to each element of the stream.
* Intermediate operation
*
* @param mapper A function to apply to each element.
* @return A stream consisting of the results of applying the given function.
*/
@Override
public Stream map(final Function super T, ? extends R> mapper) {
operations.add((Function) mapper);
return (Stream) this;
}
/**
* Converts executes all operations and returns a new Stream with the results.
* For Performance, it's recommended to use the usual {@link StreamLine#map(Function)} functions instead.
* Terminal operation
.
*
* @param mapper A function to convert elements to int values.
* @return A new Stream from the {@link StreamLine} results.
*/
@Override
public IntStream mapToInt(final ToIntFunction super T> mapper) {
operations.add(item -> mapper.applyAsInt((T) item));
return (isParallel() ? Arrays.stream(executeTerminal()).parallel() : Arrays.stream(executeTerminal())).mapToInt(Integer.class::cast);
}
/**
* Converts executes all operations and returns a new Stream with the results.
* For Performance, it's recommended to use the usual {@link StreamLine#map(Function)} functions instead.
* Terminal operation
.
*
* @param mapper A function to convert elements to int values.
* @return A new Stream from the {@link StreamLine} results.
*/
@Override
public LongStream mapToLong(final ToLongFunction super T> mapper) {
operations.add(item -> mapper.applyAsLong((T) item));
return (isParallel() ? Arrays.stream(executeTerminal()).parallel() : Arrays.stream(executeTerminal())).mapToLong(Long.class::cast);
}
/**
* Converts executes all operations and returns a new Stream with the results.
* For Performance, it's recommended to use the usual {@link StreamLine#map(Function)} functions instead.
* Terminal operation
.
*
* @param mapper A function to convert elements to int values.
* @return A new Stream from the {@link StreamLine} results.
*/
@Override
public DoubleStream mapToDouble(final ToDoubleFunction super T> mapper) {
operations.add(item -> mapper.applyAsDouble((T) item));
return (isParallel() ? Arrays.stream(executeTerminal()).parallel() : Arrays.stream(executeTerminal())).mapToDouble(Double.class::cast);
}
/**
* Transforms each element into zero or more elements by applying a function to each element.
* Terminal operation
.
* Differs from {@link Stream#map(Function)} which is Intermediate operation .
* This closes the streams as soon as possible to keep things simple and continue with clean multi thread operations.
*
* @param mapper A function to apply to each element, which returns a stream of new values.
* @return A new stream consisting of all elements produced by applying the function to each element.
*/
@Override
public Stream flatMap(final Function super T, ? extends Stream extends R>> mapper) {
operations.add(item -> mapper.apply((T) item));
return (Stream) StreamLine.of(executor, Arrays.stream(executeTerminal()).flatMap(item -> (Stream) item).toArray()).ordered(ordered).threads(threads);
}
/**
* Converts elements of this stream to a {@link IntStream} by applying a function that produces a {@link IntStream} for each element.
* Terminal operation
.
* It's recommended to use the usual {@link StreamLine#map(Function)} as the performance of {@link StreamLine} ends here.
*
* @param mapper A function to apply to each element, which returns a {@link IntStream} of new values.
* @return A new {@link IntStream} consisting of all long values produced by applying the function to each element.
*/
@Override
public IntStream flatMapToInt(final Function super T, ? extends IntStream> mapper) {
operations.add(item -> mapper.apply((T) item));
return IntStream.of(Arrays.stream(executeTerminal()).flatMapToInt(IntStream.class::cast).toArray());
}
/**
* Converts elements of this stream to a {@link LongStream} by applying a function that produces a {@link LongStream} for each element.
* Terminal operation
.
* It's recommended to use the usual {@link StreamLine#map(Function)} as the performance of {@link StreamLine} ends here.
*
* @param mapper A function to apply to each element, which returns a {@link LongStream} of new values.
* @return A new {@link LongStream} consisting of all long values produced by applying the function to each element.
*/
@Override
public LongStream flatMapToLong(final Function super T, ? extends LongStream> mapper) {
operations.add(item -> mapper.apply((T) item));
return LongStream.of(Arrays.stream(executeTerminal()).flatMapToLong(LongStream.class::cast).toArray());
}
/**
* Converts elements of this stream to a {@link DoubleStream} by applying a function that produces a {@link DoubleStream} for each element.
* Terminal operation
.
* It's recommended to use the usual {@link StreamLine#map(Function)} as the performance of {@link StreamLine} ends here.
*
* @param mapper A function to apply to each element, which returns a {@link DoubleStream} of new values.
* @return A new {@link DoubleStream} consisting of all long values produced by applying the function to each element.
*/
@Override
public DoubleStream flatMapToDouble(final Function super T, ? extends DoubleStream> mapper) {
operations.add(item -> mapper.apply((T) item));
return DoubleStream.of(Arrays.stream(executeTerminal()).flatMapToDouble(DoubleStream.class::cast).toArray());
}
/**
* Returns a stream consisting of distinct elements (according to Object.equals(Object)).
* Intermediate operation
*
* @return A stream consisting of the distinct elements of this stream.
*/
@Override
public Stream distinct() {
final Set seen = ConcurrentHashMap.newKeySet();
operations.add(item -> seen.add((T) item) ? item : null);
return this;
}
/**
* Returns a stream consisting of the elements of this stream, sorted according to natural order.
* Intermediate operation
*
* @return A stream consisting of the sorted elements of this stream.
*/
@Override
public Stream sorted() {
return sorted(null);
}
/**
* Returns a stream consisting of the elements of this stream, sorted according to the provided Comparator.
* Terminal operation
*
* @param comparator A Comparator to be used to compare stream elements.
* @return A new stream consisting of the sorted elements of this stream.
*/
@Override
public Stream sorted(final Comparator super T> comparator) {
final T[] values = executeTerminal();
Arrays.sort(values, comparator);
return StreamLine.of(executor, values).ordered(ordered).threads(threads);
}
/**
* Returns a stream consisting of the elements of this stream, each modified by the given function.
* Intermediate operation
*
* @param action A non-interfering action to perform on the elements as they are consumed from the stream.
* @return A stream consisting of the elements after applying the given action.
*/
@Override
public Stream peek(final Consumer super T> action) {
operations.add(item -> {
action.accept((T) item);
return item;
});
return this;
}
/**
* Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length.
* Terminal operation
*
* @param maxSize The maximum number of elements the stream should be limited to.
* @return A new stream consisting of the elements of this stream, truncated to maxSize in length.
*/
@Override
public Stream limit(final long maxSize) {
final T[] values = executeTerminal();
return maxSize < 1 || maxSize > values.length ? this : new StreamLine<>(executor, Arrays.copyOfRange(values, 0, (int) Math.min(maxSize, values.length))).ordered(ordered).threads(threads);
}
/**
* Returns a stream consisting of the remaining elements of this stream after discarding the first n elements.
* Terminal operation
*
* @param n The number of leading elements to skip.
* @return A new stream consisting of the remaining elements of this stream after skipping the first n elements.
*/
@Override
public Stream skip(final long n) {
final T[] values = executeTerminal();
return n < 1 ? this : new StreamLine<>(executor, Arrays.copyOfRange(values, (int) Math.min(n, values.length), values.length)).ordered(ordered).threads(threads);
}
/**
* Returns a stream consisting of the elements of this stream that match the given predicate.
* Intermediate operation
*
* @param predicate A predicate to apply to each element to determine if it should be included.
* @return A stream consisting of the elements of this stream that match the given predicate.
*/
@Override
public Stream filter(final Predicate super T> predicate) {
operations.add(item -> predicate.test((T) item) ? item : null);
return this;
}
/**
* Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty.
* When order does not matter, then {@link StreamLine#findAny()} is recommended to keep the performance and close the stream as soon as possible.
* Terminal operation
*
* @return An Optional describing the first element of this stream or an empty Optional if the stream is empty.
*/
@Override
public Optional findFirst() {
final T[] result = executeTerminal(true, false);
return result.length == 0 ? Optional.empty() : Optional.ofNullable(result[0]);
}
/**
* Returns an Optional describing some element of the stream, or an empty Optional if the stream is empty. This is a non-deterministic version of {@link StreamLine#findFirst()}.
* Terminal operation
*
* @return An Optional describing some element of the stream, or an empty Optional if the stream is empty.
*/
@Override
public Optional findAny() {
final T[] array = executeTerminal(true, true);
return Optional.ofNullable(array.length == 0 ? null : array[0]);
}
/**
* Returns an iterator over the elements in this stream.
* Terminal operation
*
* @return An Iterator over the elements in this stream.
*/
@Override
public Iterator iterator() {
return List.of(executeTerminal()).iterator();
}
/**
* Creates a Spliterator over the elements in this stream.
* Terminal operation
*
* @return (16464) A Spliterator over the elements in this stream.
* This spliterator is consistent and independent of the result of the stream pipeline.
*/
@Override
public Spliterator spliterator() {
return List.of(executeTerminal()).spliterator();
}
/**
* Returns a sequential stream considering all operations are to be performed in encounter order.
* Intermediate operation
* Sets {@link StreamLine#threads} to 1 - see {@link StreamLine#threads(int)}.
*
* @return A sequential Stream.
*/
@Override
public Stream sequential() {
threads(1);
return this;
}
/**
* Returns a possibly parallel stream considering all operations may be performed in any order.
* Intermediate operation
* Sets {@link StreamLine#threads} to 2 if it was 1 before - see {@link StreamLine#threads(int)}.
*
* @return A possibly parallel Stream.
*/
@Override
public Stream parallel() {
return threads == 1 ? this.threads(2) : this;
}
/**
* Sets stream processing to unordered, which can improve performance. See also {@link StreamLine#ordered(boolean)}
* Intermediate operation
*
* @return An unordered Stream.
*/
@Override
public Stream unordered() {return this.ordered(false);}
/**
* Returns the same stream with a close handler attached.
* Intermediate operation
*
* @param closeHandler A Runnable that will be executed when the stream is closed.
* @return The same Stream with a close handler attached.
*/
@Override
public Stream onClose(final Runnable closeHandler) {
this.closeHandler = closeHandler;
return this;
}
/**
* Closes the stream, causing all close handlers for this stream pipeline to be executed.
* {@link StreamLine} does have nothing to clean up, so this method is a no-op.
*/
@Override
public void close() {
operations.clear();
if (closeHandler != null) {
closeHandler.run();
}
}
/**
* Returns whether this stream would execute tasks in parallel. True
if the stream is parallel, otherwise false.
*
* @return True if threads != 1
, otherwise false - see also {@link StreamLine#threads(int)} ().
*/
@Override
public boolean isParallel() {
return threads != 1;
}
/**
* Performs an action for each element of this stream concurrently , without regard to the order of elements.
* This method does not guarantee thread safety; users must ensure that the provided action is thread-safe.
* Terminal operation
.
*
* forEach(Consumer): Asynchronous, Concurrently, Unordered, No Thread Safety
* {@link StreamLine#forEachSync(Consumer)}: Synchronous, Unordered, Thread Safe
* {@link StreamLine#forEachOrdered(Consumer)}: Synchronous, Ordered, Thread Safe
*
*
* @param action A non-interfering action to perform on the elements of this stream.
*/
@Override
public void forEach(final Consumer super T> action) {
forEachAsync(null, executeTerminal(false, false), pair -> action.accept(pair.getValue()));
}
/**
* Performs an action for each element synchronously
* It is required to take care of thread safety in the action.
* Terminal operation
.
*
* {forEachSync(Consumer): Synchronous, Unordered, Thread Safe
* {@link StreamLine#forEach(Consumer)}: Asynchronous, Concurrently, Unordered, No Thread Safety
* {@link StreamLine#forEachOrdered(Consumer)}: Synchronous, Ordered, Thread Safe
*
*
* @param action A non-interfering action to perform on the elements of this stream.
*/
public void forEachSync(final Consumer super T> action) {
for (final T item : executeTerminal()) {
action.accept(item);
}
}
/**
* Performs a synchronous action for each element of this stream, preserving the encounter order of the stream.
* Terminal operation
.
*
* forEachOrdered(Consumer): Synchronous, Ordered, Thread Safe
* {@link StreamLine#forEach(Consumer)}: Asynchronous, Concurrently, Unordered, No Thread Safety
* {@link StreamLine#forEachSync(Consumer)}: Synchronous, Unordered, Thread Safe
*
*
* @param action A non-interfering action to perform on the elements of this stream.
*/
@Override
public void forEachOrdered(final Consumer super T> action) {
for (final T item : executeTerminal(true, false)) {
action.accept(item);
}
}
/**
* Returns an array containing the elements of this stream.
* The order depends on {@link StreamLine#ordered(boolean)}
* Terminal operation
.
*
* @return An array containing the elements of this stream.
*/
@Override
public Object[] toArray() {
return executeTerminal();
}
/**
* Returns an array containing the elements of this stream, using the provided generator function to allocate the returned array.
* The order depends on {@link StreamLine#ordered(boolean)}
* Terminal operation
.
*
* @param generator A function which produces a new array of the desired type and the provided length.
* @return An array containing the elements of this stream.
*/
@Override
public A[] toArray(final IntFunction generator) {
return List.of(executeTerminal()).toArray(generator);
}
/**
* Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value.
* Terminal operation
.
*
* @param identity The identity value for the accumulation function.
* @param accumulator An associative, non-interfering, stateless function for combining two values.
* @return The result of the reduction.
*/
@Override
public T reduce(final T identity, final BinaryOperator accumulator) {
T result = identity;
for (final T item : executeTerminal()) {
result = accumulator.apply(result, item);
}
return result;
}
/**
* Performs a reduction on the elements of this stream using only a binary operator, starting from the first element as the initial value.
* This variant of reduce does not take an identity value; thus, it returns an Optional to account for empty streams.
* Terminal operation
*
* @param accumulator An associative, non-interfering, stateless function for combining two values.
* @return An Optional describing the result of the reduction or an empty Optional if the stream is empty.
*/
@Override
public Optional reduce(final BinaryOperator accumulator) {
T result = null;
for (final T item : executeTerminal()) {
if (result == null) {
result = item; // The first item is the initial value
} else {
result = accumulator.apply(result, item); // Apply the accumulator
}
}
return Optional.ofNullable(result);
}
/**
* Performs a reduction on the elements of this stream using an identity value, an accumulator, and a combiner function.
* This method is intended for use where parallelism is involved, although this implementation does not specifically handle parallel execution.
* Terminal operation
*
* @param identity The identity value for the accumulation function.
* @param accumulator A function that takes two parameters: a partial result and the next element, and combines them.
* @param combiner A function used to combine the partial results. This is mainly used in a parallel context.
* @return The result of the reduction.
*/
@Override
public U reduce(final U identity, final BiFunction accumulator, final BinaryOperator combiner) {
U result = identity;
for (final T item : executeTerminal()) {
result = accumulator.apply(result, item);
}
return result;
}
@Override
public R collect(final Supplier supplier, final BiConsumer accumulator, final BiConsumer combiner) {
final R[] results = Arrays.stream(executeTerminal()).map(item -> {
final R container = supplier.get();
accumulator.accept(container, item);
return container;
}).toArray(size -> (R[]) new Object[size]);
final R resultContainer = supplier.get();
for (final R result : results) {
combiner.accept(resultContainer, result);
}
return resultContainer;
}
/**
* Performs a mutable reduction operation on the elements of this stream using a collector.
* A Collector encapsulates the functions used as arguments to collect(), which can accommodate a wide range of reduction operations.
* Terminal operation
*
* @param collector The collector encoding the reduction operation.
* @return The result of the reduction.
*/
@Override
public R collect(final Collector super T, A, R> collector) {
final A resultContainer = collector.supplier().get();
executeTerminal();
for (final T item : executeTerminal()) {
collector.accumulator().accept(resultContainer, item);
}
return collector.finisher().apply(resultContainer);
}
@Override
public Optional min(final Comparator super T> comparator) {
return reduce(BinaryOperator.minBy(comparator));
}
@Override
public Optional max(final Comparator super T> comparator) {
return reduce(BinaryOperator.maxBy(comparator));
}
public double sum() {
return Arrays.stream(executeTerminal()).filter(Number.class::isInstance).map(Number.class::cast).mapToDouble(Number::doubleValue).sum();
}
public OptionalDouble max() {
return Arrays.stream(executeTerminal()).filter(Number.class::isInstance).map(Number.class::cast).mapToDouble(Number::doubleValue).max();
}
public OptionalDouble min() {
return Arrays.stream(executeTerminal()).filter(Number.class::isInstance).map(Number.class::cast).mapToDouble(Number::doubleValue).min();
}
public OptionalDouble average() {
return Arrays.stream(executeTerminal()).filter(Number.class::isInstance).map(Number.class::cast).mapToDouble(Number::doubleValue).average();
}
public DoubleSummaryStatistics statistics() {
return Arrays.stream(executeTerminal()).filter(Number.class::isInstance).map(Number.class::cast).mapToDouble(Number::doubleValue).summaryStatistics();
}
@Override
public long count() {
return executeTerminal().length;
}
/**
* Determines whether any elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result.
* This is a short-circuiting terminal operation.
* Terminal operation
*
* @param predicate A predicate to apply to elements to determine a match.
* @return true if any elements of the stream match the provided predicate, otherwise false.
*/
@Override
public boolean anyMatch(final Predicate super T> predicate) {
final AtomicBoolean result = new AtomicBoolean(false);
final AtomicBoolean terminate = new AtomicBoolean(false);
forEachAsync(terminate, executeTerminal(), item -> {
if (item.getValue() != null && predicate.test((T) item.getValue()) && !result.getAndSet(true)) {
terminate.set(true);
}
});
return result.get();
}
/**
* Determines whether all elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result.
* This is a short-circuiting terminal operation.
* Terminal operation
*
* @param predicate A predicate to apply to elements to determine a match.
* @return true if all elements of the stream match the provided predicate, otherwise false.
*/
@Override
public boolean allMatch(final Predicate super T> predicate) {
final AtomicBoolean result = new AtomicBoolean(true);
final AtomicBoolean terminate = new AtomicBoolean(false);
forEachAsync(terminate, executeTerminal(), item -> {
if (item.getValue() != null && !predicate.test((T) item.getValue()) && result.getAndSet(false)) {
terminate.set(true);
}
});
return result.get();
}
/**
* Determines whether no elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result.
* This is a short-circuiting terminal operation.
* Terminal operation
*
* @param predicate A predicate to apply to elements to determine a match.
* @return true if no elements of the stream match the provided predicate, otherwise false.
*/
@Override
public boolean noneMatch(final Predicate super T> predicate) {
return !anyMatch(predicate);
}
protected void forEachAsync(final AtomicBoolean terminate, final I[] values, final Consumer> runnable) {
final Semaphore semaphore = threads > 0 ? new Semaphore(threads) : null;
final List> futures = new ArrayList<>();
final AtomicInteger index = new AtomicInteger(0);
for (final I item : values) {
final int currentIndex = index.getAndIncrement();
try {
if (terminate != null && terminate.get()) {
break;
}
if (semaphore != null) {
semaphore.acquire();
}
futures.add(executor.submit(() -> {
try {
runnable.accept(new AbstractMap.SimpleImmutableEntry<>(currentIndex, item));
} finally {
if (semaphore != null) {
semaphore.release();
}
}
}));
} catch (final InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
waitFor(futures);
}
protected T[] executeTerminal() {
return executeTerminal(ordered, false);
}
protected T[] executeTerminal(final boolean ordered, final boolean findAny) {
final Map indexedResults = new ConcurrentHashMap<>();
final AtomicBoolean terminate = new AtomicBoolean(false);
forEachAsync(terminate, source, item -> {
if (findAny && !indexedResults.isEmpty()) {
terminate.set(true);
} else {
final Object result = applyOperations(item.getValue());
if (result != null) {
indexedResults.put(item.getKey(), (T) result);
}
}
});
// Collect results in the original order
return ordered
? IntStream.range(0, source.length).mapToObj(indexedResults::get).filter(Objects::nonNull).toArray(size -> (T[]) new Object[size])
: indexedResults.values().toArray((T[]) new Object[0]);
}
@SuppressWarnings("java:S4276") // Suppress SonarLint warning about unchecked cast
protected Object applyOperations(final Object item) {
Object result = item;
for (final Function operation : operations) {
result = operation.apply(result);
if (result == null) {
break;
}
}
return result;
}
public static void waitFor(final List> futures) {
// Wait for all futures to complete
for (final Future> future : futures) {
try {
future.get();
} catch (final InterruptedException ie) {
Thread.currentThread().interrupt();
} catch (final ExecutionException ee) {
final Throwable cause = ee.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
throw new IllegalStateException("Exception in thread execution", cause);
}
}
}
}
}