diff --git a/src/java.base/share/classes/java/io/CharArrayReader.java b/src/java.base/share/classes/java/io/CharArrayReader.java index bd2d7e965cec5..1277248399d74 100644 --- a/src/java.base/share/classes/java/io/CharArrayReader.java +++ b/src/java.base/share/classes/java/io/CharArrayReader.java @@ -25,6 +25,7 @@ package java.io; +import java.nio.CharBuffer; import java.util.Objects; /** @@ -152,6 +153,23 @@ public int read(char[] cbuf, int off, int len) throws IOException { } } + @Override + public int read(CharBuffer target) throws IOException { + synchronized (lock) { + ensureOpen(); + + if (pos >= count) { + return -1; + } + + int avail = count - pos; + int len = Math.min(avail, target.remaining()); + target.put(buf, pos, len); + pos += len; + return len; + } + } + /** * Skips characters. If the stream is already at its end before this method * is invoked, then no characters are skipped and zero is returned. diff --git a/src/java.base/share/classes/java/io/InputStreamReader.java b/src/java.base/share/classes/java/io/InputStreamReader.java index b346183c6627f..b79ac9f7a555f 100644 --- a/src/java.base/share/classes/java/io/InputStreamReader.java +++ b/src/java.base/share/classes/java/io/InputStreamReader.java @@ -25,6 +25,7 @@ package java.io; +import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import sun.nio.cs.StreamDecoder; @@ -149,6 +150,10 @@ public String getEncoding() { return sd.getEncoding(); } + public int read(CharBuffer target) throws IOException { + return sd.read(target); + } + /** * Reads a single character. * diff --git a/src/java.base/share/classes/java/io/Reader.java b/src/java.base/share/classes/java/io/Reader.java index 54262b5209950..6fe224a380ea9 100644 --- a/src/java.base/share/classes/java/io/Reader.java +++ b/src/java.base/share/classes/java/io/Reader.java @@ -184,12 +184,25 @@ protected Reader(Object lock) { * @since 1.5 */ public int read(CharBuffer target) throws IOException { - int len = target.remaining(); - char[] cbuf = new char[len]; - int n = read(cbuf, 0, len); - if (n > 0) - target.put(cbuf, 0, n); - return n; + int nread; + if (target.hasArray()) { + char[] cbuf = target.array(); + int pos = target.position(); + int rem = target.limit() - pos; + if (rem <= 0) + return -1; + int off = target.arrayOffset() + pos; + nread = this.read(cbuf, off, rem); + if (nread > 0) + target.position(pos + nread); + } else { + int len = target.remaining(); + char[] cbuf = new char[len]; + nread = read(cbuf, 0, len); + if (nread > 0) + target.put(cbuf, 0, nread); + } + return nread; } /** @@ -206,7 +219,7 @@ public int read(CharBuffer target) throws IOException { * @throws IOException If an I/O error occurs */ public int read() throws IOException { - char cb[] = new char[1]; + char[] cb = new char[1]; if (read(cb, 0, 1) == -1) return -1; else @@ -231,7 +244,7 @@ public int read() throws IOException { * * @throws IOException If an I/O error occurs */ - public int read(char cbuf[]) throws IOException { + public int read(char[] cbuf) throws IOException { return read(cbuf, 0, cbuf.length); } @@ -258,13 +271,13 @@ public int read(char cbuf[]) throws IOException { * or {@code len} is greater than {@code cbuf.length - off} * @throws IOException If an I/O error occurs */ - public abstract int read(char cbuf[], int off, int len) throws IOException; + public abstract int read(char[] cbuf, int off, int len) throws IOException; /** Maximum skip-buffer size */ private static final int maxSkipBufferSize = 8192; /** Skip buffer, null until allocated */ - private char skipBuffer[] = null; + private char[] skipBuffer = null; /** * Skips characters. This method will block until some characters are diff --git a/test/jdk/java/io/CharArrayReader/ReadCharBuffer.java b/test/jdk/java/io/CharArrayReader/ReadCharBuffer.java new file mode 100644 index 0000000000000..545ba8ba0295c --- /dev/null +++ b/test/jdk/java/io/CharArrayReader/ReadCharBuffer.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021, 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. + */ + +/* + * @test + * @bug 4926314 + * @summary Test for CharArrayReader#read(CharBuffer). + * @run testng ReadCharBuffer + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.util.Arrays; + +import static org.testng.Assert.assertEquals; + +public class ReadCharBuffer { + + private static final int BUFFER_SIZE = 7; + + @DataProvider(name = "buffers") + public Object[][] createBuffers() { + // test both on-heap and off-heap buffers as they may use different code paths + return new Object[][]{ + new Object[]{CharBuffer.allocate(BUFFER_SIZE)}, + new Object[]{ByteBuffer.allocateDirect(BUFFER_SIZE * 2).asCharBuffer()} + }; + } + + @Test(dataProvider = "buffers") + public void read(CharBuffer buffer) throws IOException { + fillBuffer(buffer); + + try (Reader reader = new CharArrayReader("ABCD".toCharArray())) { + buffer.limit(3); + buffer.position(1); + assertEquals(reader.read(buffer), 2); + assertEquals(buffer.position(), 3); + assertEquals(buffer.limit(), 3); + + buffer.limit(7); + buffer.position(4); + assertEquals(reader.read(buffer), 2); + assertEquals(buffer.position(), 6); + assertEquals(buffer.limit(), 7); + + assertEquals(reader.read(buffer), -1); + } + + buffer.clear(); + assertEquals(buffer.toString(), "xABxCDx"); + } + + private void fillBuffer(CharBuffer buffer) { + char[] filler = new char[BUFFER_SIZE]; + Arrays.fill(filler, 'x'); + buffer.put(filler); + buffer.clear(); + } + +} diff --git a/test/jdk/java/io/InputStreamReader/ReadCharBuffer.java b/test/jdk/java/io/InputStreamReader/ReadCharBuffer.java new file mode 100644 index 0000000000000..44abf3cb30d07 --- /dev/null +++ b/test/jdk/java/io/InputStreamReader/ReadCharBuffer.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021, 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. + */ + +/* + * @test + * @bug 4926314 + * @summary Test for InputStreamReader#read(CharBuffer). + * @run testng ReadCharBuffer + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.util.Arrays; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.testng.Assert.assertEquals; + +public class ReadCharBuffer { + + private static final int BUFFER_SIZE = 24; + + @DataProvider(name = "buffers") + public Object[][] createBuffers() { + // test both on-heap and off-heap buffers as they make use different code paths + return new Object[][]{ + new Object[]{CharBuffer.allocate(BUFFER_SIZE)}, + new Object[]{ByteBuffer.allocateDirect(BUFFER_SIZE * 2).asCharBuffer()} + }; + } + + @Test(dataProvider = "buffers") + public void read(CharBuffer buffer) throws IOException { + fillBuffer(buffer); + + try (Reader reader = new InputStreamReader(new ByteArrayInputStream("ABCDEFGHIJKLMNOPQRTUVWXYZ".getBytes(US_ASCII)), US_ASCII)) { + buffer.limit(7); + buffer.position(1); + assertEquals(reader.read(buffer), 6); + assertEquals(buffer.position(), 7); + assertEquals(buffer.limit(), 7); + + buffer.limit(16); + buffer.position(8); + assertEquals(reader.read(buffer), 8); + assertEquals(buffer.position(), 16); + assertEquals(buffer.limit(), 16); + } + + buffer.clear(); + assertEquals(buffer.toString(), "xABCDEFxGHIJKLMNxxxxxxxx"); + } + + private void fillBuffer(CharBuffer buffer) { + char[] filler = new char[BUFFER_SIZE]; + Arrays.fill(filler, 'x'); + buffer.put(filler); + buffer.clear(); + } + +} diff --git a/test/jdk/java/io/Reader/ReadCharBuffer.java b/test/jdk/java/io/Reader/ReadCharBuffer.java new file mode 100644 index 0000000000000..1fbadd23676bd --- /dev/null +++ b/test/jdk/java/io/Reader/ReadCharBuffer.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021, 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. + */ + +/* + * @test + * @bug 4926314 + * @summary Test for Reader#read(CharBuffer). + * @run testng ReadCharBuffer + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +import java.io.IOException; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.util.Arrays; +import java.util.Objects; + +import static org.testng.Assert.assertEquals; + +@Test(groups = "unit") +public class ReadCharBuffer { + + private static final int BUFFER_SIZE = 8 + 8192 + 2; + + @DataProvider(name = "buffers") + public Object[][] createBuffers() { + // test both on-heap and off-heap buffers as they make use different code paths + return new Object[][]{ + new Object[]{CharBuffer.allocate(BUFFER_SIZE)}, + new Object[]{ByteBuffer.allocateDirect(BUFFER_SIZE * 2).asCharBuffer()} + }; + } + + @Test(dataProvider = "buffers") + public void read(CharBuffer buffer) throws IOException { + fillBuffer(buffer); + + StringBuilder input = new StringBuilder(BUFFER_SIZE - 2 + 1); + input.append("ABCDEF"); + for (int i = 0; i < 8192; i++) { + input.append('y'); + } + input.append("GH"); + + try (Reader reader = new UnoptimizedStringReader(input.toString())) { + // put only between position and limit in the target buffer + int limit = 1 + 6; + buffer.limit(limit); + buffer.position(1); + assertEquals(reader.read(buffer), 6); + assertEquals(buffer.position(), limit); + assertEquals(buffer.limit(), limit); + + // read the full temporary buffer + // and then accurately reduce the next #read call + limit = 8 + 8192 + 1; + buffer.limit(8 + 8192 + 1); + buffer.position(8); + assertEquals(reader.read(buffer), 8192 + 1); + assertEquals(buffer.position(), limit); + assertEquals(buffer.limit(), limit); + + assertEquals(reader.read(), 'H'); + assertEquals(reader.read(), -1); + } + + buffer.clear(); + StringBuilder expected = new StringBuilder(BUFFER_SIZE); + expected.append("xABCDEFx"); + for (int i = 0; i < 8192; i++) { + expected.append('y'); + } + expected.append("Gx"); + assertEquals(buffer.toString(), expected.toString()); + } + + private void fillBuffer(CharBuffer buffer) { + char[] filler = new char[buffer.remaining()]; + Arrays.fill(filler, 'x'); + buffer.put(filler); + buffer.clear(); + } + + /** + * Unoptimized version of StringReader in case StringReader overrides + * #read(CharBuffer) + */ + static final class UnoptimizedStringReader extends Reader { + + private String str; + private int next = 0; + + UnoptimizedStringReader(String s) { + this.str = s; + } + + @Override + public int read() throws IOException { + synchronized (lock) { + if (next >= str.length()) + return -1; + return str.charAt(next++); + } + } + + @Override + public int read(char cbuf[], int off, int len) throws IOException { + synchronized (lock) { + Objects.checkFromIndexSize(off, len, cbuf.length); + if (next >= str.length()) + return -1; + int n = Math.min(str.length() - next, len); + str.getChars(next, next + n, cbuf, off); + next += n; + return n; + } + } + + @Override + public void close() throws IOException { + + } + } + +}