Skip to content

Commit

Permalink
improvement: implement java.util.BitSet#stream (#3819)
Browse files Browse the repository at this point in the history
* improvement: implement java.util.BitSet#stream
* Make BitSet & BitSetTest robust to behavior which chaged between JVM versions
  • Loading branch information
LeeTibbert committed Mar 8, 2024
1 parent 3c5c8d4 commit 18cfdcb
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 1 deletion.
43 changes: 43 additions & 0 deletions javalib/src/main/scala/java/util/BitSet.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
// Ported from Scala.js commit: c0be6b6 dated: 2021-12-22
// stream() method added for Scala Native

package java.util

import java.io.Serializable
import java.lang.Long.bitCount
import java.nio.{ByteBuffer, LongBuffer}

import java.util
import java.util.Spliterators.AbstractIntSpliterator
import java.util.function.IntConsumer
import java.util.stream.{IntStream, StreamSupport}

private object BitSet {
private final val AddressBitsPerWord = 6 // Int Based 2^6 = 64
Expand Down Expand Up @@ -644,6 +649,44 @@ class BitSet private (private var bits: Array[Long])
result
}

def stream(): IntStream = {
/* A high estimated upper bound, if all bits are set, not an actual count.
* Fit enough for purpose. A real count could get expensive with large
* sets and the spliterator size is never really used.
*/
val size = bits.length * 64

// As reported by the JVM
val characteristics =
Spliterator.DISTINCT |
Spliterator.SORTED |
Spliterator.ORDERED |
Spliterator.SIZED

/* JVM versions around 8 seem to set Spliterator.SUBSIZED.
* Later versions seem to leave it clear. Follow the latter practice.
* At some point, may need to add some JVM version specific code here.
*/

val spliter = new AbstractIntSpliterator(size, characteristics) {
var fromIndex = 0

def tryAdvance(action: IntConsumer): Boolean = {
Objects.requireNonNull(action)
val nextSet = nextSetBit(fromIndex)

if (nextSet < 0) false
else {
action.accept(nextSet)
fromIndex = nextSet + 1
true
}
}
}

StreamSupport.intStream(spliter, parallel = false)
}

final private def ensureLength(len: Int): Unit = {
if (len > bits.length)
bits = Arrays.copyOf(bits, Math.max(len, bits.length * 2))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// Ported from Scala.js commit: c0be6b6 dated: 2021-12-22
// test_stream() added for Scala Native

package org.scalanative.testsuite.javalib.util

import java.nio.{ByteBuffer, LongBuffer}
import java.util.BitSet

import java.{util => ju}
import java.util.{BitSet, Spliterator, TreeSet}
import java.util.stream.IntStream

import org.junit.Assert.{assertThrows => junitAssertThrows, _}
import org.junit.Assume._
import org.junit.Test
Expand Down Expand Up @@ -1538,4 +1543,51 @@ class BitSetTest {
}
eightbs
}

@Test def test_stream(): Unit = {
// As reported by the JVM
val expectedCharacteristics =
Spliterator.DISTINCT |
Spliterator.SORTED |
Spliterator.ORDERED |
Spliterator.SIZED

val expectedSet = new TreeSet[Int]()
val resultSet = new TreeSet[Int]()

// Use enough bits for something to go wrong. Span multiple longwords.
val bs = new BitSet(256)

IntStream
.of(3, 7, 9, 10, 72, 110, 181, 219, 220) // Arbitrary numbers used above.
.forEach(e => {
expectedSet.add(e)
bs.set(e)
})

/* It appears that JVMs circa Java 8 set SUBSIZED and that sometime
* after that they stopped. Mask off that bit to avoid having JVM
* version specific code here. The presence or absence of the bits
* that are checked is far more important.
*/
val resultCharacteristics =
bs.stream().spliterator().characteristics() & ~Spliterator.SUBSIZED

assertEquals(
"stream spliterator characteristics",
expectedCharacteristics,
resultCharacteristics
)

/* The values returned by stream() are _probably_ monotonically increasing
* but that is not a specified condition.
* Collect the results into a set, to ease comparison.
*/

bs.stream()
.forEach(e => resultSet.add(e))

assertEquals("stream contents", expectedSet, resultSet)
}

}

0 comments on commit 18cfdcb

Please sign in to comment.