diff --git a/src/java.base/share/classes/java/io/Reader.java b/src/java.base/share/classes/java/io/Reader.java index 1654156bee19a..f882a939fbcaf 100644 --- a/src/java.base/share/classes/java/io/Reader.java +++ b/src/java.base/share/classes/java/io/Reader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -193,16 +193,7 @@ public int read(char[] cbuf, int off, int len) throws IOException { if (next >= length) return -1; int n = Math.min(length - next, len); - switch (cs) { - case String s -> s.getChars(next, next + n, cbuf, off); - case StringBuilder sb -> sb.getChars(next, next + n, cbuf, off); - case StringBuffer sb -> sb.getChars(next, next + n, cbuf, off); - case CharBuffer cb -> cb.get(next, cbuf, off, n); - default -> { - for (int i = 0; i < n; i++) - cbuf[off + i] = cs.charAt(next + i); - } - } + cs.getChars(next, next + n, cbuf, off); next += n; return n; } diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b8d209b8a6e24..c83effb0b45b1 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -488,33 +488,9 @@ public int offsetByCodePoints(int index, int codePointOffset) { } /** - * Characters are copied from this sequence into the - * destination character array {@code dst}. The first character to - * be copied is at index {@code srcBegin}; the last character to - * be copied is at index {@code srcEnd-1}. The total number of - * characters to be copied is {@code srcEnd-srcBegin}. The - * characters are copied into the subarray of {@code dst} starting - * at index {@code dstBegin} and ending at index: - *
{@code
-     * dstbegin + (srcEnd-srcBegin) - 1
-     * }
- * - * @param srcBegin start copying at this offset. - * @param srcEnd stop copying at this offset. - * @param dst the array to copy the data into. - * @param dstBegin offset into {@code dst}. - * @throws IndexOutOfBoundsException if any of the following is true: - * + * {@inheritDoc CharSequence} */ + @Override public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { Preconditions.checkFromToIndex(srcBegin, srcEnd, count, Preconditions.SIOOBE_FORMATTER); // compatible to old version diff --git a/src/java.base/share/classes/java/lang/CharSequence.java b/src/java.base/share/classes/java/lang/CharSequence.java index 87bc4a5525a85..2732ee35933d6 100644 --- a/src/java.base/share/classes/java/lang/CharSequence.java +++ b/src/java.base/share/classes/java/lang/CharSequence.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, 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 @@ -302,4 +302,47 @@ public static int compare(CharSequence cs1, CharSequence cs2) { return cs1.length() - cs2.length(); } + /** + * Copies characters from this sequence into the given destination array. + * The first character to be copied is at index {@code srcBegin}; the last + * character to be copied is at index {@code srcEnd-1}. The total number of + * characters to be copied is {@code srcEnd-srcBegin}. The + * characters are copied into the subarray of {@code dst} starting + * at index {@code dstBegin} and ending at index: + *
{@code
+     * dstbegin + (srcEnd-srcBegin) - 1
+     * }
+ * + * @param srcBegin start copying at this offset. + * @param srcEnd stop copying at this offset. + * @param dst the array to copy the data into. + * @param dstBegin offset into {@code dst}. + * @throws IndexOutOfBoundsException if any of the following is true: + * + * @throws NullPointerException if {@code dst} is {@code null} + * + * @implSpec + * The default implementation invokes {@link #charAt(int index)} in a loop + * iterating {@code index} from {@code srcBegin} to {@code srcEnd-1}. + * Concurrent truncation of this character sequence can throw + * {@code IndexOutOfBoundsException}. In this case, some characters, but not + * all, may be already transferred. + * + * @since 25 + */ + public default void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { + Objects.checkFromToIndex(srcBegin, srcEnd, length()); + Objects.checkIndex(dstBegin, dst.length - (srcEnd - srcBegin) + 1); + while (srcBegin < srcEnd) + dst[dstBegin++] = charAt(srcBegin++); + } } diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 0632785d89931..76566de089c8f 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -1739,35 +1739,14 @@ public int offsetByCodePoints(int index, int codePointOffset) { } /** - * Copies characters from this string into the destination character - * array. - *

- * The first character to be copied is at index {@code srcBegin}; - * the last character to be copied is at index {@code srcEnd-1} - * (thus the total number of characters to be copied is - * {@code srcEnd-srcBegin}). The characters are copied into the - * subarray of {@code dst} starting at index {@code dstBegin} - * and ending at index: - *

-     *     dstBegin + (srcEnd-srcBegin) - 1
-     * 
- * - * @param srcBegin index of the first character in the string - * to copy. - * @param srcEnd index after the last character in the string - * to copy. - * @param dst the destination array. - * @param dstBegin the start offset in the destination array. - * @throws IndexOutOfBoundsException If any of the following - * is true: - * + * {@inheritDoc CharSequence} + * @param srcBegin {@inheritDoc CharSequence} + * @param srcEnd {@inheritDoc CharSequence} + * @param dst {@inheritDoc CharSequence} + * @param dstBegin {@inheritDoc CharSequence} + * @throws IndexOutOfBoundsException {@inheritDoc CharSequence} */ + @Override public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { checkBoundsBeginEnd(srcBegin, srcEnd, length()); checkBoundsOffCount(dstBegin, srcEnd - srcBegin, dst.length); diff --git a/src/java.base/share/classes/java/lang/StringBuffer.java b/src/java.base/share/classes/java/lang/StringBuffer.java index 1aeceb9598553..2546a84f39cae 100644 --- a/src/java.base/share/classes/java/lang/StringBuffer.java +++ b/src/java.base/share/classes/java/lang/StringBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2025, 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 @@ -279,9 +279,6 @@ public synchronized int offsetByCodePoints(int index, int codePointOffset) { return super.offsetByCodePoints(index, codePointOffset); } - /** - * @throws IndexOutOfBoundsException {@inheritDoc} - */ @Override public synchronized void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) diff --git a/src/java.base/share/classes/java/nio/X-Buffer.java.template b/src/java.base/share/classes/java/nio/X-Buffer.java.template index d089155abf9d6..283f553459778 100644 --- a/src/java.base/share/classes/java/nio/X-Buffer.java.template +++ b/src/java.base/share/classes/java/nio/X-Buffer.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, 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 @@ -1896,6 +1896,44 @@ public abstract sealed class $Type$Buffer #if[char] + /** + * Absolute bulk get method. + * + *

This method transfers {@code srcEnd-srcBegin} characters from this + * buffer into the given array, starting at index {@code srcBegin} in this + * buffer and at offset {@code dstBegin} in the array. The position of this + * buffer is unchanged. + * + * @param srcBegin + * The index in this buffer from which the first character will be + * read; must be non-negative and less than {@code limit()} + * + * @param srcEnd + * The index in this buffer directly before the last character to + * read; must be non-negative and less or equal than {@code limit()} + * and must be greater or equal than {@code srcBegin} + * + * @param dst + * The destination array + * + * @param dstBegin + * The offset within the array of the first character to be + * written; must be non-negative and less than {@code dst.length} + * + * @throws IndexOutOfBoundsException + * If the preconditions on the {@code srcBegin}, {@code srcEnd}, + * and {@code dstBegin} parameters do not hold + * + * @implSpec This method is equivalent to + * {@code get(srcBegin, dst, dstBegin, srcEnd - srcBegin)}. + * + * @since 25 + */ + @Override + public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { + get(srcBegin, dst, dstBegin, srcEnd - srcBegin); + } + /** * Returns a string containing the characters in this buffer. * diff --git a/test/jdk/java/lang/CharSequence/GetChars.java b/test/jdk/java/lang/CharSequence/GetChars.java new file mode 100644 index 0000000000000..5d0594a08975a --- /dev/null +++ b/test/jdk/java/lang/CharSequence/GetChars.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025, 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.Assert; +import org.testng.annotations.Test; + +/** + * @test + * @bug 8343110 + * @summary Check for expected behavior of default implementation of + * CharSequence.getChars(). + * @run testng GetChars + */ +public class GetChars { + private static CharSequence CS = new CharSequence() { + @Override + public int length() { + return 4; + } + + @Override + public char charAt(int index) { + return "Test".charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + throw new UnsupportedOperationException(); + } + }; + + @Test + public void testExactCopy() { + var dst = new char[4]; + CS.getChars(0, 4, dst, 0); + Assert.assertEquals(dst, new char[] {'T', 'e', 's', 't'}); + } + + @Test + public void testPartialCopy() { + var dst = new char[2]; + CS.getChars(1, 3, dst, 0); + Assert.assertEquals(dst, new char[] {'e', 's'}); + } + + @Test + public void testPositionedCopy() { + var dst = new char[] {1, 2, 3, 4, 5, 6}; + CS.getChars(0, 4, dst, 1); + Assert.assertEquals(dst, new char[] {1, 'T', 'e', 's', 't', 6}); + } + + @Test + public void testSrcBeginIsNegative() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CS.getChars(-1, 3, new char[4], 0)); + } + + @Test + public void testDstBeginIsNegative() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CS.getChars(0, 4, new char[4], -1)); + } + + @Test + public void testSrcBeginIsGreaterThanSrcEnd() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CS.getChars(4, 0, new char[4], 0)); + } + + @Test + public void testSrcEndIsGreaterThanSequenceLength() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CS.getChars(0, 5, new char[4], 0)); + } + + @Test + public void testRequestedLengthIsGreaterThanDstLength() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CS.getChars(0, 4, new char[3], 0)); + } + + @Test + public void testDstIsNull() { + Assert.assertThrows(NullPointerException.class, + () -> CS.getChars(0, 4, null, 0)); + } + +} diff --git a/test/jdk/java/nio/Buffer/GetChars.java b/test/jdk/java/nio/Buffer/GetChars.java new file mode 100644 index 0000000000000..8633482871751 --- /dev/null +++ b/test/jdk/java/nio/Buffer/GetChars.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2025, 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 java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +/** + * @test + * @bug 8343110 + * @summary Check for expected behavior of CharBuffer.getChars(). + * @run testng GetChars + * @key randomness + */ +public class GetChars { + private static CharBuffer CB = CharBuffer.wrap("Test"); + + @Test + public void testExactCopy() { + var dst = new char[4]; + CB.getChars(0, 4, dst, 0); + Assert.assertEquals(dst, new char[] {'T', 'e', 's', 't'}); + } + + @Test + public void testPartialCopy() { + var dst = new char[2]; + CB.getChars(1, 3, dst, 0); + Assert.assertEquals(dst, new char[] {'e', 's'}); + } + + @Test + public void testPositionedCopy() { + var dst = new char[] {1, 2, 3, 4, 5, 6}; + CB.getChars(0, 4, dst, 1); + Assert.assertEquals(dst, new char[] {1, 'T', 'e', 's', 't', 6}); + } + + @Test + public void testSrcBeginIsNegative() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CB.getChars(-1, 3, new char[4], 0)); + } + + @Test + public void testDstBeginIsNegative() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CB.getChars(0, 4, new char[4], -1)); + } + + @Test + public void testSrcBeginIsGreaterThanSrcEnd() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CB.getChars(4, 0, new char[4], 0)); + } + + @Test + public void testSrcEndIsGreaterThanSequenceLength() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CB.getChars(0, 5, new char[4], 0)); + } + + @Test + public void testRequestedLengthIsGreaterThanDstLength() { + Assert.assertThrows(IndexOutOfBoundsException.class, + () -> CB.getChars(0, 4, new char[3], 0)); + } + + @Test + public void testDstIsNull() { + Assert.assertThrows(NullPointerException.class, + () -> CB.getChars(0, 4, null, 0)); + } + + private static final Random RAND = new Random(); + private static final int SIZE = 128 + RAND.nextInt(1024); + + /** + * Randomize the char buffer's position and limit. + */ + private static CharBuffer randomizeRange(CharBuffer cb) { + int mid = cb.capacity() >>> 1; + int start = RAND.nextInt(mid + 1); // from 0 to mid + int end = mid + RAND.nextInt(cb.capacity() - mid + 1); // from mid to capacity + cb.position(start); + cb.limit(end); + return cb; + } + + /** + * Randomize the char buffer's contents, position and limit. + */ + private static CharBuffer randomize(CharBuffer cb) { + while (cb.hasRemaining()) { + cb.put((char)RAND.nextInt()); + } + return randomizeRange(cb); + } + + /** + * Sums the remaining chars in the char buffer. + */ + private static int intSum(CharBuffer cb) { + int sum = 0; + cb.mark(); + while (cb.hasRemaining()) { + sum += cb.get(); + } + cb.reset(); + return sum; + } + + /** + * Sums the chars in the char array. + */ + private static int intSum(char[] ca) { + int sum = 0; + for (int i = 0; i < ca.length; i++) + sum += ca[i]; + return sum; + } + + /** + * Creates char buffers to test, adding them to the given list. + */ + private static void addCases(CharBuffer cb, List buffers) { + randomize(cb); + buffers.add(cb); + + buffers.add(cb.slice()); + buffers.add(cb.duplicate()); + buffers.add(cb.asReadOnlyBuffer()); + + buffers.add(randomizeRange(cb.slice())); + buffers.add(randomizeRange(cb.duplicate())); + buffers.add(randomizeRange(cb.asReadOnlyBuffer())); + } + + @DataProvider(name = "charbuffers") + public Object[][] createCharBuffers() { + List buffers = new ArrayList<>(); + + // heap + addCases(CharBuffer.allocate(SIZE), buffers); + addCases(CharBuffer.wrap(new char[SIZE]), buffers); + addCases(ByteBuffer.allocate(SIZE*2).order(ByteOrder.BIG_ENDIAN).asCharBuffer(), + buffers); + addCases(ByteBuffer.allocate(SIZE*2).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(), + buffers); + + // direct + addCases(ByteBuffer.allocateDirect(SIZE*2).order(ByteOrder.BIG_ENDIAN).asCharBuffer(), + buffers); + addCases(ByteBuffer.allocateDirect(SIZE*2).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(), + buffers); + + // read-only buffer backed by a CharSequence + buffers.add(CharBuffer.wrap(randomize(CharBuffer.allocate(SIZE)))); + + Object[][] params = new Object[buffers.size()][]; + for (int i = 0; i < buffers.size(); i++) { + CharBuffer cb = buffers.get(i); + params[i] = new Object[] { cb.getClass().getName(), cb }; + } + + return params; + } + + @Test(dataProvider = "charbuffers") + public void testGetChars(String type, CharBuffer cb) { + System.out.format("%s position=%d, limit=%d%n", type, cb.position(), cb.limit()); + int expected = intSum(cb); + var dst = new char[cb.remaining()]; + cb.getChars(cb.position(), cb.limit(), dst, 0); + int actual = intSum(dst); + assertEquals(actual, expected); + } +}