From 10ba7b67c1a93ec6e916d836f883bc0f82dea710 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Tue, 14 Feb 2023 16:35:41 -0400 Subject: [PATCH 01/13] AbstractStringBuilder repeat --- .../java/lang/AbstractStringBuilder.java | 155 +++++++++++++++++ .../share/classes/java/lang/String.java | 30 +++- .../share/classes/java/lang/StringBuffer.java | 33 ++++ .../classes/java/lang/StringBuilder.java | 33 ++++ test/jdk/java/lang/StringBuilder/Repeat.java | 159 ++++++++++++++++++ 5 files changed, 406 insertions(+), 4 deletions(-) create mode 100644 test/jdk/java/lang/StringBuilder/Repeat.java diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 3a303121aa85a..bd34ab36b6112 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1821,4 +1821,159 @@ private final void appendChars(CharSequence s, int off, int end) { } count += end - off; } + + /** + * Appends {@code times} copies of the character {@code c} to this sequence. + *

+ * The length of this sequence increases by {@code times}. + * + * @param c character to append + * @param times number of times to repeat + * + * @return a reference to this object. + * + * @since 21 + * @throws IllegalArgumentException if {@code times} is less than zero + * @throws StringIndexOutOfBoundsException if the offset is invalid or + * if the result overflows the buffer + */ + public AbstractStringBuilder repeat(char c, int times) { + if (times < 0) { + throw new IllegalArgumentException("times is less than zero: " + times); + } + if (times == 0) { + return this; + } + ensureCapacityInternal(count + times); + if (isLatin1() && StringLatin1.canEncode(c)) { + int index = count; + while (times-- != 0) { + value[index++] = (byte) c; + } + count = index; + } else { + if (isLatin1()) { + inflate(); + } + int index = count; + while (times-- != 0) { + StringUTF16.putCharSB(value, index++, c); + } + count = index; + } + return this; + } + + private AbstractStringBuilder repeatNull(int times) { + if (times < 0) { + throw new IllegalArgumentException("times is less than zero: " + times); + } else if (times == 0) { + return this; + } + int offset = count; + appendNull(); + int length = count - offset; + int valueLength = length << coder; + if ((Integer.MAX_VALUE - offset) / times < valueLength) { + throw new OutOfMemoryError("Required length exceeds implementation limit"); + } + int limit = times * length; + ensureCapacityInternal(offset + limit); + String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); + count = offset + limit; + return this; + } + + /** + * Appends {@code times} copies of the specified string {@code str} to this sequence. + *

+ * The length of this sequence increases by {@code times} times the string length. + * + * @param str a string + * @param times number of times to repeat + * + * @return a reference to this object. + * + * @since 21 + * @throws IllegalArgumentException if {@code times} is less than zero + * @throws StringIndexOutOfBoundsException if the offset is invalid or + * if the result overflows the buffer + */ + public AbstractStringBuilder repeat(String str, int times) { + if (str == null) { + return repeatNull(times); + } else if (times < 0) { + throw new IllegalArgumentException("times is less than zero: " + times); + } else if (times == 0) { + return this; + } else if (times == 1) { + return append(str); + } + int length = str.length(); + if (length == 1) { + return repeat(str.charAt(0), times); + } + int valueLength = length << str.coder(); + if ((Integer.MAX_VALUE - count) / times < valueLength) { + throw new OutOfMemoryError("Required length exceeds implementation limit"); + } + int limit = times * length; + int offset = count; + ensureCapacityInternal(offset + limit); + putStringAt(offset, str); + String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); + count = offset + limit; + return this; + } + + /** + * Appends {@code count} copies of the specified {@code CharSequence} {@code cs} + * to this sequence. + *

+ * The length of this sequence increases by {@code times} times the + * {@code CharSequence} length. + * + * @param cs a {@code CharSequence} + * @param times number of times to repeat + * + * @return a reference to this object. + * + * @since 21 + * @throws IllegalArgumentException if {@code times} is less than zero + * @throws StringIndexOutOfBoundsException if the offset is invalid or + * if the result overflows the buffer + */ + public AbstractStringBuilder repeat(CharSequence cs, int times) { + if (cs == null) { + return repeatNull(times); + } else if (times < 0) { + throw new IllegalArgumentException("times is less than zero: " + times); + } else if (times == 0) { + return this; + } else if (times == 1) { + return append(cs); + } + + int length = cs.length(); + if (length == 1) { + return repeat(cs.charAt(0), times); + } + int valueLength = length << UTF16; + if ((Integer.MAX_VALUE - count) / times < valueLength) { + throw new OutOfMemoryError("Required length exceeds implementation limit"); + } + int limit = times * length; + int offset = count; + ensureCapacityInternal(offset + limit); + 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, limit << coder, length << coder); + count = offset + limit; + return this; + } } diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 1897a06cd6008..01312b311c469 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -4453,12 +4453,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 + * 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); } //////////////////////////////////////////////////////////////// diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index b0ee2a5c2e3dc..75e6e4441e144 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -708,6 +708,39 @@ public synchronized StringBuffer reverse() { return this; } + /** + * @since 21 + * @throws IllegalArgumentException {@inheritDoc} + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + @Override + public StringBuffer repeat(char c, int times) { + super.repeat(c, times); + return this; + } + + /** + * @since 21 + * @throws IllegalArgumentException {@inheritDoc} + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + @Override + public StringBuffer repeat(String str, int times) { + super.repeat(str, times); + return this; + } + + /** + * @since 21 + * @throws IllegalArgumentException {@inheritDoc} + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + @Override + public StringBuffer repeat(CharSequence cs, int times) { + super.repeat(cs, times); + return this; + } + @Override @IntrinsicCandidate public synchronized String toString() { diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java b/src/java.base/share/classes/java/lang/StringBuilder.java index 17fd105ad3fd0..0a1e8945652d8 100644 --- a/src/java.base/share/classes/java/lang/StringBuilder.java +++ b/src/java.base/share/classes/java/lang/StringBuilder.java @@ -446,6 +446,39 @@ public StringBuilder reverse() { return this; } + /** + * @since 21 + * @throws IllegalArgumentException {@inheritDoc} + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + @Override + public StringBuilder repeat(char c, int times) { + super.repeat(c, times); + return this; + } + + /** + * @since 21 + * @throws IllegalArgumentException {@inheritDoc} + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + @Override + public StringBuilder repeat(String str, int times) { + super.repeat(str, times); + return this; + } + + /** + * @since 21 + * @throws IllegalArgumentException {@inheritDoc} + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + @Override + public StringBuilder repeat(CharSequence cs, int times) { + super.repeat(cs, times); + return this; + } + @Override @IntrinsicCandidate public String toString() { diff --git a/test/jdk/java/lang/StringBuilder/Repeat.java b/test/jdk/java/lang/StringBuilder/Repeat.java new file mode 100644 index 0000000000000..458e7e239e385 --- /dev/null +++ b/test/jdk/java/lang/StringBuilder/Repeat.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; + +/** + * @test + * @bug 8302323 + * @summary Test StringBuilder.repeat sanity tests + * @run testng/othervm -XX:-CompactStrings Repeat + * @run testng/othervm -XX:+CompactStrings Repeat + */ +@Test +public class Repeat { + private static class MyChars implements CharSequence { + private static final char[] DATA = new char[] { 'a', 'b', 'c' }; + + @Override + public int length() { + return DATA.length; + } + + @Override + public char charAt(int index) { + return DATA[index]; + } + + @Override + public CharSequence subSequence(int start, int end) { + return new String(Arrays.copyOfRange(DATA, start, end)); + } + } + + private static final MyChars MYCHARS = new MyChars(); + + public void sanity() { + StringBuilder sb = new StringBuilder(); + // prime the StringBuilder + sb.append("repeat"); + + // single character Latin1 + sb.repeat('1', 0); + sb.repeat('2', 1); + sb.repeat('3', 5); + + // single string Latin1 (optimized) + sb.repeat("1", 0); + sb.repeat("2", 1); + sb.repeat("3", 5); + + // multi string Latin1 + sb.repeat("-1", 0); + sb.repeat("-2", 1); + sb.repeat("-3", 5); + + // single character UTF16 + sb.repeat('\u2460', 0); + sb.repeat('\u2461', 1); + sb.repeat('\u2462', 5); + + // single string UTF16 (optimized) + sb.repeat("\u2460", 0); + sb.repeat("\u2461", 1); + sb.repeat("\u2462", 5); + + // multi string UTF16 + + sb.repeat("-\u2460", 0); + sb.repeat("-\u2461", 1); + sb.repeat("-\u2462", 5); + + // CharSequence + sb.repeat(MYCHARS, 3); + + // null + sb.repeat((String)null, 0); + sb.repeat((String)null, 1); + sb.repeat((String)null, 5); + sb.repeat((CharSequence)null, 0); + sb.repeat((CharSequence)null, 1); + sb.repeat((CharSequence)null, 5); + + + 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" + + "nullnullnullnullnullnullnullnullnullnullnullnull"; + assertEquals(expected, sb.toString()); + } + + public void exceptions() { + StringBuilder sb = new StringBuilder(); + + try { + sb.repeat(' ', Integer.MAX_VALUE); + throw new RuntimeException("No OutOfMemoryError thrown"); + } catch (OutOfMemoryError | StringIndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(" ", Integer.MAX_VALUE); + throw new RuntimeException("No OutOfMemoryError thrown"); + } catch (OutOfMemoryError | StringIndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(MYCHARS, Integer.MAX_VALUE); + throw new RuntimeException("No OutOfMemoryError thrown"); + } catch (OutOfMemoryError | StringIndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(' ', -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException | StringIndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat("abc", -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException | StringIndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(MYCHARS, -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException | StringIndexOutOfBoundsException ex) { + // Okay + } + + } +} From 5acdf75e81202694f674c1c4592d437c68f1dfa8 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Fri, 17 Feb 2023 15:57:26 -0400 Subject: [PATCH 02/13] Add synchronized to StringBuffer repeats --- src/java.base/share/classes/java/lang/StringBuffer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index 75e6e4441e144..a02a047a3cc5a 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -714,7 +714,7 @@ public synchronized StringBuffer reverse() { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public StringBuffer repeat(char c, int times) { + public synchronized StringBuffer repeat(char c, int times) { super.repeat(c, times); return this; } @@ -725,7 +725,7 @@ public StringBuffer repeat(char c, int times) { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public StringBuffer repeat(String str, int times) { + public synchronized StringBuffer repeat(String str, int times) { super.repeat(str, times); return this; } @@ -736,7 +736,7 @@ public StringBuffer repeat(String str, int times) { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public StringBuffer repeat(CharSequence cs, int times) { + public synchronized StringBuffer repeat(CharSequence cs, int times) { super.repeat(cs, times); return this; } From c4c1e76afaf0661d534705771e6db6d1b55d1cf9 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Thu, 23 Feb 2023 09:10:44 -0400 Subject: [PATCH 03/13] Clean up fro CSR --- .../java/lang/AbstractStringBuilder.java | 126 ++++++------------ .../share/classes/java/lang/StringBuffer.java | 19 +-- .../classes/java/lang/StringBuilder.java | 19 +-- 3 files changed, 49 insertions(+), 115 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index bd34ab36b6112..81b8c1e5e61fc 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1823,106 +1823,63 @@ private final void appendChars(CharSequence s, int off, int end) { } /** - * Appends {@code times} copies of the character {@code c} to this sequence. + * Appends {@code count} copies of the character {@code c} to this sequence. *

- * The length of this sequence increases by {@code times}. + * The length of this sequence increases by {@code count}. * * @param c character to append - * @param times number of times to repeat + * @param count number of times to copy * * @return a reference to this object. * * @since 21 - * @throws IllegalArgumentException if {@code times} is less than zero - * @throws StringIndexOutOfBoundsException if the offset is invalid or - * if the result overflows the buffer + * @throws IllegalArgumentException if {@code count} is less than zero + * @throws StringIndexOutOfBoundsException if the result overflows the buffer */ - public AbstractStringBuilder repeat(char c, int times) { - if (times < 0) { - throw new IllegalArgumentException("times is less than zero: " + times); + public AbstractStringBuilder repeat(char c, int count) { + if (count < 0) { + throw new IllegalArgumentException("count is less than zero: " + count); } - if (times == 0) { + if (count == 0) { return this; } - ensureCapacityInternal(count + times); + ensureCapacityInternal(this.count + count); if (isLatin1() && StringLatin1.canEncode(c)) { - int index = count; - while (times-- != 0) { + int index = this.count; + while (count-- != 0) { value[index++] = (byte) c; } - count = index; + this.count = index; } else { if (isLatin1()) { inflate(); } - int index = count; - while (times-- != 0) { + int index = this.count; + while (count-- != 0) { StringUTF16.putCharSB(value, index++, c); } - count = index; + this.count = index; } return this; } - private AbstractStringBuilder repeatNull(int times) { - if (times < 0) { - throw new IllegalArgumentException("times is less than zero: " + times); - } else if (times == 0) { + private AbstractStringBuilder repeatNull(int count) { + if (count < 0) { + throw new IllegalArgumentException("count is less than zero: " + count); + } else if (count == 0) { return this; } - int offset = count; + int offset = this.count; appendNull(); - int length = count - offset; + int length = this.count - offset; int valueLength = length << coder; - if ((Integer.MAX_VALUE - offset) / times < valueLength) { + if ((Integer.MAX_VALUE - offset) / count < valueLength) { throw new OutOfMemoryError("Required length exceeds implementation limit"); } - int limit = times * length; + int limit = count * length; ensureCapacityInternal(offset + limit); String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); - count = offset + limit; - return this; - } - - /** - * Appends {@code times} copies of the specified string {@code str} to this sequence. - *

- * The length of this sequence increases by {@code times} times the string length. - * - * @param str a string - * @param times number of times to repeat - * - * @return a reference to this object. - * - * @since 21 - * @throws IllegalArgumentException if {@code times} is less than zero - * @throws StringIndexOutOfBoundsException if the offset is invalid or - * if the result overflows the buffer - */ - public AbstractStringBuilder repeat(String str, int times) { - if (str == null) { - return repeatNull(times); - } else if (times < 0) { - throw new IllegalArgumentException("times is less than zero: " + times); - } else if (times == 0) { - return this; - } else if (times == 1) { - return append(str); - } - int length = str.length(); - if (length == 1) { - return repeat(str.charAt(0), times); - } - int valueLength = length << str.coder(); - if ((Integer.MAX_VALUE - count) / times < valueLength) { - throw new OutOfMemoryError("Required length exceeds implementation limit"); - } - int limit = times * length; - int offset = count; - ensureCapacityInternal(offset + limit); - putStringAt(offset, str); - String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); - count = offset + limit; + this.count = offset + limit; return this; } @@ -1930,40 +1887,39 @@ public AbstractStringBuilder repeat(String str, int times) { * Appends {@code count} copies of the specified {@code CharSequence} {@code cs} * to this sequence. *

- * The length of this sequence increases by {@code times} times the + * The length of this sequence increases by {@code count} times the * {@code CharSequence} length. * - * @param cs a {@code CharSequence} - * @param times number of times to repeat + * @param cs a {@code CharSequence} + * @param count number of times to copy * * @return a reference to this object. * * @since 21 - * @throws IllegalArgumentException if {@code times} is less than zero - * @throws StringIndexOutOfBoundsException if the offset is invalid or - * if the result overflows the buffer + * @throws IllegalArgumentException if {@code count} is less than zero + * @throws StringIndexOutOfBoundsException if the result overflows the buffer */ - public AbstractStringBuilder repeat(CharSequence cs, int times) { + public AbstractStringBuilder repeat(CharSequence cs, int count) { if (cs == null) { - return repeatNull(times); - } else if (times < 0) { - throw new IllegalArgumentException("times is less than zero: " + times); - } else if (times == 0) { + return repeatNull(count); + } else if (count < 0) { + throw new IllegalArgumentException("count is less than zero: " + count); + } else if (count == 0) { return this; - } else if (times == 1) { + } else if (count == 1) { return append(cs); } int length = cs.length(); if (length == 1) { - return repeat(cs.charAt(0), times); + return repeat(cs.charAt(0), count); } int valueLength = length << UTF16; - if ((Integer.MAX_VALUE - count) / times < valueLength) { + if ((Integer.MAX_VALUE - this.count) / count < valueLength) { throw new OutOfMemoryError("Required length exceeds implementation limit"); } - int limit = times * length; - int offset = count; + int limit = count * length; + int offset = this.count; ensureCapacityInternal(offset + limit); if (cs instanceof String str) { putStringAt(offset, str); @@ -1973,7 +1929,7 @@ public AbstractStringBuilder repeat(CharSequence cs, int times) { appendChars(cs, 0, length); } String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); - count = offset + limit; + this.count = offset + limit; return this; } } diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index a02a047a3cc5a..bdd30bd622dd8 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -714,8 +714,8 @@ public synchronized StringBuffer reverse() { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public synchronized StringBuffer repeat(char c, int times) { - super.repeat(c, times); + public synchronized StringBuffer repeat(char c, int count) { + super.repeat(c, count); return this; } @@ -725,19 +725,8 @@ public synchronized StringBuffer repeat(char c, int times) { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public synchronized StringBuffer repeat(String str, int times) { - super.repeat(str, times); - return this; - } - - /** - * @since 21 - * @throws IllegalArgumentException {@inheritDoc} - * @throws StringIndexOutOfBoundsException {@inheritDoc} - */ - @Override - public synchronized StringBuffer repeat(CharSequence cs, int times) { - super.repeat(cs, times); + public synchronized StringBuffer repeat(CharSequence cs, int count) { + super.repeat(cs, count); return this; } diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java b/src/java.base/share/classes/java/lang/StringBuilder.java index 0a1e8945652d8..aab0edb987363 100644 --- a/src/java.base/share/classes/java/lang/StringBuilder.java +++ b/src/java.base/share/classes/java/lang/StringBuilder.java @@ -452,8 +452,8 @@ public StringBuilder reverse() { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public StringBuilder repeat(char c, int times) { - super.repeat(c, times); + public StringBuilder repeat(char c, int count) { + super.repeat(c, count); return this; } @@ -463,19 +463,8 @@ public StringBuilder repeat(char c, int times) { * @throws StringIndexOutOfBoundsException {@inheritDoc} */ @Override - public StringBuilder repeat(String str, int times) { - super.repeat(str, times); - return this; - } - - /** - * @since 21 - * @throws IllegalArgumentException {@inheritDoc} - * @throws StringIndexOutOfBoundsException {@inheritDoc} - */ - @Override - public StringBuilder repeat(CharSequence cs, int times) { - super.repeat(cs, times); + public StringBuilder repeat(CharSequence cs, int count) { + super.repeat(cs, count); return this; } From 530f5543a5e57897cbcf538a536ae4f2e4b94451 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Thu, 23 Feb 2023 13:34:01 -0400 Subject: [PATCH 04/13] PR Cleanup --- .../java/lang/AbstractStringBuilder.java | 68 +++++++------------ .../share/classes/java/lang/StringBuffer.java | 4 +- .../classes/java/lang/StringBuilder.java | 4 +- test/jdk/java/lang/StringBuilder/Repeat.java | 12 ++-- 4 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 81b8c1e5e61fc..b0fcda2448360 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1834,52 +1834,31 @@ private final void appendChars(CharSequence s, int off, int end) { * * @since 21 * @throws IllegalArgumentException if {@code count} is less than zero - * @throws StringIndexOutOfBoundsException if the result overflows the buffer + * @throws IndexOutOfBoundsException if the result overflows the buffer */ public AbstractStringBuilder repeat(char c, int count) { if (count < 0) { throw new IllegalArgumentException("count is less than zero: " + count); - } - if (count == 0) { + } else if (count == 0) { return this; } ensureCapacityInternal(this.count + count); - if (isLatin1() && StringLatin1.canEncode(c)) { - int index = this.count; - while (count-- != 0) { - value[index++] = (byte) c; + int index = this.count; + int limit = index + count; + boolean isLatin1 = isLatin1(); + if (isLatin1 && StringLatin1.canEncode(c)) { + while (index < limit) { + value[index++] = (byte)c; } - this.count = index; } else { - if (isLatin1()) { + if (isLatin1) { inflate(); } - int index = this.count; - while (count-- != 0) { + while (index < limit) { StringUTF16.putCharSB(value, index++, c); } - this.count = index; - } - return this; - } - - private AbstractStringBuilder repeatNull(int count) { - if (count < 0) { - throw new IllegalArgumentException("count is less than zero: " + count); - } else if (count == 0) { - return this; - } - int offset = this.count; - appendNull(); - int length = this.count - offset; - int valueLength = length << coder; - if ((Integer.MAX_VALUE - offset) / count < valueLength) { - throw new OutOfMemoryError("Required length exceeds implementation limit"); } - int limit = count * length; - ensureCapacityInternal(offset + limit); - String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); - this.count = offset + limit; + this.count = limit; return this; } @@ -1897,30 +1876,31 @@ private AbstractStringBuilder repeatNull(int count) { * * @since 21 * @throws IllegalArgumentException if {@code count} is less than zero - * @throws StringIndexOutOfBoundsException if the result overflows the buffer + * @throws IndexOutOfBoundsException if the result overflows the buffer */ public AbstractStringBuilder repeat(CharSequence cs, int count) { - if (cs == null) { - return repeatNull(count); - } else if (count < 0) { + if (count < 0) { throw new IllegalArgumentException("count is less than zero: " + 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 == 1) { return repeat(cs.charAt(0), count); } - int valueLength = length << UTF16; - if ((Integer.MAX_VALUE - this.count) / count < valueLength) { + int offset = this.count; + int valueLength = length << coder; + if ((Integer.MAX_VALUE - offset) / count < valueLength) { throw new OutOfMemoryError("Required length exceeds implementation limit"); } - int limit = count * length; - int offset = this.count; - ensureCapacityInternal(offset + limit); + int total = count * length; + int limit = offset + total; + ensureCapacityInternal(limit); if (cs instanceof String str) { putStringAt(offset, str); } else if (cs instanceof AbstractStringBuilder asb) { @@ -1928,8 +1908,8 @@ public AbstractStringBuilder repeat(CharSequence cs, int count) { } else { appendChars(cs, 0, length); } - String.repeatCopyRest(value, offset << coder, limit << coder, length << coder); - this.count = offset + limit; + String.repeatCopyRest(value, offset << coder, total << coder, length << coder); + this.count = limit; return this; } } diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index bdd30bd622dd8..54da30212a8c1 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -711,7 +711,7 @@ public synchronized StringBuffer reverse() { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public synchronized StringBuffer repeat(char c, int count) { @@ -722,7 +722,7 @@ public synchronized StringBuffer repeat(char c, int count) { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public synchronized StringBuffer repeat(CharSequence cs, int count) { diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java b/src/java.base/share/classes/java/lang/StringBuilder.java index aab0edb987363..7ee70fc6775bf 100644 --- a/src/java.base/share/classes/java/lang/StringBuilder.java +++ b/src/java.base/share/classes/java/lang/StringBuilder.java @@ -449,7 +449,7 @@ public StringBuilder reverse() { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder repeat(char c, int count) { @@ -460,7 +460,7 @@ public StringBuilder repeat(char c, int count) { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder repeat(CharSequence cs, int count) { diff --git a/test/jdk/java/lang/StringBuilder/Repeat.java b/test/jdk/java/lang/StringBuilder/Repeat.java index 458e7e239e385..5fec3ae998035 100644 --- a/test/jdk/java/lang/StringBuilder/Repeat.java +++ b/test/jdk/java/lang/StringBuilder/Repeat.java @@ -116,42 +116,42 @@ public void exceptions() { try { sb.repeat(' ', Integer.MAX_VALUE); throw new RuntimeException("No OutOfMemoryError thrown"); - } catch (OutOfMemoryError | StringIndexOutOfBoundsException ex) { + } catch (OutOfMemoryError | IndexOutOfBoundsException ex) { // Okay } try { sb.repeat(" ", Integer.MAX_VALUE); throw new RuntimeException("No OutOfMemoryError thrown"); - } catch (OutOfMemoryError | StringIndexOutOfBoundsException ex) { + } catch (OutOfMemoryError | IndexOutOfBoundsException ex) { // Okay } try { sb.repeat(MYCHARS, Integer.MAX_VALUE); throw new RuntimeException("No OutOfMemoryError thrown"); - } catch (OutOfMemoryError | StringIndexOutOfBoundsException ex) { + } catch (OutOfMemoryError | IndexOutOfBoundsException ex) { // Okay } try { sb.repeat(' ', -1); throw new RuntimeException("No IllegalArgumentException thrown"); - } catch (IllegalArgumentException | StringIndexOutOfBoundsException ex) { + } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { // Okay } try { sb.repeat("abc", -1); throw new RuntimeException("No IllegalArgumentException thrown"); - } catch (IllegalArgumentException | StringIndexOutOfBoundsException ex) { + } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { // Okay } try { sb.repeat(MYCHARS, -1); throw new RuntimeException("No IllegalArgumentException thrown"); - } catch (IllegalArgumentException | StringIndexOutOfBoundsException ex) { + } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { // Okay } From 01ffb19bb6c2d72b5769cb7dbf35d8c74f7a4e53 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Mon, 27 Feb 2023 09:26:12 -0400 Subject: [PATCH 05/13] Optimize for empty CharSequence --- .../share/classes/java/lang/AbstractStringBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b0fcda2448360..47b22f4625741 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1890,7 +1890,9 @@ public AbstractStringBuilder repeat(CharSequence cs, int count) { cs = "null"; } int length = cs.length(); - if (length == 1) { + if (length == 0) { + return this; + } else if (length == 1) { return repeat(cs.charAt(0), count); } int offset = this.count; From 9da9ec9d394dc0fb7d64cb13f04d72028295bda0 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Tue, 28 Feb 2023 09:05:44 -0400 Subject: [PATCH 06/13] Change error report to use "is negative" --- .../share/classes/java/lang/AbstractStringBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 47b22f4625741..c23df620eb348 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1833,12 +1833,12 @@ private final void appendChars(CharSequence s, int off, int end) { * @return a reference to this object. * * @since 21 - * @throws IllegalArgumentException if {@code count} is less than zero + * @throws IllegalArgumentException if {@code count} is negative * @throws IndexOutOfBoundsException if the result overflows the buffer */ public AbstractStringBuilder repeat(char c, int count) { if (count < 0) { - throw new IllegalArgumentException("count is less than zero: " + count); + throw new IllegalArgumentException("count is negative: " + count); } else if (count == 0) { return this; } @@ -1875,12 +1875,12 @@ public AbstractStringBuilder repeat(char c, int count) { * @return a reference to this object. * * @since 21 - * @throws IllegalArgumentException if {@code count} is less than zero + * @throws IllegalArgumentException if {@code count} is negative * @throws IndexOutOfBoundsException if the result overflows the buffer */ public AbstractStringBuilder repeat(CharSequence cs, int count) { if (count < 0) { - throw new IllegalArgumentException("count is less than zero: " + count); + throw new IllegalArgumentException("count is negative: " + count); } else if (count == 0) { return this; } else if (count == 1) { From 9a157ce792a44f64f67afcbeac5bf3caeb254f21 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Tue, 28 Feb 2023 09:25:13 -0400 Subject: [PATCH 07/13] Remove @throws IndexOutOfBoundsException --- .../share/classes/java/lang/AbstractStringBuilder.java | 5 +++-- src/java.base/share/classes/java/lang/StringBuffer.java | 2 -- src/java.base/share/classes/java/lang/StringBuilder.java | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index c23df620eb348..031da1cdb56de 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1834,7 +1834,6 @@ private final void appendChars(CharSequence s, int off, int end) { * * @since 21 * @throws IllegalArgumentException if {@code count} is negative - * @throws IndexOutOfBoundsException if the result overflows the buffer */ public AbstractStringBuilder repeat(char c, int count) { if (count < 0) { @@ -1868,6 +1867,9 @@ public AbstractStringBuilder repeat(char c, int count) { *

* The length of this sequence increases by {@code count} times the * {@code CharSequence} length. + *

+ * If {@code cs} is {@code null}, then the four characters + * {@code "null"} are repeated into this sequence. * * @param cs a {@code CharSequence} * @param count number of times to copy @@ -1876,7 +1878,6 @@ public AbstractStringBuilder repeat(char c, int count) { * * @since 21 * @throws IllegalArgumentException if {@code count} is negative - * @throws IndexOutOfBoundsException if the result overflows the buffer */ public AbstractStringBuilder repeat(CharSequence cs, int count) { if (count < 0) { diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index 54da30212a8c1..09e6fb3fad204 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -711,7 +711,6 @@ public synchronized StringBuffer reverse() { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public synchronized StringBuffer repeat(char c, int count) { @@ -722,7 +721,6 @@ public synchronized StringBuffer repeat(char c, int count) { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public synchronized StringBuffer repeat(CharSequence cs, int count) { diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java b/src/java.base/share/classes/java/lang/StringBuilder.java index 7ee70fc6775bf..1b41bde77130f 100644 --- a/src/java.base/share/classes/java/lang/StringBuilder.java +++ b/src/java.base/share/classes/java/lang/StringBuilder.java @@ -449,7 +449,6 @@ public StringBuilder reverse() { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder repeat(char c, int count) { @@ -460,7 +459,6 @@ public StringBuilder repeat(char c, int count) { /** * @since 21 * @throws IllegalArgumentException {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public StringBuilder repeat(CharSequence cs, int count) { From 22a993b05a1993099a776c35e537156ea9eb8c76 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Wed, 1 Mar 2023 08:47:11 -0400 Subject: [PATCH 08/13] Move @since --- .../share/classes/java/lang/AbstractStringBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 031da1cdb56de..052310cde2501 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1832,8 +1832,9 @@ private final void appendChars(CharSequence s, int off, int end) { * * @return a reference to this object. * - * @since 21 * @throws IllegalArgumentException if {@code count} is negative + * + * @since 21 */ public AbstractStringBuilder repeat(char c, int count) { if (count < 0) { @@ -1876,8 +1877,9 @@ public AbstractStringBuilder repeat(char c, int count) { * * @return a reference to this object. * - * @since 21 * @throws IllegalArgumentException if {@code count} is negative + * + * @since 21 */ public AbstractStringBuilder repeat(CharSequence cs, int count) { if (count < 0) { From 34c7948c21ba31c0f6b6d2aa07203d86768496b4 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Wed, 1 Mar 2023 08:50:05 -0400 Subject: [PATCH 09/13] Move @since in subclasses --- src/java.base/share/classes/java/lang/StringBuffer.java | 6 ++++-- src/java.base/share/classes/java/lang/StringBuilder.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index 09e6fb3fad204..032c1721a03cf 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -709,8 +709,9 @@ public synchronized StringBuffer reverse() { } /** - * @since 21 * @throws IllegalArgumentException {@inheritDoc} + * + * @since 21 */ @Override public synchronized StringBuffer repeat(char c, int count) { @@ -719,8 +720,9 @@ public synchronized StringBuffer repeat(char c, int count) { } /** - * @since 21 * @throws IllegalArgumentException {@inheritDoc} + * + * @since 21 */ @Override public synchronized StringBuffer repeat(CharSequence cs, int count) { diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java b/src/java.base/share/classes/java/lang/StringBuilder.java index 1b41bde77130f..9c169d8699d8a 100644 --- a/src/java.base/share/classes/java/lang/StringBuilder.java +++ b/src/java.base/share/classes/java/lang/StringBuilder.java @@ -447,8 +447,9 @@ public StringBuilder reverse() { } /** - * @since 21 * @throws IllegalArgumentException {@inheritDoc} + * + * @since 21 */ @Override public StringBuilder repeat(char c, int count) { @@ -457,8 +458,9 @@ public StringBuilder repeat(char c, int count) { } /** - * @since 21 * @throws IllegalArgumentException {@inheritDoc} + * + * @since 21 */ @Override public StringBuilder repeat(CharSequence cs, int count) { From 02a4cc70bf6136f405985cae5914104de32d1330 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Thu, 2 Mar 2023 16:00:11 -0400 Subject: [PATCH 10/13] Support code points --- .../java/lang/AbstractStringBuilder.java | 57 ++++++++++++------- .../share/classes/java/lang/StringBuffer.java | 4 +- .../classes/java/lang/StringBuilder.java | 4 +- test/jdk/java/lang/StringBuilder/Repeat.java | 21 +++++++ 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 052310cde2501..9db149945ca3f 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -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; @@ -1822,43 +1823,55 @@ 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)) { + byte b = (byte)c; + for (int index = this.count; index < limit; index++) { + value[index] = b; + } + } else { + if (isLatin1) { + inflate(); + } + for (int index = this.count; index < limit; index++) { + StringUTF16.putCharSB(value, index, c); + } + } + this.count = limit; + return this; + } + /** - * Appends {@code count} copies of the character {@code c} to this sequence. + * Repeats {@code count} copies of the string representation of the + * {@code codePoint} argument to this sequence. *

- * The length of this sequence increases by {@code count}. + * The length of this sequence increases by {@code count} times the + * string representation length. * - * @param c character to append - * @param count number of times to copy + * @param codePoint code point to append + * @param count number of times to copy * * @return a reference to this object. * - * @throws IllegalArgumentException if {@code count} is negative + * @throws IllegalArgumentException if the specified {@code codePoint} + * is not a valid Unicode code point or if {@code count} is negative. * * @since 21 */ - public AbstractStringBuilder repeat(char c, int count) { + public AbstractStringBuilder repeat(int codePoint, int count) { if (count < 0) { throw new IllegalArgumentException("count is negative: " + count); } else if (count == 0) { return this; } - ensureCapacityInternal(this.count + count); - int index = this.count; - int limit = index + count; - boolean isLatin1 = isLatin1(); - if (isLatin1 && StringLatin1.canEncode(c)) { - while (index < limit) { - value[index++] = (byte)c; - } + if (Character.isBmpCodePoint(codePoint)) { + repeat((char)codePoint, count); } else { - if (isLatin1) { - inflate(); - } - while (index < limit) { - StringUTF16.putCharSB(value, index++, c); - } + repeat(CharBuffer.wrap(Character.toChars(codePoint)), count); } - this.count = limit; return this; } @@ -1908,7 +1921,7 @@ public AbstractStringBuilder repeat(CharSequence cs, int count) { ensureCapacityInternal(limit); if (cs instanceof String str) { putStringAt(offset, str); - } else if (cs instanceof AbstractStringBuilder asb) { + } else if (cs instanceof AbstractStringBuilder asb) { append(asb); } else { appendChars(cs, 0, length); diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index 032c1721a03cf..959dd01fe5c84 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -714,8 +714,8 @@ public synchronized StringBuffer reverse() { * @since 21 */ @Override - public synchronized StringBuffer repeat(char c, int count) { - super.repeat(c, count); + public synchronized StringBuffer repeat(int codePoint, int count) { + super.repeat(codePoint, count); return this; } diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java b/src/java.base/share/classes/java/lang/StringBuilder.java index 9c169d8699d8a..add50081c7709 100644 --- a/src/java.base/share/classes/java/lang/StringBuilder.java +++ b/src/java.base/share/classes/java/lang/StringBuilder.java @@ -452,8 +452,8 @@ public StringBuilder reverse() { * @since 21 */ @Override - public StringBuilder repeat(char c, int count) { - super.repeat(c, count); + public StringBuilder repeat(int codePoint, int count) { + super.repeat(codePoint, count); return this; } diff --git a/test/jdk/java/lang/StringBuilder/Repeat.java b/test/jdk/java/lang/StringBuilder/Repeat.java index 5fec3ae998035..a3110ca7cb11c 100644 --- a/test/jdk/java/lang/StringBuilder/Repeat.java +++ b/test/jdk/java/lang/StringBuilder/Repeat.java @@ -108,6 +108,27 @@ public void sanity() { 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" + "nullnullnullnullnullnullnullnullnullnullnullnull"; assertEquals(expected, sb.toString()); + + // Codepoints + + sb.setLength(0); + + sb.repeat(0, 0); + sb.repeat(0, 1); + sb.repeat(0, 5); + sb.repeat((int)' ', 0); + sb.repeat((int)' ', 1); + sb.repeat((int)' ', 5); + sb.repeat(0x2460, 0); + sb.repeat(0x2461, 1); + sb.repeat(0x2462, 5); + sb.repeat(0x10FFFF, 0); + sb.repeat(0x10FFFF, 1); + sb.repeat(0x10FFFF, 5); + + 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"; + assertEquals(expected, sb.toString()); + } public void exceptions() { From 7d3ecc77b421c4652e92a0f5dcf682a973e5248c Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Thu, 2 Mar 2023 16:12:30 -0400 Subject: [PATCH 11/13] Add snippet --- .../share/classes/java/lang/AbstractStringBuilder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 9db149945ca3f..745f2ade53414 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1850,6 +1850,12 @@ private AbstractStringBuilder repeat(char c, int count) { *

* The length of this sequence increases by {@code count} times the * string representation length. + *

+ * 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 From 1a77988bc92b4f0a3106e61687fe58eceae18f59 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Fri, 3 Mar 2023 14:56:52 -0400 Subject: [PATCH 12/13] Expand test for StringBuffer and illegal code points --- .../StringBuilder/StringBufferRepeat.java | 194 ++++++++++++++++++ .../{Repeat.java => StringBuilderRepeat.java} | 26 ++- 2 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 test/jdk/java/lang/StringBuilder/StringBufferRepeat.java rename test/jdk/java/lang/StringBuilder/{Repeat.java => StringBuilderRepeat.java} (87%) diff --git a/test/jdk/java/lang/StringBuilder/StringBufferRepeat.java b/test/jdk/java/lang/StringBuilder/StringBufferRepeat.java new file mode 100644 index 0000000000000..2d1f3c64f60cc --- /dev/null +++ b/test/jdk/java/lang/StringBuilder/StringBufferRepeat.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; + +/** + * @test + * @bug 8302323 + * @summary Test StringBuffer.repeat sanity tests + * @run testng/othervm -XX:-CompactStrings StringBufferRepeat + * @run testng/othervm -XX:+CompactStrings StringBufferRepeat + */ +@Test +public class StringBufferRepeat { + private static class MyChars implements CharSequence { + private static final char[] DATA = new char[] { 'a', 'b', 'c' }; + + @Override + public int length() { + return DATA.length; + } + + @Override + public char charAt(int index) { + return DATA[index]; + } + + @Override + public CharSequence subSequence(int start, int end) { + return new String(Arrays.copyOfRange(DATA, start, end)); + } + } + + private static final MyChars MYCHARS = new MyChars(); + + public void sanity() { + StringBuffer sb = new StringBuffer(); + // prime the StringBuffer + sb.append("repeat"); + + // single character Latin1 + sb.repeat('1', 0); + sb.repeat('2', 1); + sb.repeat('3', 5); + + // single string Latin1 (optimized) + sb.repeat("1", 0); + sb.repeat("2", 1); + sb.repeat("3", 5); + + // multi string Latin1 + sb.repeat("-1", 0); + sb.repeat("-2", 1); + sb.repeat("-3", 5); + + // single character UTF16 + sb.repeat('\u2460', 0); + sb.repeat('\u2461', 1); + sb.repeat('\u2462', 5); + + // single string UTF16 (optimized) + sb.repeat("\u2460", 0); + sb.repeat("\u2461", 1); + sb.repeat("\u2462", 5); + + // multi string UTF16 + + sb.repeat("-\u2460", 0); + sb.repeat("-\u2461", 1); + sb.repeat("-\u2462", 5); + + // CharSequence + sb.repeat(MYCHARS, 3); + + // null + sb.repeat((String)null, 0); + sb.repeat((String)null, 1); + sb.repeat((String)null, 5); + sb.repeat((CharSequence)null, 0); + sb.repeat((CharSequence)null, 1); + sb.repeat((CharSequence)null, 5); + + + 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" + + "nullnullnullnullnullnullnullnullnullnullnullnull"; + assertEquals(expected, sb.toString()); + + // Codepoints + + sb.setLength(0); + + sb.repeat(0, 0); + sb.repeat(0, 1); + sb.repeat(0, 5); + sb.repeat((int)' ', 0); + sb.repeat((int)' ', 1); + sb.repeat((int)' ', 5); + sb.repeat(0x2460, 0); + sb.repeat(0x2461, 1); + sb.repeat(0x2462, 5); + sb.repeat(0x10FFFF, 0); + sb.repeat(0x10FFFF, 1); + sb.repeat(0x10FFFF, 5); + + 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"; + assertEquals(expected, sb.toString()); + + } + + public void exceptions() { + StringBuffer sb = new StringBuffer(); + + try { + sb.repeat(' ', Integer.MAX_VALUE); + throw new RuntimeException("No OutOfMemoryError thrown"); + } catch (OutOfMemoryError | IndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(" ", Integer.MAX_VALUE); + throw new RuntimeException("No OutOfMemoryError thrown"); + } catch (OutOfMemoryError | IndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(MYCHARS, Integer.MAX_VALUE); + throw new RuntimeException("No OutOfMemoryError thrown"); + } catch (OutOfMemoryError | IndexOutOfBoundsException ex) { + // Okay + } + + try { + sb.repeat(' ', -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { + // Okay + } + + try { + sb.repeat("abc", -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { + // Okay + } + + try { + sb.repeat(MYCHARS, -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { + // Okay + } + + try { + sb.repeat(0x10FFFF + 1, -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { + // Okay + } + + try { + sb.repeat(-1, -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { + // Okay + } + + } +} diff --git a/test/jdk/java/lang/StringBuilder/Repeat.java b/test/jdk/java/lang/StringBuilder/StringBuilderRepeat.java similarity index 87% rename from test/jdk/java/lang/StringBuilder/Repeat.java rename to test/jdk/java/lang/StringBuilder/StringBuilderRepeat.java index a3110ca7cb11c..c191e85d7f172 100644 --- a/test/jdk/java/lang/StringBuilder/Repeat.java +++ b/test/jdk/java/lang/StringBuilder/StringBuilderRepeat.java @@ -31,11 +31,11 @@ * @test * @bug 8302323 * @summary Test StringBuilder.repeat sanity tests - * @run testng/othervm -XX:-CompactStrings Repeat - * @run testng/othervm -XX:+CompactStrings Repeat + * @run testng/othervm -XX:-CompactStrings StringBuilderRepeat + * @run testng/othervm -XX:+CompactStrings StringBuilderRepeat */ @Test -public class Repeat { +public class StringBuilderRepeat { private static class MyChars implements CharSequence { private static final char[] DATA = new char[] { 'a', 'b', 'c' }; @@ -158,21 +158,35 @@ public void exceptions() { try { sb.repeat(' ', -1); throw new RuntimeException("No IllegalArgumentException thrown"); - } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { + } catch (IllegalArgumentException ex) { // Okay } try { sb.repeat("abc", -1); throw new RuntimeException("No IllegalArgumentException thrown"); - } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { + } catch (IllegalArgumentException ex) { // Okay } try { sb.repeat(MYCHARS, -1); throw new RuntimeException("No IllegalArgumentException thrown"); - } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { + } catch (IllegalArgumentException ex) { + // Okay + } + + try { + sb.repeat(0x10FFFF + 1, -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { + // Okay + } + + try { + sb.repeat(-1, -1); + throw new RuntimeException("No IllegalArgumentException thrown"); + } catch (IllegalArgumentException ex) { // Okay } From 796406b55eaed0d3726636d3071036a4f643cd44 Mon Sep 17 00:00:00 2001 From: JimLaskey Date: Mon, 27 Mar 2023 15:30:35 -0300 Subject: [PATCH 13/13] Use Arrays.fill --- .../share/classes/java/lang/AbstractStringBuilder.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 745f2ade53414..87ac55b274ad0 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1828,10 +1828,7 @@ private AbstractStringBuilder repeat(char c, int count) { ensureCapacityInternal(limit); boolean isLatin1 = isLatin1(); if (isLatin1 && StringLatin1.canEncode(c)) { - byte b = (byte)c; - for (int index = this.count; index < limit; index++) { - value[index] = b; - } + Arrays.fill(value, this.count, limit, (byte)c); } else { if (isLatin1) { inflate();