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 #3409: Remove defects from Collectors#joining method #3421

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
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