1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.*;
  25 import java.util.Arrays;
  26 import java.util.Random;
  27 
  28 import jdk.test.lib.RandomFactory;
  29 
  30 import static java.lang.String.format;
  31 
  32 /*
  33  * @test
  34  * @bug 8066870
  35  * @summary tests whether java.lang.Readable.transferTo default implementation
  36  *          conforms to the contract defined in the javadoc
  37  * @library /test/lib
  38  * @build jdk.test.lib.RandomFactory
  39  * @run main TransferTo
  40  * @key randomness
  41  * @author Patrick Reinhart
  42  */
  43 public class TransferTo {
  44 
  45     private static Random generator = RandomFactory.getRandom();
  46 
  47     public static void main(String[] args) throws IOException {
  48         ifOutIsNullThenNpeIsThrown();
  49         ifExceptionInInputNeitherSideIsClosed();
  50         ifExceptionInOutputNeitherSideIsClosed();
  51         onReturnNeitherSideIsClosed();
  52         onReturnInputIsAtEnd();
  53         contents();
  54     }
  55 
  56     private static void ifOutIsNullThenNpeIsThrown() throws IOException {
  57         try (Reader in = input()) {
  58             assertThrowsNPE(() -> in.transferTo(null), "out");
  59         }
  60 
  61         try (Reader in = input((char) 1)) {
  62             assertThrowsNPE(() -> in.transferTo(null), "out");
  63         }
  64 
  65         try (Reader in = input((char) 1, (char) 2)) {
  66             assertThrowsNPE(() -> in.transferTo(null), "out");
  67         }
  68 
  69         Reader in = null;
  70         try {
  71             Reader fin = in = new ThrowingReader();
  72             // null check should precede everything else:
  73             // Reader shouldn't be touched if Writer is null
  74             assertThrowsNPE(() -> fin.transferTo(null), "out");
  75         } finally {
  76             if (in != null)
  77                 try {
  78                     in.close();
  79                 } catch (IOException ignored) { }
  80         }
  81     }
  82 
  83     private static void ifExceptionInInputNeitherSideIsClosed()
  84             throws IOException {
  85         transferToThenCheckIfAnyClosed(input(0, new char[]{1, 2, 3}), output());
  86         transferToThenCheckIfAnyClosed(input(1, new char[]{1, 2, 3}), output());
  87         transferToThenCheckIfAnyClosed(input(2, new char[]{1, 2, 3}), output());
  88     }
  89 
  90     private static void ifExceptionInOutputNeitherSideIsClosed()
  91             throws IOException {
  92         transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(0));
  93         transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(1));
  94         transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(2));
  95     }
  96 
  97     private static void transferToThenCheckIfAnyClosed(Reader input,
  98                                                        Writer output)
  99             throws IOException {
 100         try (CloseLoggingReader in = new CloseLoggingReader(input);
 101              CloseLoggingWriter out =
 102                      new CloseLoggingWriter(output)) {
 103             boolean thrown = false;
 104             try {
 105                 in.transferTo(out);
 106             } catch (IOException ignored) {
 107                 thrown = true;
 108             }
 109             if (!thrown)
 110                 throw new AssertionError();
 111 
 112             if (in.wasClosed() || out.wasClosed()) {
 113                 throw new AssertionError();
 114             }
 115         }
 116     }
 117 
 118     private static void onReturnNeitherSideIsClosed()
 119             throws IOException {
 120         try (CloseLoggingReader in =
 121                      new CloseLoggingReader(input(new char[]{1, 2, 3}));
 122              CloseLoggingWriter out =
 123                      new CloseLoggingWriter(output())) {
 124 
 125             in.transferTo(out);
 126 
 127             if (in.wasClosed() || out.wasClosed()) {
 128                 throw new AssertionError();
 129             }
 130         }
 131     }
 132 
 133     private static void onReturnInputIsAtEnd() throws IOException {
 134         try (Reader in = input(new char[]{1, 2, 3});
 135              Writer out = output()) {
 136 
 137             in.transferTo(out);
 138 
 139             if (in.read() != -1) {
 140                 throw new AssertionError();
 141             }
 142         }
 143     }
 144 
 145     private static void contents() throws IOException {
 146         checkTransferredContents(new char[0]);
 147         checkTransferredContents(createRandomChars(1024, 4096));
 148         // to span through several batches
 149         checkTransferredContents(createRandomChars(16384, 16384));
 150     }
 151 
 152     private static void checkTransferredContents(char[] chars)
 153             throws IOException {
 154         try (Reader in = input(chars);
 155              StringWriter out = new StringWriter()) {
 156             in.transferTo(out);
 157 
 158             char[] outChars = out.toString().toCharArray();
 159             if (!Arrays.equals(chars, outChars)) {
 160                 throw new AssertionError(
 161                         format("chars.length=%s, outChars.length=%s",
 162                                 chars.length, outChars.length));
 163             }
 164         }
 165     }
 166 
 167     private static char[] createRandomChars(int min, int maxRandomAdditive) {
 168         char[] chars = new char[min + generator.nextInt(maxRandomAdditive)];
 169         for (int index=0; index<chars.length; index++) {
 170             chars[index] = (char)generator.nextInt();
 171         }
 172         return chars;
 173     }
 174 
 175     private static Writer output() {
 176         return output(-1);
 177     }
 178 
 179     private static Writer output(int exceptionPosition) {
 180         return new Writer() {
 181 
 182             int pos;
 183 
 184             @Override
 185             public void write(int b) throws IOException {
 186                 if (pos++ == exceptionPosition)
 187                     throw new IOException();
 188             }
 189 
 190             @Override
 191             public void write(char[] chars, int off, int len) throws IOException {
 192                 for (int i=0; i<len; i++) {
 193                     write(chars[off + i]);
 194                 }
 195             }
 196 
 197             @Override
 198             public Writer append(CharSequence csq, int start, int end) throws IOException {
 199                 for (int i = start; i < end; i++) {
 200                     write(csq.charAt(i));
 201                 }
 202                 return this;
 203             }
 204 
 205             @Override
 206             public void flush() throws IOException {
 207             }
 208 
 209             @Override
 210             public void close() throws IOException {
 211             }
 212         };
 213     }
 214 
 215     private static Reader input(char... chars) {
 216         return input(-1, chars);
 217     }
 218 
 219     private static Reader input(int exceptionPosition, char... chars) {
 220         return new Reader() {
 221 
 222             int pos;
 223 
 224             @Override
 225             public int read() throws IOException {
 226                 if (pos == exceptionPosition) {
 227                     throw new IOException();
 228                 }
 229 
 230                 if (pos >= chars.length)
 231                     return -1;
 232                 return chars[pos++];
 233             }
 234 
 235             @Override
 236             public int read(char[] cbuf, int off, int len) throws IOException {
 237                 int c = read();
 238                 if (c == -1) {
 239                     return -1;
 240                 }
 241                 cbuf[off] = (char)c;
 242 
 243                 int i = 1;
 244                 for (; i < len ; i++) {
 245                     c = read();
 246                     if (c == -1) {
 247                         break;
 248                     }
 249                     cbuf[off + i] = (char)c;
 250                 }
 251                 return i;
 252             }
 253 
 254             @Override
 255             public void close() throws IOException {
 256             }
 257         };
 258     }
 259 
 260     private static class ThrowingReader extends Reader {
 261 
 262         boolean closed;
 263 
 264         @Override
 265         public int read(char[] b, int off, int len) throws IOException {
 266             throw new IOException();
 267         }
 268 
 269         @Override
 270         public void close() throws IOException {
 271             if (!closed) {
 272                 closed = true;
 273                 throw new IOException();
 274             }
 275         }
 276         @Override
 277         public int read() throws IOException {
 278             throw new IOException();
 279         }
 280     }
 281 
 282     private static class CloseLoggingReader extends FilterReader {
 283 
 284         boolean closed;
 285 
 286         CloseLoggingReader(Reader in) {
 287             super(in);
 288         }
 289 
 290         @Override
 291         public void close() throws IOException {
 292             closed = true;
 293             super.close();
 294         }
 295 
 296         boolean wasClosed() {
 297             return closed;
 298         }
 299     }
 300 
 301     private static class CloseLoggingWriter extends FilterWriter {
 302 
 303         boolean closed;
 304 
 305         CloseLoggingWriter(Writer out) {
 306             super(out);
 307         }
 308 
 309         @Override
 310         public void close() throws IOException {
 311             closed = true;
 312             super.close();
 313         }
 314 
 315         boolean wasClosed() {
 316             return closed;
 317         }
 318     }
 319 
 320     public interface Thrower {
 321         public void run() throws Throwable;
 322     }
 323 
 324     public static void assertThrowsNPE(Thrower thrower, String message) {
 325         assertThrows(thrower, NullPointerException.class, message);
 326     }
 327 
 328     public static <T extends Throwable> void assertThrows(Thrower thrower,
 329                                                           Class<T> throwable,
 330                                                           String message) {
 331         Throwable thrown;
 332         try {
 333             thrower.run();
 334             thrown = null;
 335         } catch (Throwable caught) {
 336             thrown = caught;
 337         }
 338 
 339         if (!throwable.isInstance(thrown)) {
 340             String caught = thrown == null ?
 341                     "nothing" : thrown.getClass().getCanonicalName();
 342             throw new AssertionError(
 343                     format("Expected to catch %s, but caught %s",
 344                             throwable, caught), thrown);
 345         }
 346 
 347         if (thrown != null && !message.equals(thrown.getMessage())) {
 348             throw new AssertionError(
 349                     format("Expected exception message to be '%s', but it's '%s'",
 350                             message, thrown.getMessage()));
 351         }
 352     }
 353 }