--- 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