Skip to content

Commit 9b9b5a7

Browse files
author
Jim Laskey
committedApr 3, 2023
8302323: Add repeat methods to StringBuilder/StringBuffer
Reviewed-by: tvaleev, redestad
1 parent dd7ca75 commit 9b9b5a7

File tree

6 files changed

+570
-4
lines changed

6 files changed

+570
-4
lines changed
 

‎src/java.base/share/classes/java/lang/AbstractStringBuilder.java

+112
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import jdk.internal.math.FloatToDecimal;
3030

3131
import java.io.IOException;
32+
import java.nio.CharBuffer;
3233
import java.util.Arrays;
3334
import java.util.Spliterator;
3435
import java.util.stream.IntStream;
@@ -1821,4 +1822,115 @@ private final void appendChars(CharSequence s, int off, int end) {
18211822
}
18221823
count += end - off;
18231824
}
1825+
1826+
private AbstractStringBuilder repeat(char c, int count) {
1827+
int limit = this.count + count;
1828+
ensureCapacityInternal(limit);
1829+
boolean isLatin1 = isLatin1();
1830+
if (isLatin1 && StringLatin1.canEncode(c)) {
1831+
Arrays.fill(value, this.count, limit, (byte)c);
1832+
} else {
1833+
if (isLatin1) {
1834+
inflate();
1835+
}
1836+
for (int index = this.count; index < limit; index++) {
1837+
StringUTF16.putCharSB(value, index, c);
1838+
}
1839+
}
1840+
this.count = limit;
1841+
return this;
1842+
}
1843+
1844+
/**
1845+
* Repeats {@code count} copies of the string representation of the
1846+
* {@code codePoint} argument to this sequence.
1847+
* <p>
1848+
* The length of this sequence increases by {@code count} times the
1849+
* string representation length.
1850+
* <p>
1851+
* It is usual to use {@code char} expressions for code points. For example:
1852+
* {@snippet lang="java":
1853+
* // insert 10 asterisks into the buffer
1854+
* sb.repeat('*', 10);
1855+
* }
1856+
*
1857+
* @param codePoint code point to append
1858+
* @param count number of times to copy
1859+
*
1860+
* @return a reference to this object.
1861+
*
1862+
* @throws IllegalArgumentException if the specified {@code codePoint}
1863+
* is not a valid Unicode code point or if {@code count} is negative.
1864+
*
1865+
* @since 21
1866+
*/
1867+
public AbstractStringBuilder repeat(int codePoint, int count) {
1868+
if (count < 0) {
1869+
throw new IllegalArgumentException("count is negative: " + count);
1870+
} else if (count == 0) {
1871+
return this;
1872+
}
1873+
if (Character.isBmpCodePoint(codePoint)) {
1874+
repeat((char)codePoint, count);
1875+
} else {
1876+
repeat(CharBuffer.wrap(Character.toChars(codePoint)), count);
1877+
}
1878+
return this;
1879+
}
1880+
1881+
/**
1882+
* Appends {@code count} copies of the specified {@code CharSequence} {@code cs}
1883+
* to this sequence.
1884+
* <p>
1885+
* The length of this sequence increases by {@code count} times the
1886+
* {@code CharSequence} length.
1887+
* <p>
1888+
* If {@code cs} is {@code null}, then the four characters
1889+
* {@code "null"} are repeated into this sequence.
1890+
*
1891+
* @param cs a {@code CharSequence}
1892+
* @param count number of times to copy
1893+
*
1894+
* @return a reference to this object.
1895+
*
1896+
* @throws IllegalArgumentException if {@code count} is negative
1897+
*
1898+
* @since 21
1899+
*/
1900+
public AbstractStringBuilder repeat(CharSequence cs, int count) {
1901+
if (count < 0) {
1902+
throw new IllegalArgumentException("count is negative: " + count);
1903+
} else if (count == 0) {
1904+
return this;
1905+
} else if (count == 1) {
1906+
return append(cs);
1907+
}
1908+
if (cs == null) {
1909+
cs = "null";
1910+
}
1911+
int length = cs.length();
1912+
if (length == 0) {
1913+
return this;
1914+
} else if (length == 1) {
1915+
return repeat(cs.charAt(0), count);
1916+
}
1917+
int offset = this.count;
1918+
int valueLength = length << coder;
1919+
if ((Integer.MAX_VALUE - offset) / count < valueLength) {
1920+
throw new OutOfMemoryError("Required length exceeds implementation limit");
1921+
}
1922+
int total = count * length;
1923+
int limit = offset + total;
1924+
ensureCapacityInternal(limit);
1925+
if (cs instanceof String str) {
1926+
putStringAt(offset, str);
1927+
} else if (cs instanceof AbstractStringBuilder asb) {
1928+
append(asb);
1929+
} else {
1930+
appendChars(cs, 0, length);
1931+
}
1932+
String.repeatCopyRest(value, offset << coder, total << coder, length << coder);
1933+
this.count = limit;
1934+
return this;
1935+
}
18241936
}

