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

JDK-8302323 Add repeat methods to StringBuilder/StringBuffer #12728

Closed
wants to merge 14 commits into from
112 changes: 112 additions & 0 deletions src/java.base/share/classes/java/lang/AbstractStringBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import jdk.internal.math.FloatToDecimal;

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Spliterator;
import java.util.stream.IntStream;
Expand Down Expand Up @@ -1821,4 +1822,115 @@ private final void appendChars(CharSequence s, int off, int end) {
}
count += end - off;
}

private AbstractStringBuilder repeat(char c, int count) {
int limit = this.count + count;
ensureCapacityInternal(limit);
boolean isLatin1 = isLatin1();
if (isLatin1 && StringLatin1.canEncode(c)) {
Arrays.fill(value, this.count, limit, (byte)c);
} else {
if (isLatin1) {
inflate();
}
for (int index = this.count; index < limit; index++) {
StringUTF16.putCharSB(value, index, c);
}
}
this.count = limit;
return this;
}

/**
* Repeats {@code count} copies of the string representation of the
* {@code codePoint} argument to this sequence.
* <p>
* The length of this sequence increases by {@code count} times the
* string representation length.
* <p>
* It is usual to use {@code char} expressions for code points. For example:
* {@snippet lang="java":
* // insert 10 asterisks into the buffer
* sb.repeat('*', 10);
* }
*
* @param codePoint code point to append
* @param count number of times to copy
*
* @return a reference to this object.
*
* @throws IllegalArgumentException if the specified {@code codePoint}
* is not a valid Unicode code point or if {@code count} is negative.
*
* @since 21
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
*/
public AbstractStringBuilder repeat(int codePoint, int count) {
if (count < 0) {
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException("count is negative: " + count);
} else if (count == 0) {
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
return this;
}
if (Character.isBmpCodePoint(codePoint)) {
repeat((char)codePoint, count);
} else {
repeat(CharBuffer.wrap(Character.toChars(codePoint)), count);
}
return this;
}

/**
* Appends {@code count} copies of the specified {@code CharSequence} {@code cs}
* to this sequence.
* <p>
* The length of this sequence increases by {@code count} times the
* {@code CharSequence} length.
* <p>
* If {@code cs} is {@code null}, then the four characters
* {@code "null"} are repeated into this sequence.
*
* @param cs a {@code CharSequence}
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
* @param count number of times to copy
*
* @return a reference to this object.
*
* @throws IllegalArgumentException if {@code count} is negative
*
* @since 21
*/
public AbstractStringBuilder repeat(CharSequence cs, int count) {
if (count < 0) {
throw new IllegalArgumentException("count is negative: " + count);
} else if (count == 0) {
return this;
} else if (count == 1) {
return append(cs);
}
if (cs == null) {
cs = "null";
}
int length = cs.length();
if (length == 0) {
return this;
} else if (length == 1) {
return repeat(cs.charAt(0), count);
}
int offset = this.count;
int valueLength = length << coder;
if ((Integer.MAX_VALUE - offset) / count < valueLength) {
throw new OutOfMemoryError("Required length exceeds implementation limit");
}
int total = count * length;
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
int limit = offset + total;
ensureCapacityInternal(limit);
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
if (cs instanceof String str) {
putStringAt(offset, str);
} else if (cs instanceof AbstractStringBuilder asb) {
append(asb);
} else {
appendChars(cs, 0, length);
}
String.repeatCopyRest(value, offset << coder, total << coder, length << coder);
this.count = limit;
return this;
}
}
30 changes: 26 additions & 4 deletions src/java.base/share/classes/java/lang/String.java
Original file line number Diff line number Diff line change
Expand Up @@ -4522,12 +4522,34 @@ public String repeat(int count) {
final int limit = len * count;
final byte[] multiple = new byte[limit];
System.arraycopy(value, 0, multiple, 0, len);
int copied = len;
repeatCopyRest(multiple, 0, limit, len);
return new String(multiple, coder);
}

/**
* Used to perform copying after the initial insertion. Copying is optimized
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
* by using power of two duplication. First pass duplicates original copy,
* second pass then duplicates the original and the copy yielding four copies,
* third pass duplicates four copies yielding eight copies, and so on.
* Finally, the remainder is filled in with prior copies.
*
* @implNote The technique used here is significantly faster than hand-rolled
* loops or special casing small numbers due to the intensive optimization
* done by intrinsic {@code System.arraycopy}.
*
* @param buffer destination buffer
* @param offset offset in the destination buffer
* @param limit total replicated including what is already in the buffer
* @param copied number of bytes that have already in the buffer
*/
static void repeatCopyRest(byte[] buffer, int offset, int limit, int copied) {
// Initial copy is in the buffer.
for (; copied < limit - copied; copied <<= 1) {
System.arraycopy(multiple, 0, multiple, copied, copied);
// Power of two duplicate.
System.arraycopy(buffer, offset, buffer, offset + copied, copied);
}
System.arraycopy(multiple, 0, multiple, copied, limit - copied);
return new String(multiple, coder);
// Duplicate remainder.
System.arraycopy(buffer, offset, buffer, offset + copied, limit - copied);
}

////////////////////////////////////////////////////////////////
Expand Down
22 changes: 22 additions & 0 deletions src/java.base/share/classes/java/lang/StringBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,28 @@ public synchronized StringBuffer reverse() {
return this;
}

/**
* @throws IllegalArgumentException {@inheritDoc}
JimLaskey marked this conversation as resolved.
Show resolved Hide resolved
*
* @since 21
*/
@Override
public synchronized StringBuffer repeat(int codePoint, int count) {
super.repeat(codePoint, count);
return this;
}

/**
* @throws IllegalArgumentException {@inheritDoc}
*
* @since 21
*/
@Override
public synchronized StringBuffer repeat(CharSequence cs, int count) {
super.repeat(cs, count);
return this;
}

@Override
@IntrinsicCandidate
public synchronized String toString() {
Expand Down
22 changes: 22 additions & 0 deletions src/java.base/share/classes/java/lang/StringBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,28 @@ public StringBuilder reverse() {
return this;
}

/**
* @throws IllegalArgumentException {@inheritDoc}
*
* @since 21
*/
@Override
public StringBuilder repeat(int codePoint, int count) {
super.repeat(codePoint, count);
return this;
}

/**
* @throws IllegalArgumentException {@inheritDoc}
*
* @since 21
*/
@Override
public StringBuilder repeat(CharSequence cs, int count) {
super.repeat(cs, count);
return this;
}

@Override
@IntrinsicCandidate
public String toString() {
Expand Down