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

net.jqwik.engine.support.StreamConcatenation Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
package net.jqwik.engine.support;

import org.jspecify.annotations.*;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

// @formatter:off
/**
 * 

* Stolen from https://github.com/TechEmpower/misc/blob/master/concat/src/main/java/rnd/StreamConcatenation.java. * See discussion in https://github.com/jqwik-team/jqwik/issues/526 why StreamConcatenation is used. *

* * Utility methods for concatenating streams. * * @author Michael Hixson */ final class StreamConcatenation { private StreamConcatenation() { throw new AssertionError("This class cannot be instantiated"); } /** * Creates a lazily concatenated stream whose elements are the elements of * each of the input streams. In other words, the returned stream contains * all the elements of the first input stream followed by all the elements of * the second input stream, and so on. * *

Although this method does not eagerly consume the elements of the input * streams, this method is a terminal operation for the input streams. * *

The returned stream is parallel if any of the input streams is parallel. * *

When the returned stream is closed, the close handlers for all the input * streams are invoked. If one of those handlers throws an exception, that * exception will be rethrown after the remaining handlers are invoked. If * the remaining handlers throw exceptions, those exceptions are added as * suppressed exceptions of the first. * *

If the argument array or any of the input streams are modified after * being passed to this method, the behavior of this method is undefined. * * @param the type of stream elements * @param streams the streams to be concatenated * @return the concatenation of the input streams * @throws NullPointerException if the argument array or any of the input * streams are {@code null} */ @SafeVarargs @SuppressWarnings({"unchecked", "varargs"}) // TODO: Explain why this is ok. public static Stream concat(Stream... streams) { return concatInternal( (Stream[]) streams, size -> (Spliterator[]) new Spliterator[size], Stream::spliterator, ConcatSpliterator.OfRef::new, StreamSupport::stream ); } /** * Creates a lazily concatenated stream whose elements are the elements of * each of the input streams. In other words, the returned stream contains * all the elements of the first input stream followed by all the elements of * the second input stream, and so on. * *

Although this method does not eagerly consume the elements of the input * streams, this method is a terminal operation for the input streams. * *

The returned stream is parallel if any of the input streams is parallel. * *

When the returned stream is closed, the close handlers for all the input * streams are invoked. If one of those handlers throws an exception, that * exception will be rethrown after the remaining handlers are invoked. If * the remaining handlers throw exceptions, those exceptions are added as * suppressed exceptions of the first. * *

If the argument array or any of the input streams are modified after * being passed to this method, the behavior of this method is undefined. * * @param streams the streams to be concatenated * @return the concatenation of the input streams * @throws NullPointerException if the argument array or any of the input * streams are {@code null} */ public static IntStream concat(IntStream... streams) { return concatInternal( streams, Spliterator.OfInt[]::new, IntStream::spliterator, ConcatSpliterator.OfInt::new, StreamSupport::intStream ); } /** * Creates a lazily concatenated stream whose elements are the elements of * each of the input streams. In other words, the returned stream contains * all the elements of the first input stream followed by all the elements of * the second input stream, and so on. * *

Although this method does not eagerly consume the elements of the input * streams, this method is a terminal operation for the input streams. * *

The returned stream is parallel if any of the input streams is parallel. * *

When the returned stream is closed, the close handlers for all the input * streams are invoked. If one of those handlers throws an exception, that * exception will be rethrown after the remaining handlers are invoked. If * the remaining handlers throw exceptions, those exceptions are added as * suppressed exceptions of the first. * *

If the argument array or any of the input streams are modified after * being passed to this method, the behavior of this method is undefined. * * @param streams the streams to be concatenated * @return the concatenation of the input streams * @throws NullPointerException if the argument array or any of the input * streams are {@code null} */ public static LongStream concat(LongStream... streams) { return concatInternal( streams, Spliterator.OfLong[]::new, LongStream::spliterator, ConcatSpliterator.OfLong::new, StreamSupport::longStream ); } /** * Creates a lazily concatenated stream whose elements are the elements of * each of the input streams. In other words, the returned stream contains * all the elements of the first input stream followed by all the elements of * the second input stream, and so on. * *

Although this method does not eagerly consume the elements of the input * streams, this method is a terminal operation for the input streams. * *

The returned stream is parallel if any of the input streams is parallel. * *

When the returned stream is closed, the close handlers for all the input * streams are invoked. If one of those handlers throws an exception, that * exception will be rethrown after the remaining handlers are invoked. If * the remaining handlers throw exceptions, those exceptions are added as * suppressed exceptions of the first. * *

If the argument array or any of the input streams are modified after * being passed to this method, the behavior of this method is undefined. * * @param streams the streams to be concatenated * @return the concatenation of the input streams * @throws NullPointerException if the argument array or any of the input * streams are {@code null} */ public static DoubleStream concat(DoubleStream... streams) { return concatInternal( streams, Spliterator.OfDouble[]::new, DoubleStream::spliterator, ConcatSpliterator.OfDouble::new, StreamSupport::doubleStream ); } // The generics and function objects are ugly, but this method lets us reuse // the same logic in all the public concat(...) methods. private static < T, T_SPLITR extends Spliterator, T_STREAM extends BaseStream> T_STREAM concatInternal( T_STREAM[] streams, IntFunction arrayFunction, Function spliteratorFunction, Function concatFunction, BiFunction streamFunction ) { T_SPLITR[] spliterators = arrayFunction.apply(streams.length); boolean parallel = false; for (int i = 0; i < streams.length; i++) { T_STREAM inStream = streams[i]; T_SPLITR inSpliterator = spliteratorFunction.apply(inStream); spliterators[i] = inSpliterator; parallel = parallel || inStream.isParallel(); } T_SPLITR outSpliterator = concatFunction.apply(spliterators); T_STREAM outStream = streamFunction.apply(outSpliterator, parallel); return outStream.onClose(new ComposedClose(streams)); } abstract static class ConcatSpliterator> implements Spliterator { final T_SPLITR[] spliterators; int low; // increases only after trySplit() int cursor; // increases after iteration or trySplit() final int high; ConcatSpliterator(T_SPLITR[] spliterators, int fromIndex, int toIndex) { this.spliterators = spliterators; low = cursor = fromIndex; high = toIndex; } // Having these two abstract methods let us reuse the same trySplit() // implementation in all subclasses. // invokes spliterator.trySplit() on the argument abstract T_SPLITR invokeTrySplit(T_SPLITR spliterator); // invokes our constructor with the same array but modified bounds abstract T_SPLITR slice(int fromIndex, int toIndex); @Override public int characteristics() { int i = low; // ignore the cursor; iteration can't affect characteristics if (i >= high) { // TODO(perf): This may report fewer characteristics than it should. return Spliterators.emptySpliterator().characteristics(); } if (i == high - 1) { // note for getComparator(): this is the only time we might be SORTED return spliterators[i].characteristics(); } // // DISTINCT and SORTED are *not* safe to inherit. Imagine our input // spliterators each contain the elements [1, 2] (distinct and sorted) and // so we are [1, 2, 1, 2] (neither distinct nor sorted). // // SIZED and SUBSIZED might be safe to inherit, but we have to account for // arithmetic overflow. If we're unable to produce a correct sum in // estimateSize() due to overflow, we must not report SIZED (or SUBSIZED). // // ORDERED, NONNULL, IMMUTABLE, and CONCURRENT are safe to inherit. // // We assume that all other characteristics not listed here (that do not // exist yet at the time of this writing) are *not* safe to inherit. // False negatives generally result in degraded performance, while false // positives generally result in incorrect behavior. // int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.CONCURRENT; long size = 0; do { Spliterator spliterator = spliterators[i]; characteristics &= spliterator.characteristics(); if ((characteristics & Spliterator.SIZED) == Spliterator.SIZED) { size += spliterator.estimateSize(); if (size < 0) { // overflow characteristics &= ~(Spliterator.SIZED | Spliterator.SUBSIZED); } } } while (++i < high); return characteristics; } @Override public long estimateSize() { long size = 0; for (int i = cursor; i < high; i++) { size += spliterators[i].estimateSize(); if (size < 0) { // overflow return Long.MAX_VALUE; } } return size; } @Override public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); int i = cursor; if (i < high) { do { spliterators[i].forEachRemaining(action); } while (++i < high); cursor = high; } } @Override public Comparator getComparator() { int i = low; // like characteristics() if (i == high - 1) { // this is the only time we might be SORTED; see characteristics() return spliterators[i].getComparator(); } throw new IllegalStateException(); } @Override public boolean tryAdvance(Consumer action) { Objects.requireNonNull(action); int i = cursor; if (i < high) { do { if (spliterators[i].tryAdvance(action)) { cursor = i; return true; } } while (++i < high); cursor = high; } return false; } @Override public T_SPLITR trySplit() { // // TODO(perf): Should we split differently when we're SIZED? // // 1) Rather than splitting our *spliterators* in half, we could try to // split our *elements* in half. // // 2) We could refuse to split if our total element count is lower than // some threshold. // int i = cursor; if (i >= high) { return null; } if (i == high - 1) { return invokeTrySplit(spliterators[i]); } int mid = (i + high) >>> 1; low = cursor = mid; if (mid == i + 1) { return spliterators[i]; } return slice(i, mid); } static final class OfRef extends ConcatSpliterator> { OfRef(Spliterator[] spliterators) { super(spliterators, 0, spliterators.length); } OfRef(Spliterator[] spliterators, int fromIndex, int toIndex) { super(spliterators, fromIndex, toIndex); } @Override Spliterator invokeTrySplit(Spliterator spliterator) { return spliterator.trySplit(); } @Override Spliterator slice(int fromIndex, int toIndex) { return new ConcatSpliterator.OfRef<>(spliterators, fromIndex, toIndex); } } abstract static class OfPrimitive< T, T_CONS, T_SPLITR extends Spliterator.OfPrimitive> extends ConcatSpliterator implements Spliterator.OfPrimitive { OfPrimitive(T_SPLITR[] spliterators, int fromIndex, int toIndex) { super(spliterators, fromIndex, toIndex); } // TODO: Is there a good way to share this logic with the base class? @Override public void forEachRemaining(T_CONS action) { Objects.requireNonNull(action); int i = cursor; if (i < high) { do { spliterators[i].forEachRemaining(action); } while (++i < high); cursor = high; } } @Override public boolean tryAdvance(T_CONS action) { Objects.requireNonNull(action); int i = cursor; if (i < high) { do { if (spliterators[i].tryAdvance(action)) { cursor = i; return true; } } while (++i < high); cursor = high; } return false; } } static final class OfInt extends ConcatSpliterator.OfPrimitive< Integer, IntConsumer, Spliterator.OfInt> implements Spliterator.OfInt { OfInt(Spliterator.OfInt[] spliterators) { super(spliterators, 0, spliterators.length); } OfInt(Spliterator.OfInt[] spliterators, int fromIndex, int toIndex) { super(spliterators, fromIndex, toIndex); } @Override Spliterator.OfInt invokeTrySplit(Spliterator.OfInt spliterator) { return spliterator.trySplit(); } @Override Spliterator.OfInt slice(int fromIndex, int toIndex) { return new ConcatSpliterator.OfInt(spliterators, fromIndex, toIndex); } } static final class OfLong extends ConcatSpliterator.OfPrimitive< Long, LongConsumer, Spliterator.OfLong> implements Spliterator.OfLong { OfLong(Spliterator.OfLong[] spliterators) { super(spliterators, 0, spliterators.length); } OfLong(Spliterator.OfLong[] spliterators, int fromIndex, int toIndex) { super(spliterators, fromIndex, toIndex); } @Override Spliterator.OfLong invokeTrySplit(Spliterator.OfLong spliterator) { return spliterator.trySplit(); } @Override Spliterator.OfLong slice(int fromIndex, int toIndex) { return new ConcatSpliterator.OfLong(spliterators, fromIndex, toIndex); } } static final class OfDouble extends ConcatSpliterator.OfPrimitive< Double, DoubleConsumer, Spliterator.OfDouble> implements Spliterator.OfDouble { OfDouble(Spliterator.OfDouble[] spliterators) { super(spliterators, 0, spliterators.length); } OfDouble(Spliterator.OfDouble[] spliterators, int fromIndex, int toIndex) { super(spliterators, fromIndex, toIndex); } @Override Spliterator.OfDouble invokeTrySplit(Spliterator.OfDouble spliterator) { return spliterator.trySplit(); } @Override Spliterator.OfDouble slice(int fromIndex, int toIndex) { return new ConcatSpliterator.OfDouble(spliterators, fromIndex, toIndex); } } } static final class ComposedClose implements Runnable { final BaseStream[] streams; ComposedClose(BaseStream[] streams) { this.streams = streams; } @Override public void run() { int i = 0; BaseStream stream; while (i < streams.length) { stream = streams[i++]; try { stream.close(); } catch (Throwable e1) { while (i < streams.length) { stream = streams[i++]; try { stream.close(); } catch (Throwable e2) { // TODO: Should we wrap this in a try/catch too? e1.addSuppressed(e2); } } throw e1; } } } } } // @formatter:on





© 2015 - 2024 Weber Informatics LLC | Privacy Policy