‎src/java.base/share/classes/java/lang/String.java

+26-4
Original file line numberDiff line numberDiff line change
@@ -4563,12 +4563,34 @@ public String repeat(int count) {
45634563
final int limit = len * count;
45644564
final byte[] multiple = new byte[limit];
45654565
System.arraycopy(value, 0, multiple, 0, len);
4566-
int copied = len;
4566+
repeatCopyRest(multiple, 0, limit, len);
4567+
return new String(multiple, coder);
4568+
}
4569+
4570+
/**
4571+
* Used to perform copying after the initial insertion. Copying is optimized
4572+
* by using power of two duplication. First pass duplicates original copy,
4573+
* second pass then duplicates the original and the copy yielding four copies,
4574+
* third pass duplicates four copies yielding eight copies, and so on.
4575+
* Finally, the remainder is filled in with prior copies.
4576+
*
4577+
* @implNote The technique used here is significantly faster than hand-rolled
4578+
* loops or special casing small numbers due to the intensive optimization
4579+
* done by intrinsic {@code System.arraycopy}.
4580+
*
4581+
* @param buffer destination buffer
4582+
* @param offset offset in the destination buffer
4583+
* @param limit total replicated including what is already in the buffer
4584+
* @param copied number of bytes that have already in the buffer
4585+
*/
4586+
static void repeatCopyRest(byte[] buffer, int offset, int limit, int copied) {
4587+
// Initial copy is in the buffer.
45674588
for (; copied < limit - copied; copied <<= 1) {
4568-
System.arraycopy(multiple, 0, multiple, copied, copied);
4589+
// Power of two duplicate.
4590+
System.arraycopy(buffer, offset, buffer, offset + copied, copied);
45694591
}
4570-
System.arraycopy(multiple, 0, multiple, copied, limit - copied);
4571-
return new String(multiple, coder);
4592+
// Duplicate remainder.
4593+
System.arraycopy(buffer, offset, buffer, offset + copied, limit - copied);
45724594
}
45734595

45744596
////////////////////////////////////////////////////////////////

‎src/java.base/share/classes/java/lang/StringBuffer.java

+22
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,28 @@ public synchronized StringBuffer reverse() {
708708
return this;
709709
}
710710

