Skip to content

Commit a4d9801

Browse files
committed
Fix #4872: Implement java.util.StringJoiner.
1 parent 3fe1c30 commit a4d9801

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package java.util
14+
15+
@inline
16+
final class StringJoiner private (delimiter: String, prefix: String, suffix: String) extends AnyRef {
17+
/** The custom value to return if empty, set by `setEmptyValue` (nullable).
18+
*
19+
* If `null`, defaults to `prefix + suffix`.
20+
*/
21+
private var emptyValue: String = null
22+
23+
/** The current value, excluding prefix and suffix. */
24+
private var value: String = ""
25+
26+
/** Whether the string joiner is currently empty. */
27+
private var isEmpty: Boolean = true
28+
29+
def this(delimiter: CharSequence) =
30+
this(delimiter.toString(), "", "")
31+
32+
def this(delimiter: CharSequence, prefix: CharSequence, suffix: CharSequence) =
33+
this(delimiter.toString(), prefix.toString(), suffix.toString())
34+
35+
def setEmptyValue(emptyValue: CharSequence): StringJoiner = {
36+
this.emptyValue = emptyValue.toString()
37+
this
38+
}
39+
40+
override def toString(): String =
41+
if (isEmpty && emptyValue != null) emptyValue
42+
else prefix + value + suffix
43+
44+
def add(newElement: CharSequence): StringJoiner = {
45+
if (isEmpty)
46+
isEmpty = false
47+
else
48+
value += delimiter
49+
value += newElement // if newElement is null, adds "null"
50+
this
51+
}
52+
53+
def merge(other: StringJoiner): StringJoiner = {
54+
if (!other.isEmpty) // if `other` is empty, `merge` has no effect
55+
add(other.value) // without prefix nor suffix, but with delimiters
56+
this
57+
}
58+
59+
def length(): Int =
60+
if (isEmpty && emptyValue != null) emptyValue.length()
61+
else prefix.length() + value.length() + suffix.length()
62+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.testsuite.javalib.util
14+
15+
import java.nio.CharBuffer
16+
import java.util.StringJoiner
17+
18+
import org.junit.Assert._
19+
import org.junit.Assume._
20+
import org.junit.Test
21+
22+
import org.scalajs.testsuite.utils.AssertThrows.assertThrows
23+
import org.scalajs.testsuite.utils.Platform
24+
25+
class StringJoinerTest {
26+
import StringJoinerTest._
27+
28+
@Test def testEmpty(): Unit = {
29+
assertJoinerResult("")(new StringJoiner(","))
30+
assertJoinerResult("[]")(new StringJoiner(";", "[", "]"))
31+
assertJoinerResult("--")(new StringJoiner(";").setEmptyValue("--"))
32+
assertJoinerResult("--")(new StringJoiner(";", "[", "]").setEmptyValue("--"))
33+
}
34+
35+
@Test def testNonEmpty(): Unit = {
36+
assertJoinerResult("one") {
37+
new StringJoiner(",").add("one")
38+
}
39+
assertJoinerResult("one,two,three") {
40+
new StringJoiner(",").add("one").add("two").add("three")
41+
}
42+
assertJoinerResult("[one, two, three]") {
43+
new StringJoiner(", ", "[", "]").add("one").add("two").add("three")
44+
}
45+
assertJoinerResult("[one, null, three]") {
46+
new StringJoiner(", ", "[", "]").add("one").add(null).add("three")
47+
}
48+
assertJoinerResult("[one, two, three]") {
49+
new StringJoiner(", ", "[", "]").add("one").add(CharBuffer.wrap("two")).add("three")
50+
}
51+
assertJoinerResult("") {
52+
new StringJoiner(",").add("")
53+
}
54+
assertJoinerResult("one,") {
55+
new StringJoiner(",").add("one").add("")
56+
}
57+
assertJoinerResult(",two") {
58+
new StringJoiner(",").add("").add("two")
59+
}
60+
assertJoinerResult("one,,three") {
61+
new StringJoiner(",").add("one").add("").add("three")
62+
}
63+
64+
assertJoinerResult("one") {
65+
new StringJoiner(",").setEmptyValue("--").add("one")
66+
}
67+
assertJoinerResult("one,two,three") {
68+
new StringJoiner(",").setEmptyValue("--").add("one").add("two").add("three")
69+
}
70+
assertJoinerResult("[one, two, three]") {
71+
new StringJoiner(", ", "[", "]").add("one").add("two").setEmptyValue("--").add("three")
72+
}
73+
assertJoinerResult("[one, two, three]") {
74+
new StringJoiner(", ", "[", "]").add("one").add("two").add("three").setEmptyValue("--")
75+
}
76+
assertJoinerResult("") {
77+
new StringJoiner(",").setEmptyValue("--").add("")
78+
}
79+
assertJoinerResult("one,") {
80+
new StringJoiner(",").setEmptyValue("--").add("one").add("")
81+
}
82+
}
83+
84+
@Test def testMerge(): Unit = {
85+
val empty = new StringJoiner(";", "[", "]").setEmptyValue("--")
86+
val single = new StringJoiner(";", "[", "]").setEmptyValue("--").add("single")
87+
val multiple = new StringJoiner(";", "[", "]").setEmptyValue("--").add("a").add("b").add("c")
88+
val singleBlank = new StringJoiner(";", "[", "]").setEmptyValue("--").add("")
89+
90+
assertJoinerResult("+++") {
91+
new StringJoiner(", ", "{", "}").merge(empty).setEmptyValue("+++")
92+
}
93+
assertJoinerResult("+++") {
94+
new StringJoiner(", ", "{", "}").setEmptyValue("+++").merge(empty)
95+
}
96+
assertJoinerResult("{}") {
97+
new StringJoiner(", ", "{", "}").merge(singleBlank).setEmptyValue("+++")
98+
}
99+
assertJoinerResult("{}") {
100+
new StringJoiner(", ", "{", "}").setEmptyValue("+++").merge(singleBlank)
101+
}
102+
assertJoinerResult("{one, two}") {
103+
new StringJoiner(", ", "{", "}").add("one").merge(empty).add("two")
104+
}
105+
assertJoinerResult("{one, single, two}") {
106+
new StringJoiner(", ", "{", "}").add("one").merge(single).add("two")
107+
}
108+
assertJoinerResult("{one, a;b;c, two}") {
109+
new StringJoiner(", ", "{", "}").add("one").merge(multiple).add("two")
110+
}
111+
assertJoinerResult("{one, , two}") {
112+
new StringJoiner(", ", "{", "}").add("one").merge(singleBlank).add("two")
113+
}
114+
assertJoinerResult("{single}") {
115+
new StringJoiner(", ", "{", "}").merge(single)
116+
}
117+
assertJoinerResult("{a;b;c}") {
118+
new StringJoiner(", ", "{", "}").merge(multiple)
119+
}
120+
assertJoinerResult("{}") {
121+
new StringJoiner(", ", "{", "}").merge(singleBlank)
122+
}
123+
}
124+
125+
@Test def testState(): Unit = {
126+
val mutableCharSeq = CharBuffer.allocate(2).put(0, '?').put(1, '!')
127+
128+
val joiner = new StringJoiner(mutableCharSeq, mutableCharSeq, mutableCharSeq)
129+
assertJoinerResult("?!?!")(joiner)
130+
joiner.setEmptyValue(mutableCharSeq)
131+
assertJoinerResult("?!")(joiner)
132+
133+
mutableCharSeq.put(0, '-')
134+
assertJoinerResult("?!")(joiner) // the previously set emptyValue is not affected
135+
joiner.setEmptyValue(mutableCharSeq)
136+
assertJoinerResult("-!")(joiner)
137+
138+
joiner.add("one")
139+
assertJoinerResult("?!one?!")(joiner) // the previously set prefix and suffix are not affected
140+
141+
joiner.add("two")
142+
assertJoinerResult("?!one?!two?!")(joiner) // the previously set delimiter is not affected
143+
}
144+
145+
@Test def testNPE(): Unit = {
146+
assumeTrue("requires compliant null pointers", Platform.hasCompliantNullPointers)
147+
148+
@noinline
149+
def assertNPE[U](code: => U): Unit =
150+
assertThrows(classOf[NullPointerException], code)
151+
152+
assertNPE(new StringJoiner(null))
153+
assertNPE(new StringJoiner(null, "[", "]"))
154+
assertNPE(new StringJoiner(",", null, "]"))
155+
assertNPE(new StringJoiner(",", "[", null))
156+
157+
assertNPE(new StringJoiner(",").setEmptyValue(null))
158+
159+
assertNPE(new StringJoiner(",").merge(null))
160+
}
161+
}
162+
163+
object StringJoinerTest {
164+
def assertJoinerResult(expected: String)(joiner: StringJoiner): Unit = {
165+
assertEquals(expected, joiner.toString())
166+
assertEquals(expected.length(), joiner.length())
167+
}
168+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy