Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #3309: javalib stream limit methods now match Java 8 #3390

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 28 additions & 5 deletions javalib/src/main/scala/java/util/stream/DoubleStreamImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -373,16 +373,32 @@ private[stream] class DoubleStreamImpl(
}

def limit(maxSize: Long): DoubleStream = {

/* Important:
* See Issue #3309 & ObjectStreamImpl#limit for discussion of size
* & characteristics in JVM 17 (and possibly as early as JVM 12)
* for parallel ORDERED streams.
* The behavior implemented here is Java 8 and at least Java 11.
*/

if (maxSize < 0)
throw new IllegalArgumentException(maxSize.toString())

commenceOperation() // JVM tests argument before operatedUpon or closed.

var nSeen = 0L

val startingBits = _spliter.characteristics()

val alwaysClearedBits =
Spliterator.SIZED | Spliterator.SUBSIZED |
Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.CONCURRENT

val newStreamCharacteristics = startingBits & ~alwaysClearedBits

val spl = new Spliterators.AbstractDoubleSpliterator(
maxSize,
_spliter.characteristics()
Long.MaxValue,
newStreamCharacteristics
) {
def tryAdvance(action: DoubleConsumer): Boolean =
if (nSeen >= maxSize) false
Expand Down Expand Up @@ -583,11 +599,18 @@ private[stream] class DoubleStreamImpl(

Arrays.sort(buffer)

val spl = Spliterators.spliterator(
buffer,
val startingBits = _spliter.characteristics()
val alwaysSetBits =
Spliterator.SORTED | Spliterator.ORDERED |
Spliterator.SIZED | Spliterator.SUBSIZED
)

// Time & experience may show that additional bits need to be cleared here.
val alwaysClearedBits = Spliterator.IMMUTABLE

val newCharacteristics =
(startingBits | alwaysSetBits) & ~alwaysClearedBits

val spl = Spliterators.spliterator(buffer, newCharacteristics)

new DoubleStreamImpl(spl, _parallel, pipeline)
}
Expand Down
41 changes: 36 additions & 5 deletions javalib/src/main/scala/java/util/stream/ObjectStreamImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -406,16 +406,34 @@ private[stream] class ObjectStreamImpl[T](
}

def limit(maxSize: Long): Stream[T] = {

/* Important:
* See Issue #3309 for discussion of size & characteristics
* in JVM 17 (and possibly as early as JVM 12) for parallel ORDERED
* streams. The behavior implemented here is Java 8 and at least Java 11.
*
* If you are reading this block with more than a passing interest,
* prepare yourself for not having a good day, the muck is deep.
*/

if (maxSize < 0)
throw new IllegalArgumentException(maxSize.toString())

commenceOperation() // JVM tests argument before operatedUpon or closed.

var nSeen = 0L

val startingBits = _spliter.characteristics()

val alwaysClearedBits =
Spliterator.SIZED | Spliterator.SUBSIZED |
Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.CONCURRENT

val newStreamCharacteristics = startingBits & ~alwaysClearedBits

val spl = new Spliterators.AbstractSpliterator[T](
maxSize,
_spliter.characteristics()
Long.MaxValue,
newStreamCharacteristics
) {
def tryAdvance(action: Consumer[_ >: T]): Boolean =
if (nSeen >= maxSize) false
Expand Down Expand Up @@ -659,11 +677,24 @@ private[stream] class ObjectStreamImpl[T](
Arrays
.sort[Object](buffer, comparator.asInstanceOf[Comparator[_ >: Object]])

val spl = Spliterators.spliterator[T](
buffer,
/* // FIXME
val newCharacteristics = _spliter.characteristics() |
Spliterator.SORTED | Spliterator.ORDERED |
Spliterator.SIZED | Spliterator.SUBSIZED
*/ // FIXME

val startingBits = _spliter.characteristics()
val alwaysSetBits =
Spliterator.SORTED | Spliterator.ORDERED |
Spliterator.SIZED | Spliterator.SUBSIZED
)

// Time & experience may show that additional bits need to be cleared here.
val alwaysClearedBits = Spliterator.IMMUTABLE

val newCharacteristics =
(startingBits | alwaysSetBits) & ~alwaysClearedBits

val spl = Spliterators.spliterator[T](buffer, newCharacteristics)

new ObjectStreamImpl[T](spl, _parallel, pipeline)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import java.util.stream._

import org.junit.Test
import org.junit.Assert._
import org.junit.BeforeClass
import org.junit.Ignore

import org.scalanative.testsuite.utils.AssertThrows.assertThrows
Expand All @@ -38,6 +39,11 @@ import org.scalanative.testsuite.utils.AssertThrows.assertThrows
* - doubleStreamMapToLong, requires LongStream
*/

object DoubleStreamTest {
@BeforeClass def checkLimitMethodCharacteristics(): Unit =
StreamTestHelpers.requireJDK8CompatibleCharacteristics()
}

class DoubleStreamTest {

final val epsilon = 0.00001 // tolerance for Floating point comparisons.
Expand Down Expand Up @@ -863,6 +869,8 @@ class DoubleStreamTest {
}

@Test def doubleStreamLimit(): Unit = {
StreamTestHelpers.requireJDK8CompatibleCharacteristics()

val expectedCount = 10
var data = -1

Expand All @@ -876,6 +884,167 @@ class DoubleStreamTest {
assertEquals(s"unexpected element count", expectedCount, s1.count())
}

/* Note Well: See Issue #3309 comments in StreamTest.scala and
* in original issue.
*/

// Issue #3309 - 1 of 5
@Test def doubleSstreamLimit_Size(): Unit = {
StreamTestHelpers.requireJDK8CompatibleCharacteristics()

val srcSize = 10

val spliter = DoubleStream
.iterate(2.71828, e => e + 1.0)
.limit(srcSize)
.spliterator()

val expectedExactSize = -1
assertEquals(
"expected exact size",
expectedExactSize,
spliter.getExactSizeIfKnown()
)

val expectedEstimatedSize = Long.MaxValue
assertEquals(
"expected estimated size",
expectedEstimatedSize,
spliter.estimateSize()
)
}

// Issue #3309 - 2 of 5
@Test def doubleStreamLimit_Characteristics(): Unit = {
StreamTestHelpers.requireJDK8CompatibleCharacteristics()

val zeroCharacteristicsSpliter =
new Spliterators.AbstractDoubleSpliterator(Long.MaxValue, 0x0) {
def tryAdvance(action: DoubleConsumer): Boolean = true
}

val sZero = StreamSupport.doubleStream(zeroCharacteristicsSpliter, false)
val sZeroLimited = sZero.limit(9)

val sZeroLimitedSpliter = sZeroLimited.spliterator()

val expectedSZeroLimitedCharacteristics = 0x0

assertEquals(
"Unexpected characteristics for zero characteristics stream",
expectedSZeroLimitedCharacteristics,
sZeroLimitedSpliter.characteristics()
)

/* JVM fails the StreamSupport.stream() call with IllegalStateException
* when SORTED is specified. Top of stack traceback is:
* at java.util.Spliterator.getComparator(Spliterator.java:471)
*
* Test the bits we can here and let Test
* streamLimit_SortedCharacteristics() handle SORTED.
*/
val allCharacteristicsSpliter =
new Spliterators.AbstractDoubleSpliterator(Long.MaxValue, 0x5551) {
def tryAdvance(action: DoubleConsumer): Boolean = true
}

val sAll = StreamSupport.doubleStream(allCharacteristicsSpliter, false)

val sAllLimited = sAll.limit(9)
val sAllLimitedSpliter = sAllLimited.spliterator()

// JVM 8 expects 0x11 (decimal 17), JVM >= 17 expects 0x4051 (Dec 16465)
val expectedSAllLimitedCharacteristics =
Spliterator.ORDERED | Spliterator.DISTINCT // 0x11
// Drop SIZED, SUBSIZED, CONCURRENT, IMMUTABLE, & NONNULL.
// SORTED was not there to drop.

assertEquals(
"Unexpected characteristics for all characteristics stream",
expectedSAllLimitedCharacteristics,
sAllLimitedSpliter.characteristics()
)
}

// Issue #3309 - 3 of 5
@Test def streamLimit_SortedCharacteristics(): Unit = {
StreamTestHelpers.requireJDK8CompatibleCharacteristics()

/* Address issues with SORTED described in Test
* streamLimit_sequentialAlwaysCharacteristics
*/
val allCharacteristicsSpliter =
new Spliterators.AbstractDoubleSpliterator(0, 0x5551) {
def tryAdvance(action: DoubleConsumer): Boolean = false
}

val sAll = StreamSupport.doubleStream(allCharacteristicsSpliter, false)

val sAllLimited = sAll.sorted().limit(9)
val sAllLimitedSpliter = sAllLimited.spliterator()

// JVM 8 expects 0x15 (decimal 21), JVM >= 17 expects 0x4055 (Dec 16469)
val expectedSAllLimitedCharacteristics =
Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.SORTED // 0x15 // Drop SIZED, SUBSIZED, CONCURRENT, IMMUTABLE, & NONNULL.

assertEquals(
"Unexpected characteristics for all characteristics sorted stream",
expectedSAllLimitedCharacteristics,
sAllLimitedSpliter.characteristics()
)
}

// Issue #3309 - 4 of 5
@Test def streamLimit_UnsizedCharacteristics(): Unit = {
StreamTestHelpers.requireJDK8CompatibleCharacteristics()

val srcSize = 20

val unsizedSpliter = DoubleStream
.iterate(1.2, n => n + 1.1)
.limit(srcSize)
.spliterator()

val expectedUnsizedCharacteristics = Spliterator.ORDERED // 0x10

assertEquals(
"Unexpected unsized characteristics",
expectedUnsizedCharacteristics,
unsizedSpliter.characteristics()
)
}

// Issue #3309 - 5 of 5
@Test def streamLimit_SizedCharacteristics(): Unit = {
StreamTestHelpers.requireJDK8CompatibleCharacteristics()

val proofSpliter = DoubleStream.of(1.12, 2.23, 3.34, -1.12).spliterator()

val expectedProofCharacteristics =
Spliterator.SIZED | Spliterator.SUBSIZED |
Spliterator.ORDERED | Spliterator.IMMUTABLE // 0x4450

assertEquals(
"Unexpected origin stream characteristics",
expectedProofCharacteristics,
proofSpliter.characteristics()
)

val sizedSpliter = DoubleStream
.of(1.12, 2.23, 3.34, -1.12)
.limit(3)
.spliterator()

// JVM 8 expects 0x10 (decimal 16), JVM >= 17 expects 0x4050 (Dec 16464)
val expectedSizedLimitCharacteristics = Spliterator.ORDERED

assertEquals(
"Unexpected characteristics for SIZED stream",
expectedSizedLimitCharacteristics,
sizedSpliter.characteristics()
)
}

@Test def doubleStreamMap(): Unit = {
val nElements = 4
val prefix = "mapped_"
Expand Down