711+
/**
712+
* @throws IllegalArgumentException {@inheritDoc}
713+
*
714+
* @since 21
715+
*/
716+
@Override
717+
public synchronized StringBuffer repeat(int codePoint, int count) {
718+
super.repeat(codePoint, count);
719+
return this;
720+
}
721+
722+
/**
723+
* @throws IllegalArgumentException {@inheritDoc}
724+
*
725+
* @since 21
726+
*/
727+
@Override
728+
public synchronized StringBuffer repeat(CharSequence cs, int count) {
729+
super.repeat(cs, count);
730+
return this;
731+
}
732+
711733
@Override
712734
@IntrinsicCandidate
713735
public synchronized String toString() {

‎src/java.base/share/classes/java/lang/StringBuilder.java

+22
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,28 @@ public StringBuilder reverse() {
446446
return this;
447447
}
448448

449+
/**
450+
* @throws IllegalArgumentException {@inheritDoc}
451+
*
452+
* @since 21
453+
*/
454+
@Override
455+
public StringBuilder repeat(int codePoint, int count) {
456+
super.repeat(codePoint, count);
457+
return this;
458+
}
459+
460+
/**
461+
* @throws IllegalArgumentException {@inheritDoc}
462+
*
463+
* @since 21
464+
*/
465+
@Override
466+
public StringBuilder repeat(CharSequence cs, int count) {
467+
super.repeat(cs, count);
468+
return this;
469+
}
470+
449471
@Override
450472
@IntrinsicCandidate
451473
public String toString() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import org.testng.annotations.Test;
25+
26+
import static org.testng.Assert.assertEquals;
27+
28+
import java.util.Arrays;
29+
30+
/**
31+
* @test
32+
* @bug 8302323
33+
* @summary Test StringBuffer.repeat sanity tests
34+
* @run testng/othervm -XX:-CompactStrings StringBufferRepeat
35+
* @run testng/othervm -XX:+CompactStrings StringBufferRepeat
36+
*/
37+
@Test
38+
public class StringBufferRepeat {
39+
private static class MyChars implements CharSequence {
40+
private static final char[] DATA = new char[] { 'a', 'b', 'c' };
41+
42+
@Override
43+
public int length() {
44+
return DATA.length;
45+
}
46+
47+
@Override
48+
public char charAt(int index) {
49+
return DATA[index];
50+
}
51+
52+
@Override
53+
public CharSequence subSequence(int start, int end) {
54+
return new String(Arrays.copyOfRange(DATA, start, end));
55+
}
56+
}
57+
58+
private static final MyChars MYCHARS = new MyChars();
59+
60+
public void sanity() {
61+
StringBuffer sb = new StringBuffer();
62+
// prime the StringBuffer
63+
sb.append("repeat");
64+
65+
// single character Latin1
66+
sb.repeat('1', 0);
67+
sb.repeat('2', 1);
68+
sb.repeat('3', 5);
69+
70+
// single string Latin1 (optimized)
71+
sb.repeat("1", 0);
72+
sb.repeat("2", 1);
73+
sb.repeat("3", 5);
74+
75+
// multi string Latin1
76+
sb.repeat("-1", 0);
77+
sb.repeat("-2", 1);
78+
sb.repeat("-3", 5);
79+
80+
// single character UTF16
81+
sb.repeat('\u2460', 0);
82+
sb.repeat('\u2461', 1);
83+
sb.repeat('\u2462', 5);
84+
85+
// single string UTF16 (optimized)
86+
sb.repeat("\u2460", 0);
87+
sb.repeat("\u2461", 1);
88+
sb.repeat("\u2462", 5);
89+
90+
// multi string UTF16
91+
92+
sb.repeat("-\u2460", 0);
93+
sb.repeat("-\u2461", 1);
94+
sb.repeat("-\u2462", 5);
95+
96+
// CharSequence
97+
sb.repeat(MYCHARS, 3);
98+
99+
// null
100+
sb.repeat((String)null, 0);
101+
sb.repeat((String)null, 1);
102+
sb.repeat((String)null, 5);
103+
sb.repeat((CharSequence)null, 0);
104+
sb.repeat((CharSequence)null, 1);
105+
sb.repeat((CharSequence)null, 5);
106+
107+
108+
String expected = "repeat233333233333-2-3-3-3-3-3\u2461\u2462\u2462\u2462\u2462\u2462\u2461\u2462\u2462\u2462\u2462\u2462-\u2461-\u2462-\u2462-\u2462-\u2462-\u2462abcabcabc" +
109+
"nullnullnullnullnullnullnullnullnullnullnullnull";
110+
assertEquals(expected, sb.toString());
111+
112+
// Codepoints
113+
114+
sb.setLength(0);
115+
116+
sb.repeat(0, 0);
117+
sb.repeat(0, 1);
118+
sb.repeat(0, 5);
119+
sb.repeat((int)' ', 0);
120+
sb.repeat((int)' ', 1);
121+
sb.repeat((int)' ', 5);
122+
sb.repeat(0x2460, 0);
123+
sb.repeat(0x2461, 1);
124+
sb.repeat(0x2462, 5);
125+
sb.repeat(0x10FFFF, 0);
126+
sb.repeat(0x10FFFF, 1);
127+
sb.repeat(0x10FFFF, 5);
128+
129+
expected = "\u0000\u0000\u0000\u0000\u0000\u0000\u0020\u0020\u0020\u0020\u0020\u0020\u2461\u2462\u2462\u2462\u2462\u2462\udbff\udfff\udbff\udfff\udbff\udfff\udbff\udfff\udbff\udfff\udbff\udfff";
130+
assertEquals(expected, sb.toString());
131+
132+
}
133+
134+
public void exceptions() {
135+
StringBuffer sb = new StringBuffer();
136+
137+
try {
138+
sb.repeat(' ', Integer.MAX_VALUE);
139+
throw new RuntimeException("No OutOfMemoryError thrown");
140+
} catch (OutOfMemoryError | IndexOutOfBoundsException ex) {
141+
// Okay
142+
}
143+
144+
try {
145+
sb.repeat(" ", Integer.MAX_VALUE);
146+
throw new RuntimeException("No OutOfMemoryError thrown");
147+
} catch (OutOfMemoryError | IndexOutOfBoundsException ex) {
148+
// Okay
149+
}
150+
151+
try {
152+
sb.repeat(MYCHARS, Integer.MAX_VALUE);
153+
throw new RuntimeException("No OutOfMemoryError thrown");
154+
} catch (OutOfMemoryError | IndexOutOfBoundsException ex) {
155+
// Okay
156+
}
157+
158+
try {
159+
sb.repeat(' ', -1);
160+
throw new RuntimeException("No IllegalArgumentException thrown");
161+
} catch (IllegalArgumentException ex) {
162+
// Okay
163+
}
164+
165+
try {
166+
sb.repeat("abc", -1);
167+
throw new RuntimeException("No IllegalArgumentException thrown");
168+
} catch (IllegalArgumentException ex) {
169+
// Okay
170+
}
171+
172+
try {
173+
sb.repeat(MYCHARS, -1);
174+
throw new RuntimeException("No IllegalArgumentException thrown");
175+
} catch (IllegalArgumentException ex) {
176+
// Okay
177+
}
178+
179+
try {
180+
sb.repeat(0x10FFFF + 1, -1);
181+
throw new RuntimeException("No IllegalArgumentException thrown");
182+
} catch (IllegalArgumentException ex) {
183+
// Okay
184+
}
185+
186+
try {
187+
sb.repeat(-1, -1);
188+
throw new RuntimeException("No IllegalArgumentException thrown");
189+
} catch (IllegalArgumentException ex) {
190+
// Okay
191+
}
192+
193+
}
194+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import org.testng.annotations.Test;
25+
26+
import static org.testng.Assert.assertEquals;
27+
28+
import java.util.Arrays;
29+
30+
/**
31+
* @test
32+
* @bug 8302323
33+
* @summary Test StringBuilder.repeat sanity tests
34+
* @run testng/othervm -XX:-CompactStrings StringBuilderRepeat
35+
* @run testng/othervm -XX:+CompactStrings StringBuilderRepeat
36+
*/
37+
@Test
38+
public class StringBuilderRepeat {
39+
private static class MyChars implements CharSequence {
40+
private static final char[] DATA = new char[] { 'a', 'b', 'c' };
41+
42+
@Override
43+
public int length() {
44+
return DATA.length;
45+
}
46+
47+
@Override
48+
public char charAt(int index) {
49+
return DATA[index];
50+
}
51+
52+
@Override
53+
public CharSequence subSequence(int start, int end) {
54+
return new String(Arrays.copyOfRange(DATA, start, end));
55+
}
56+
}
57+
58+
private static final MyChars MYCHARS = new MyChars();
59+
60+
public void sanity() {
61+
StringBuilder sb = new StringBuilder();
62+
// prime the StringBuilder
63+
sb.append("repeat");
64+
65+
// single character Latin1
66+
sb.repeat('1', 0);
67+
sb.repeat('2', 1);
68+
sb.repeat('3', 5);
69+
70+
// single string Latin1 (optimized)
71+
sb.repeat("1", 0);
72+
sb.repeat("2", 1);
73+
sb.repeat("3", 5);
74+
75+
// multi string Latin1
76+
sb.repeat("-1", 0);
77+
sb.repeat("-2", 1);
78+
sb.repeat("-3", 5);
79+
80+
// single character UTF16
81+
sb.repeat('\u2460', 0);
82+
sb.repeat('\u2461', 1);
83+
sb.repeat('\u2462', 5);
84+
85+
// single string UTF16 (optimized)
86+
sb.repeat("\u2460", 0);
87+
sb.repeat("\u2461", 1);
88+
sb.repeat("\u2462", 5);
89+
90+
// multi string UTF16
91+
92+
sb.repeat("-\u2460", 0);
93+
sb.repeat("-\u2461", 1);
94+
sb.repeat("-\u2462", 5);
95+
96+
// CharSequence
97+
sb.repeat(MYCHARS, 3);
98+
99+
// null
100+
sb.repeat((String)null, 0);
101+
sb.repeat((String)null, 1);
102+
sb.repeat((String)null, 5);
103+
sb.repeat((CharSequence)null, 0);
104+
sb.repeat((CharSequence)null, 1);
105+
sb.repeat((CharSequence)null, 5);
106+
107+
108+
String expected = "repeat233333233333-2-3-3-3-3-3\u2461\u2462\u2462\u2462\u2462\u2462\u2461\u2462\u2462\u2462\u2462\u2462-\u2461-\u2462-\u2462-\u2462-\u2462-\u2462abcabcabc" +
109+
"nullnullnullnullnullnullnullnullnullnullnullnull";
110+
assertEquals(expected, sb.toString());
111+
112+
// Codepoints
113+
114+
sb.setLength(0);
115+
116+
sb.repeat(0, 0);
117+
sb.repeat(0, 1);
118+
sb.repeat(0, 5);
119+
sb.repeat((int)' ', 0);
120+
sb.repeat((int)' ', 1);
121+
sb.repeat((int)' ', 5);
122+
sb.repeat(0x2460, 0);
123+
sb.repeat(0x2461, 1);
124+
sb.repeat(0x2462, 5);
125+
sb.repeat(0x10FFFF, 0);
126+
sb.repeat(0x10FFFF, 1);
127+
sb.repeat(0x10FFFF, 5);
128+
129+
expected = "\u0000\u0000\u0000\u0000\u0000\u0000\u0020\u0020\u0020\u0020\u0020\u0020\u2461\u2462\u2462\u2462\u2462\u2462\udbff\udfff\udbff\udfff\udbff\udfff\udbff\udfff\udbff\udfff\udbff\udfff";
130+
assertEquals(expected, sb.toString());
131+
132+
}
133+
134+
public void exceptions() {
135+
StringBuilder sb = new StringBuilder();
136+
137+
try {
138+
sb.repeat(' ', Integer.MAX_VALUE);
139+
throw new RuntimeException("No OutOfMemoryError thrown");
140+
} catch (OutOfMemoryError | IndexOutOfBoundsException ex) {
141+
// Okay
142+
}
143+
144+
try {
145+
sb.repeat(" ", Integer.MAX_VALUE);
146+
throw new RuntimeException("No OutOfMemoryError thrown");
147+
} catch (OutOfMemoryError | IndexOutOfBoundsException ex) {
148+
// Okay
149+
}
150+
151+
try {
152+
sb.repeat(MYCHARS, Integer.MAX_VALUE);
153+
throw new RuntimeException("No OutOfMemoryError thrown");
154+
} catch (OutOfMemoryError | IndexOutOfBoundsException ex) {
155+
// Okay
156+
}
157+
158+
try {
159+
sb.repeat(' ', -1);
160+
throw new RuntimeException("No IllegalArgumentException thrown");
161+
} catch (IllegalArgumentException ex) {
162+
// Okay
163+
}
164+
165+
try {
166+
sb.repeat("abc", -1);
167+
throw new RuntimeException("No IllegalArgumentException thrown");
168+
} catch (IllegalArgumentException ex) {
169+
// Okay
170+
}
171+
172+
try {
173+
sb.repeat(MYCHARS, -1);
174+
throw new RuntimeException("No IllegalArgumentException thrown");
175+
} catch (IllegalArgumentException ex) {
176+
// Okay
177+
}
178+
179+
try {
180+
sb.repeat(0x10FFFF + 1, -1);
181+
throw new RuntimeException("No IllegalArgumentException thrown");
182+
} catch (IllegalArgumentException ex) {
183+
// Okay
184+
}
185+
186+
try {
187+
sb.repeat(-1, -1);
188+
throw new RuntimeException("No IllegalArgumentException thrown");
189+
} catch (IllegalArgumentException ex) {
190+
// Okay
191+
}
192+
193+
}
194+
}

0 commit comments

Comments
 (0)
Please sign in to comment.