Skip to content

Commit 0c9daa7

Browse files
committed
8265029: Preserve SIZED characteristics on slice operations (skip, limit)
Reviewed-by: psandoz
1 parent 95b1fa7 commit 0c9daa7

File tree

12 files changed

+711
-96
lines changed

12 files changed

+711
-96
lines changed

src/java.base/share/classes/java/util/stream/AbstractPipeline.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -466,7 +466,32 @@ final StreamShape getSourceShape() {
466466

467467
@Override
468468
final <P_IN> long exactOutputSizeIfKnown(Spliterator<P_IN> spliterator) {
469-
return StreamOpFlag.SIZED.isKnown(getStreamAndOpFlags()) ? spliterator.getExactSizeIfKnown() : -1;
469+
int flags = getStreamAndOpFlags();
470+
long size = StreamOpFlag.SIZED.isKnown(flags) ? spliterator.getExactSizeIfKnown() : -1;
471+
// Currently, we have no stateless SIZE_ADJUSTING intermediate operations,
472+
// so we can simply ignore SIZE_ADJUSTING in parallel streams, since adjustments
473+
// are already accounted in the input spliterator.
474+
//
475+
// If we ever have a stateless SIZE_ADJUSTING intermediate operation,
476+
// we would need step back until depth == 0, then call exactOutputSize() for
477+
// the subsequent stages.
478+
if (size != -1 && StreamOpFlag.SIZE_ADJUSTING.isKnown(flags) && !isParallel()) {
479+
// Skip the source stage as it's never SIZE_ADJUSTING
480+
for (AbstractPipeline<?, ?, ?> stage = sourceStage.nextStage; stage != null; stage = stage.nextStage) {
481+
size = stage.exactOutputSize(size);
482+
}
483+
}
484+
return size;
485+
}
486+
487+
/**
488+
* Returns the exact output size of the pipeline given the exact size reported by the previous stage.
489+
*
490+
* @param previousSize the exact size reported by the previous stage
491+
* @return the output size of this stage
492+
*/
493+
long exactOutputSize(long previousSize) {
494+
return previousSize;
470495
}
471496

472497
@Override

src/java.base/share/classes/java/util/stream/PipelineHelper.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -82,7 +82,8 @@ abstract class PipelineHelper<P_OUT> {
8282
* The exact output size is known if the {@code Spliterator} has the
8383
* {@code SIZED} characteristic, and the operation flags
8484
* {@link StreamOpFlag#SIZED} is known on the combined stream and operation
85-
* flags.
85+
* flags. The exact output size may differ from spliterator size,
86+
* if pipeline contains a slice operation.
8687
*
8788
* @param spliterator the spliterator describing the relevant portion of the
8889
* source data

src/java.base/share/classes/java/util/stream/ReduceOps.java

+25-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -252,16 +252,18 @@ public ReducingSink makeSink() {
252252
@Override
253253
public <P_IN> Long evaluateSequential(PipelineHelper<T> helper,
254254
Spliterator<P_IN> spliterator) {
255-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
256-
return spliterator.getExactSizeIfKnown();
255+
long size = helper.exactOutputSizeIfKnown(spliterator);
256+
if (size != -1)
257+
return size;
257258
return super.evaluateSequential(helper, spliterator);
258259
}
259260

260261
@Override
261262
public <P_IN> Long evaluateParallel(PipelineHelper<T> helper,
262263
Spliterator<P_IN> spliterator) {
263-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
264-
return spliterator.getExactSizeIfKnown();
264+
long size = helper.exactOutputSizeIfKnown(spliterator);
265+
if (size != -1)
266+
return size;
265267
return super.evaluateParallel(helper, spliterator);
266268
}
267269

@@ -426,16 +428,18 @@ public ReducingSink makeSink() {
426428
@Override
427429
public <P_IN> Long evaluateSequential(PipelineHelper<Integer> helper,
428430
Spliterator<P_IN> spliterator) {
429-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
430-
return spliterator.getExactSizeIfKnown();
431+
long size = helper.exactOutputSizeIfKnown(spliterator);
432+
if (size != -1)
433+
return size;
431434
return super.evaluateSequential(helper, spliterator);
432435
}
433436

434437
@Override
435438
public <P_IN> Long evaluateParallel(PipelineHelper<Integer> helper,
436439
Spliterator<P_IN> spliterator) {
437-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
438-
return spliterator.getExactSizeIfKnown();
440+
long size = helper.exactOutputSizeIfKnown(spliterator);
441+
if (size != -1)
442+
return size;
439443
return super.evaluateParallel(helper, spliterator);
440444
}
441445

@@ -600,16 +604,18 @@ public ReducingSink makeSink() {
600604
@Override
601605
public <P_IN> Long evaluateSequential(PipelineHelper<Long> helper,
602606
Spliterator<P_IN> spliterator) {
603-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
604-
return spliterator.getExactSizeIfKnown();
607+
long size = helper.exactOutputSizeIfKnown(spliterator);
608+
if (size != -1)
609+
return size;
605610
return super.evaluateSequential(helper, spliterator);
606611
}
607612

608613
@Override
609614
public <P_IN> Long evaluateParallel(PipelineHelper<Long> helper,
610615
Spliterator<P_IN> spliterator) {
611-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
612-
return spliterator.getExactSizeIfKnown();
616+
long size = helper.exactOutputSizeIfKnown(spliterator);
617+
if (size != -1)
618+
return size;
613619
return super.evaluateParallel(helper, spliterator);
614620
}
615621

@@ -774,16 +780,18 @@ public ReducingSink makeSink() {
774780
@Override
775781
public <P_IN> Long evaluateSequential(PipelineHelper<Double> helper,
776782
Spliterator<P_IN> spliterator) {
777-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
778-
return spliterator.getExactSizeIfKnown();
783+
long size = helper.exactOutputSizeIfKnown(spliterator);
784+
if (size != -1)
785+
return size;
779786
return super.evaluateSequential(helper, spliterator);
780787
}
781788

782789
@Override
783790
public <P_IN> Long evaluateParallel(PipelineHelper<Double> helper,
784791
Spliterator<P_IN> spliterator) {
785-
if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
786-
return spliterator.getExactSizeIfKnown();
792+
long size = helper.exactOutputSizeIfKnown(spliterator);
793+
if (size != -1)
794+
return size;
787795
return super.evaluateParallel(helper, spliterator);
788796
}
789797

src/java.base/share/classes/java/util/stream/SliceOps.java

+47-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -50,7 +50,7 @@ private SliceOps() { }
5050
* @return the sliced size
5151
*/
5252
private static long calcSize(long size, long skip, long limit) {
53-
return size >= 0 ? Math.max(-1, Math.min(size - skip, limit)) : -1;
53+
return size >= 0 ? Math.max(0, Math.min(size - skip, limit)) : -1;
5454
}
5555

5656
/**
@@ -72,28 +72,23 @@ private static long calcSliceFence(long skip, long limit) {
7272
* spliterator type. Requires that the underlying Spliterator
7373
* be SUBSIZED.
7474
*/
75-
@SuppressWarnings("unchecked")
7675
private static <P_IN> Spliterator<P_IN> sliceSpliterator(StreamShape shape,
7776
Spliterator<P_IN> s,
7877
long skip, long limit) {
7978
assert s.hasCharacteristics(Spliterator.SUBSIZED);
8079
long sliceFence = calcSliceFence(skip, limit);
81-
switch (shape) {
82-
case REFERENCE:
83-
return new StreamSpliterators
84-
.SliceSpliterator.OfRef<>(s, skip, sliceFence);
85-
case INT_VALUE:
86-
return (Spliterator<P_IN>) new StreamSpliterators
87-
.SliceSpliterator.OfInt((Spliterator.OfInt) s, skip, sliceFence);
88-
case LONG_VALUE:
89-
return (Spliterator<P_IN>) new StreamSpliterators
90-
.SliceSpliterator.OfLong((Spliterator.OfLong) s, skip, sliceFence);
91-
case DOUBLE_VALUE:
92-
return (Spliterator<P_IN>) new StreamSpliterators
93-
.SliceSpliterator.OfDouble((Spliterator.OfDouble) s, skip, sliceFence);
94-
default:
95-
throw new IllegalStateException("Unknown shape " + shape);
96-
}
80+
@SuppressWarnings("unchecked")
81+
Spliterator<P_IN> sliceSpliterator = (Spliterator<P_IN>) switch (shape) {
82+
case REFERENCE
83+
-> new StreamSpliterators.SliceSpliterator.OfRef<>(s, skip, sliceFence);
84+
case INT_VALUE
85+
-> new StreamSpliterators.SliceSpliterator.OfInt((Spliterator.OfInt) s, skip, sliceFence);
86+
case LONG_VALUE
87+
-> new StreamSpliterators.SliceSpliterator.OfLong((Spliterator.OfLong) s, skip, sliceFence);
88+
case DOUBLE_VALUE
89+
-> new StreamSpliterators.SliceSpliterator.OfDouble((Spliterator.OfDouble) s, skip, sliceFence);
90+
};
91+
return sliceSpliterator;
9792
}
9893

9994
/**
@@ -110,9 +105,15 @@ public static <T> Stream<T> makeRef(AbstractPipeline<?, T, ?> upstream,
110105
long skip, long limit) {
111106
if (skip < 0)
112107
throw new IllegalArgumentException("Skip must be non-negative: " + skip);
108+
long normalizedLimit = limit >= 0 ? limit : Long.MAX_VALUE;
113109

114110
return new ReferencePipeline.StatefulOp<T, T>(upstream, StreamShape.REFERENCE,
115111
flags(limit)) {
112+
@Override
113+
long exactOutputSize(long previousSize) {
114+
return calcSize(previousSize, skip, normalizedLimit);
115+
}
116+
116117
Spliterator<T> unorderedSkipLimitSpliterator(Spliterator<T> s,
117118
long skip, long limit, long sizeIfKnown) {
118119
if (skip <= sizeIfKnown) {
@@ -182,9 +183,9 @@ <P_IN> Node<T> opEvaluateParallel(PipelineHelper<T> helper,
182183

183184
@Override
184185
Sink<T> opWrapSink(int flags, Sink<T> sink) {
185-
return new Sink.ChainedReference<T, T>(sink) {
186+
return new Sink.ChainedReference<>(sink) {
186187
long n = skip;
187-
long m = limit >= 0 ? limit : Long.MAX_VALUE;
188+
long m = normalizedLimit;
188189

189190
@Override
190191
public void begin(long size) {
@@ -226,9 +227,15 @@ public static IntStream makeInt(AbstractPipeline<?, Integer, ?> upstream,
226227
long skip, long limit) {
227228
if (skip < 0)
228229
throw new IllegalArgumentException("Skip must be non-negative: " + skip);
230+
long normalizedLimit = limit >= 0 ? limit : Long.MAX_VALUE;
229231

230232
return new IntPipeline.StatefulOp<Integer>(upstream, StreamShape.INT_VALUE,
231233
flags(limit)) {
234+
@Override
235+
long exactOutputSize(long previousSize) {
236+
return calcSize(previousSize, skip, normalizedLimit);
237+
}
238+
232239
Spliterator.OfInt unorderedSkipLimitSpliterator(
233240
Spliterator.OfInt s, long skip, long limit, long sizeIfKnown) {
234241
if (skip <= sizeIfKnown) {
@@ -291,9 +298,9 @@ <P_IN> Node<Integer> opEvaluateParallel(PipelineHelper<Integer> helper,
291298

292299
@Override
293300
Sink<Integer> opWrapSink(int flags, Sink<Integer> sink) {
294-
return new Sink.ChainedInt<Integer>(sink) {
301+
return new Sink.ChainedInt<>(sink) {
295302
long n = skip;
296-
long m = limit >= 0 ? limit : Long.MAX_VALUE;
303+
long m = normalizedLimit;
297304

298305
@Override
299306
public void begin(long size) {
@@ -335,9 +342,15 @@ public static LongStream makeLong(AbstractPipeline<?, Long, ?> upstream,
335342
long skip, long limit) {
336343
if (skip < 0)
337344
throw new IllegalArgumentException("Skip must be non-negative: " + skip);
345+
long normalizedLimit = limit >= 0 ? limit : Long.MAX_VALUE;
338346

339347
return new LongPipeline.StatefulOp<Long>(upstream, StreamShape.LONG_VALUE,
340348
flags(limit)) {
349+
@Override
350+
long exactOutputSize(long previousSize) {
351+
return calcSize(previousSize, skip, normalizedLimit);
352+
}
353+
341354
Spliterator.OfLong unorderedSkipLimitSpliterator(
342355
Spliterator.OfLong s, long skip, long limit, long sizeIfKnown) {
343356
if (skip <= sizeIfKnown) {
@@ -400,9 +413,9 @@ <P_IN> Node<Long> opEvaluateParallel(PipelineHelper<Long> helper,
400413

401414
@Override
402415
Sink<Long> opWrapSink(int flags, Sink<Long> sink) {
403-
return new Sink.ChainedLong<Long>(sink) {
416+
return new Sink.ChainedLong<>(sink) {
404417
long n = skip;
405-
long m = limit >= 0 ? limit : Long.MAX_VALUE;
418+
long m = normalizedLimit;
406419

407420
@Override
408421
public void begin(long size) {
@@ -444,9 +457,15 @@ public static DoubleStream makeDouble(AbstractPipeline<?, Double, ?> upstream,
444457
long skip, long limit) {
445458
if (skip < 0)
446459
throw new IllegalArgumentException("Skip must be non-negative: " + skip);
460+
long normalizedLimit = limit >= 0 ? limit : Long.MAX_VALUE;
447461

448462
return new DoublePipeline.StatefulOp<Double>(upstream, StreamShape.DOUBLE_VALUE,
449463
flags(limit)) {
464+
@Override
465+
long exactOutputSize(long previousSize) {
466+
return calcSize(previousSize, skip, normalizedLimit);
467+
}
468+
450469
Spliterator.OfDouble unorderedSkipLimitSpliterator(
451470
Spliterator.OfDouble s, long skip, long limit, long sizeIfKnown) {
452471
if (skip <= sizeIfKnown) {
@@ -509,9 +528,9 @@ <P_IN> Node<Double> opEvaluateParallel(PipelineHelper<Double> helper,
509528

510529
@Override
511530
Sink<Double> opWrapSink(int flags, Sink<Double> sink) {
512-
return new Sink.ChainedDouble<Double>(sink) {
531+
return new Sink.ChainedDouble<>(sink) {
513532
long n = skip;
514-
long m = limit >= 0 ? limit : Long.MAX_VALUE;
533+
long m = normalizedLimit;
515534

516535
@Override
517536
public void begin(long size) {
@@ -541,7 +560,7 @@ public boolean cancellationRequested() {
541560
}
542561

543562
private static int flags(long limit) {
544-
return StreamOpFlag.NOT_SIZED | ((limit != -1) ? StreamOpFlag.IS_SHORT_CIRCUIT : 0);
563+
return StreamOpFlag.IS_SIZE_ADJUSTING | ((limit != -1) ? StreamOpFlag.IS_SHORT_CIRCUIT : 0);
545564
}
546565

547566
/**

src/java.base/share/classes/java/util/stream/StreamOpFlag.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,24 @@ enum StreamOpFlag {
325325
*/
326326
// 12, 0x01000000
327327
SHORT_CIRCUIT(12,
328-
set(Type.OP).set(Type.TERMINAL_OP));
328+
set(Type.OP).set(Type.TERMINAL_OP)),
329+
330+
/**
331+
* Characteristic value signifying that an operation may adjust the
332+
* total size of the stream.
333+
* <p>
334+
* The flag, if present, is only valid when SIZED is present;
335+
* and is only valid for sequential streams.
336+
* <p>
337+
* An intermediate operation can preserve or inject this value.
338+
*/
339+
// 13, 0x04000000
340+
SIZE_ADJUSTING(13,
341+
set(Type.OP));
329342

330343
// The following 2 flags are currently undefined and a free for any further
331344
// stream flags if/when required
332345
//
333-
// 13, 0x04000000
334346
// 14, 0x10000000
335347
// 15, 0x40000000
336348

@@ -629,6 +641,11 @@ private static int createFlagMask() {
629641
*/
630642
static final int IS_SHORT_CIRCUIT = SHORT_CIRCUIT.set;
631643

644+
/**
645+
* The bit value to inject {@link #SIZE_ADJUSTING}.
646+
*/
647+
static final int IS_SIZE_ADJUSTING = SIZE_ADJUSTING.set;
648+
632649
private static int getMask(int flags) {
633650
return (flags == 0)
634651
? FLAG_MASK

0 commit comments

Comments
 (0)