Skip to content

Commit

Permalink
Fix scala-native#3409: Remove defects from Collectors#joining method (s…
Browse files Browse the repository at this point in the history
  • Loading branch information
LeeTibbert committed Aug 9, 2023
1 parent 652323b commit 5b5db37
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 41 deletions.
56 changes: 15 additions & 41 deletions javalib/src/main/scala/java/util/stream/Collectors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -579,59 +579,33 @@ object Collectors {
prefix: CharSequence,
suffix: CharSequence
): Collector[CharSequence, AnyRef, String] = {
val delimiterLength = delimiter.length()

val supplier = new Supplier[StringBuilder] {
def get(): StringBuilder = {
val sb = new StringBuilder()
if (prefix != "")
sb.append(prefix)
sb

val supplier = new Supplier[StringJoiner] {
def get(): StringJoiner = {
new StringJoiner(delimiter, prefix, suffix)
}
}

val accumulator = new BiConsumer[StringBuilder, CharSequence] {
def accept(accum: StringBuilder, element: CharSequence): Unit = {
val acc = accum.append(element)
if (delimiter != "")
accum.append(delimiter)
val accumulator = new BiConsumer[StringJoiner, CharSequence] {
def accept(accum: StringJoiner, element: CharSequence): Unit = {
accum.add(element)
}
}

val combiner = new BinaryOperator[StringBuilder] {
def apply(
sb1: StringBuilder,
sb2: StringBuilder
): StringBuilder = {
sb1.append(sb2)
val combiner = new BinaryOperator[StringJoiner] {
def apply(sj1: StringJoiner, sj2: StringJoiner): StringJoiner = {
sj1.merge(sj2)
}
}

val finisher =
new Function[StringBuilder, String] {
def apply(accum: StringBuilder): String = {

if ((accum.length() > prefix.length()) && (delimiterLength > 0)) {
/* This branch means accum has contents beyond a possible prefix.
* If a delimiter arg was is specified, accumlator() will have
* appended that delimiter. A delimiter is unwanted after what is
* now known to be the last item, so trim it off before possibly
* adding a suffix.
*/
val lastIndex = accum.length() - delimiterLength
accum.setLength(lastIndex) // trim off last delimiter sequence.
}
// Else empty stream; no token accepted, hence no delimiter to trim.

if (suffix != "")
accum.append(suffix)

accum.toString()
}
val finisher = new Function[StringJoiner, String] {
def apply(accum: StringJoiner): String = {
accum.toString()
}
}

Collector
.of[CharSequence, StringBuilder, String](
.of[CharSequence, StringJoiner, String](
supplier,
accumulator,
combiner,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.scalanative.testsuite.javalib.util.stream

import java.{lang => jl}
import java.{util => ju}
import java.util._

Expand Down Expand Up @@ -719,6 +720,52 @@ class CollectorsTest {
assertEquals("unexpected joined", expected, joined)
}

// Issue #3409
@Test def collectorsJoining_Merge(): Unit = {
/* The idea is to test that a delimiter is added between the
* two arguments when Collectors.joining() merge() method is called.
*
* One would not normally call merge() directory, but writers of
* parallel library methods might. So the method should match its
* JVM description.
*
* The complexity comes from not wanting to know the actual implementation
* type of the accumulator A in <T, ?, R>. combiner() takes two arguments
* of that exact type. To get the unknown type right, each of the
* arguments passed to combiner() should come from the same supplier of
* the same Collector.joining().
*
* So far, this type fun & games is true with both JVM and Scala Native.
*
* This gets the interior implementation type correct, but also means
* that both arguments use the same prefix, suffix, & delimiter.
* Experience with parallel Collectors may show a way around this
* restriction/feature.
*/

val left = "Left"
val right = "Right"
val delim = "|"

val expected = s"${left}${delim}${right}"

val collector = Collectors.joining(delim)

val supplier = collector.supplier
val accumulator = collector.accumulator
val combiner = collector.combiner

val accLeft = supplier.get()
accumulator.accept(accLeft, left)

val accRight = supplier.get()
accumulator.accept(accRight, right)

val combined = combiner.apply(accLeft, accRight).toString()

assertEquals("unexpected combined", expected, combined)
}

@Test def collectorsMapping(): Unit = {
/* This implements one of the examples in the Java 19 description of the
* java.util.Collectors class:
Expand Down

0 comments on commit 5b5db37

Please sign in to comment.