--- old/src/java.base/share/classes/java/lang/Readable.java 2017-11-13 23:14:28.000000000 +0100 +++ new/src/java.base/share/classes/java/lang/Readable.java 2017-11-13 23:14:27.000000000 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2017, 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 @@ -26,6 +26,8 @@ package java.lang; import java.io.IOException; +import java.nio.CharBuffer; +import java.util.Objects; /** * A {@code Readable} is a source of characters. Characters from @@ -50,4 +52,68 @@ * @throws java.nio.ReadOnlyBufferException if cb is a read only buffer */ public int read(java.nio.CharBuffer cb) throws IOException; + + /** + * Reads all characters from this source and appends them to a destination + * in the order in which they are read. On return, the source of characters + * will be at its end. + *

+ * This method may block indefinitely while reading from the source or + * writing to the destination. If the source or destination is + * {@link AutoCloseable closeable}, then the behavior when either is + * asynchronously closed, or the thread is interrupted during the + * transfer, is highly implementation-dependent and hence unspecified. + *

+ * If an I/O error occurs during the operation, then not all characters + * might have been transferred and the source or destination could be + * left in an inconsistent state. The caller of this method should therefore + * ensure in such a case that measures are taken to release any resources + * held by the source and destination. + *

+ * Depending on which class implements the appendable, there may be a limit + * of data that can written to which in turn could lead to an exception. + * + * @implSpec + * The default implementation invokes the read method to read all characters + * from this source and invokes the {@link Appendable#append(CharSequence, int, int)} + * method to write all characters to the appendable. + * + * The default implementation behaves as if: + *

{@code
+     *     long transferred = 0;
+     *     CharBuffer buffer = CharBuffer.allocate(8192);
+     *     int read;
+     *     while ((read = this.read(buffer)) >= 0) {
+     *         buffer.rewind();
+     *         out.append(buffer, 0, read);
+     *         transferred += read;
+     *     }
+     *     return transferred;
+     * }
+ * + * @implNote + * The default implementation should usually be overridden in cases where + * the implementer is already a {@link CharSequence} or its data is already + * available in the internal representation to avoid the extra overhead of + * a buffer in order to transfer its data to the destination. + * + * @param out the appendable, non-null + * @return the number of characters transferred + * @throws IOException if an I/O error occurs when reading or writing + * @throws NullPointerException if {@code out} is {@code null} + * + * @since 10 + */ + public default long transferTo(Appendable out) throws IOException { + Objects.requireNonNull(out, "out"); + long transferred = 0; + CharBuffer buffer = CharBuffer.allocate(8192); + int read; + while ((read = this.read(buffer)) >= 0) { + buffer.rewind(); + out.append(buffer, 0, read); + transferred += read; + } + return transferred; + } } --- old/src/java.base/share/classes/java/nio/X-Buffer.java.template 2017-11-13 23:14:29.000000000 +0100 +++ new/src/java.base/share/classes/java/nio/X-Buffer.java.template 2017-11-13 23:14:28.000000000 +0100 @@ -29,6 +29,7 @@ #if[char] import java.io.IOException; +import java.util.Objects; #end[char] #if[streamableType] import java.util.Spliterator; @@ -1548,6 +1549,42 @@ return put($x$); } + /** + * {@inheritDoc} + * + * @implSpec + * The implementation is using two strategies of transferring data from the + * actual source to the given destination. If the given {@link Appendable} + * is a {@link CharBuffer} the transfer uses the {@code put(CharBuffer)} + * method to transfer it's data if possible. For the other cases source + * is passed into the {@link Appendable#append(CharSequence, int, int)} + * method along with the current position and data length. + * + * @param out the appendable, non-null + * @return the number of characters transferred + * @throws IOException if an I/O error occurs when reading or writing + * @throws NullPointerException if {@code out} is {@code null} + * @throws BufferOverflowException if there is insufficient space in out + * buffer for the remaining chars in the source buffer + * @throws IllegalArgumentException if out is this buffer + * @throws ReadOnlyBufferException if out is a read-only buffer + * + * @since 10 + */ + @Override + public long transferTo(Appendable out) throws IOException { + Objects.requireNonNull(out, "out"); + if (this == out) { + throw new IllegalArgumentException("Illegal transfer of a buffer to itself"); + } + int length = remaining(); + if (out instanceof CharBuffer) { + ((CharBuffer)out).put(this); + } else { + out.append(this, position(), length); + } + return length; + } #end[char] --- /dev/null 2017-11-13 23:14:30.000000000 +0100 +++ new/test/jdk/java/lang/Readable/TransferTo.java 2017-11-13 23:14:29.000000000 +0100 @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2017, 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.io.*; +import java.util.Arrays; +import java.util.Random; + +import jdk.test.lib.RandomFactory; + +import static java.lang.String.format; + +/* + * @test + * @bug 8067661 + * @summary tests whether java.lang.Readable.transferTo default implementation + * conforms to the contract defined in the javadoc + * @library /test/lib + * @build jdk.test.lib.RandomFactory + * @run main TransferTo + * @key randomness + */ +public class TransferTo { + + private static Random generator = RandomFactory.getRandom(); + + public static void main(String[] args) throws IOException { + ifOutIsNullThenNpeIsThrown(); + ifExceptionInInputNeitherSideIsClosed(); + ifExceptionInOutputNeitherSideIsClosed(); + onReturnNeitherSideIsClosed(); + onReturnInputIsAtEnd(); + contents(); + } + + private static void ifOutIsNullThenNpeIsThrown() throws IOException { + try (Reader in = input()) { + assertThrowsNPE(() -> in.transferTo(null), "out"); + } + + try (Reader in = input((char) 1)) { + assertThrowsNPE(() -> in.transferTo(null), "out"); + } + + try (Reader in = input((char) 1, (char) 2)) { + assertThrowsNPE(() -> in.transferTo(null), "out"); + } + + Reader in = null; + try { + Reader fin = in = new ThrowingReader(); + // null check should precede everything else: + // Reader shouldn't be touched if Writer is null + assertThrowsNPE(() -> fin.transferTo(null), "out"); + } finally { + if (in != null) + try { + in.close(); + } catch (IOException ignored) { } + } + } + + private static void ifExceptionInInputNeitherSideIsClosed() + throws IOException { + transferToThenCheckIfAnyClosed(input(0, new char[]{1, 2, 3}), output()); + transferToThenCheckIfAnyClosed(input(1, new char[]{1, 2, 3}), output()); + transferToThenCheckIfAnyClosed(input(2, new char[]{1, 2, 3}), output()); + } + + private static void ifExceptionInOutputNeitherSideIsClosed() + throws IOException { + transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(0)); + transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(1)); + transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(2)); + } + + private static void transferToThenCheckIfAnyClosed(Reader input, + Writer output) + throws IOException { + try (CloseLoggingReader in = new CloseLoggingReader(input); + CloseLoggingWriter out = + new CloseLoggingWriter(output)) { + boolean thrown = false; + try { + in.transferTo(out); + } catch (IOException ignored) { + thrown = true; + } + if (!thrown) + throw new AssertionError(); + + if (in.wasClosed() || out.wasClosed()) { + throw new AssertionError(); + } + } + } + + private static void onReturnNeitherSideIsClosed() + throws IOException { + try (CloseLoggingReader in = + new CloseLoggingReader(input(new char[]{1, 2, 3})); + CloseLoggingWriter out = + new CloseLoggingWriter(output())) { + + in.transferTo(out); + + if (in.wasClosed() || out.wasClosed()) { + throw new AssertionError(); + } + } + } + + private static void onReturnInputIsAtEnd() throws IOException { + try (Reader in = input(new char[]{1, 2, 3}); + Writer out = output()) { + + in.transferTo(out); + + if (in.read() != -1) { + throw new AssertionError(); + } + } + } + + private static void contents() throws IOException { + checkTransferredContents(new char[0]); + checkTransferredContents(createRandomChars(1024, 4096)); + // to span through several batches + checkTransferredContents(createRandomChars(16384, 16384)); + } + + private static void checkTransferredContents(char[] chars) + throws IOException { + try (Reader in = input(chars); + StringWriter out = new StringWriter()) { + in.transferTo(out); + + char[] outChars = out.toString().toCharArray(); + if (!Arrays.equals(chars, outChars)) { + throw new AssertionError( + format("chars.length=%s, outChars.length=%s", + chars.length, outChars.length)); + } + } + } + + private static char[] createRandomChars(int min, int maxRandomAdditive) { + char[] chars = new char[min + generator.nextInt(maxRandomAdditive)]; + for (int index=0; index= chars.length) + return -1; + return chars[pos++]; + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int c = read(); + if (c == -1) { + return -1; + } + cbuf[off] = (char)c; + + int i = 1; + for (; i < len ; i++) { + c = read(); + if (c == -1) { + break; + } + cbuf[off + i] = (char)c; + } + return i; + } + + @Override + public void close() throws IOException { + } + }; + } + + private static class ThrowingReader extends Reader { + + boolean closed; + + @Override + public int read(char[] b, int off, int len) throws IOException { + throw new IOException(); + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + throw new IOException(); + } + } + @Override + public int read() throws IOException { + throw new IOException(); + } + } + + private static class CloseLoggingReader extends FilterReader { + + boolean closed; + + CloseLoggingReader(Reader in) { + super(in); + } + + @Override + public void close() throws IOException { + closed = true; + super.close(); + } + + boolean wasClosed() { + return closed; + } + } + + private static class CloseLoggingWriter extends FilterWriter { + + boolean closed; + + CloseLoggingWriter(Writer out) { + super(out); + } + + @Override + public void close() throws IOException { + closed = true; + super.close(); + } + + boolean wasClosed() { + return closed; + } + } + + public interface Thrower { + public void run() throws Throwable; + } + + public static void assertThrowsNPE(Thrower thrower, String message) { + assertThrows(thrower, NullPointerException.class, message); + } + + public static void assertThrows(Thrower thrower, + Class throwable, + String message) { + Throwable thrown; + try { + thrower.run(); + thrown = null; + } catch (Throwable caught) { + thrown = caught; + } + + if (!throwable.isInstance(thrown)) { + String caught = thrown == null ? + "nothing" : thrown.getClass().getCanonicalName(); + throw new AssertionError( + format("Expected to catch %s, but caught %s", + throwable, caught), thrown); + } + + if (thrown != null && !message.equals(thrown.getMessage())) { + throw new AssertionError( + format("Expected exception message to be '%s', but it's '%s'", + message, thrown.getMessage())); + } + } +} --- /dev/null 2017-11-13 23:14:31.000000000 +0100 +++ new/test/jdk/java/nio/Buffer/TransferTo.java 2017-11-13 23:14:30.000000000 +0100 @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017, 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 source 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 source 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 Franklsource 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.io.IOException; +import java.io.StringWriter; +import java.nio.BufferOverflowException; +import java.nio.CharBuffer; +import java.nio.ReadOnlyBufferException; +import java.util.Objects; +import java.util.function.Supplier; + +import static java.lang.String.format; + +/* + * @test + * @bug 8067661 + * @summary tests whether java.nio.CharBuffer.transferTo conforms to its + * contract defined source the javadoc + */ +public class TransferTo { + + public static void main(String[] args) throws IOException { + ifOutIsNullThenNpeIsThrown(); + ifOutIsSelfThenIllegalAccessExceptionIsThrown(); + targetIsAppendable(); + targetIsCharBuffer(); + targetIsTooSmallCharBuffer(); + targetIsReadOnlyCharBuffer(); + } + + private static void ifOutIsNullThenNpeIsThrown() { + CharBuffer source = createSource(); + assertThrowsNPE(() -> source.transferTo(null), "out"); + } + + private static void ifOutIsSelfThenIllegalAccessExceptionIsThrown() { + CharBuffer source = createSource(); + assertThrows(() -> source.transferTo(source), IllegalArgumentException.class, "Illegal transfer of a buffer to itself"); + } + + private static void targetIsAppendable() throws IOException { + CharBuffer source = createSource(); + StringWriter target = new StringWriter(); + source.transferTo(target); + assertEquals(source, () -> target.toString()); + } + + private static void targetIsCharBuffer() throws IOException { + CharBuffer source = createSource(); + CharBuffer target = CharBuffer.allocate(source.limit()); + source.transferTo(target); + assertEquals(source, () -> { + target.rewind(); + return target.toString(); + }); + } + + private static void targetIsTooSmallCharBuffer() { + CharBuffer source = createSource(); + CharBuffer target = CharBuffer.allocate(source.limit() - 1); + assertThrows(() -> source.transferTo(target), BufferOverflowException.class, null); + } + + private static void targetIsReadOnlyCharBuffer() { + CharBuffer source = createSource(); + CharBuffer target = CharBuffer.allocate(source.limit()).asReadOnlyBuffer(); + assertThrows(() -> source.transferTo(target), ReadOnlyBufferException.class, null); + } + + private static CharBuffer createSource() { + CharBuffer buffer = CharBuffer.wrap("mary had a little lamb"); + buffer.rewind(); + return buffer; + } + + private static void assertEquals(CharBuffer expected, Supplier testValueSupplier) { + expected.rewind(); + String correct = expected.toString(); + String tested = testValueSupplier.get(); + if (!correct.equals(tested)) { + throw new AssertionError(format("Expected: '%s', but got '%s'", correct, tested)); + } + } + + public interface Thrower { + public void run() throws Throwable; + } + + public static void assertThrowsNPE(Thrower thrower, String message) { + assertThrows(thrower, NullPointerException.class, message); + } + + public static void assertThrows(Thrower thrower, + Class throwable, + String message) { + Throwable thrown; + try { + thrower.run(); + thrown = null; + } catch (Throwable caught) { + thrown = caught; + } + + if (!throwable.isInstance(thrown)) { + String caught = thrown == null ? + "nothing" : thrown.getClass().getCanonicalName(); + throw new AssertionError( + format("Expected to catch %s, but caught %s", + throwable, caught), thrown); + } + + if (thrown != null && !Objects.equals(message, thrown.getMessage())) { + throw new AssertionError( + format("Expected exception message to be '%s', but it's '%s'", + message, thrown.getMessage())); + } + } +}