Skip to content

Commit 9b9b5a7

Browse files
author
Jim Laskey
committed
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

Lines changed: 112 additions & 0 deletions
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

Lines changed: 26 additions & 4 deletions
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

Lines changed: 22 additions & 0 deletions
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

Lines changed: 22 additions & 0 deletions
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() {

0 commit comments

Comments
 (0)