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 8067661
  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  */
  42 public class TransferTo {
  43 
  44     private static Random generator = RandomFactory.getRandom();
  45 
  46     public static void main(String[] args) throws IOException {
  47         ifOutIsNullThenNpeIsThrown();
  48         ifExceptionInInputNeitherSideIsClosed();
  49         ifExceptionInOutputNeitherSideIsClosed();
  50         onReturnNeitherSideIsClosed();
  51         onReturnInputIsAtEnd();
  52         contents();
  53     }
  54 
  55     private static void ifOutIsNullThenNpeIsThrown() throws IOException {
  56         try (Reader in = input()) {
  57             assertThrowsNPE(() -> in.transferTo(null), "out");
  58         }
  59 
  60         try (Reader in = input((char) 1)) {
  61             assertThrowsNPE(() -> in.transferTo(null), "out");
  62         }
  63 
  64         try (Reader in = input((char) 1, (char) 2)) {
  65             assertThrowsNPE(() -> in.transferTo(null), "out");
  66         }
  67 
  68         Reader in = null;
  69         try {
  70             Reader fin = in = new ThrowingReader();
  71             // null check should precede everything else:
  72             // Reader shouldn't be touched if Writer is null
  73             assertThrowsNPE(() -> fin.transferTo(null), "out");
  74         } finally {
  75             if (in != null)
  76                 try {
  77                     in.close();
  78                 } catch (IOException ignored) { }
  79         }
  80     }
  81 
  82     private static void ifExceptionInInputNeitherSideIsClosed()
  83             throws IOException {
  84         transferToThenCheckIfAnyClosed(input(0, new char[]{1, 2, 3}), output());
  85         transferToThenCheckIfAnyClosed(input(1, new char[]{1, 2, 3}), output());
  86         transferToThenCheckIfAnyClosed(input(2, new char[]{1, 2, 3}), output());
  87     }
  88 
  89     private static void ifExceptionInOutputNeitherSideIsClosed()
  90             throws IOException {
  91         transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(0));
  92         transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(1));
  93         transferToThenCheckIfAnyClosed(input(new char[]{1, 2, 3}), output(2));
  94     }
  95 
  96     private static void transferToThenCheckIfAnyClosed(Reader input,
  97                                                        Writer output)
  98             throws IOException {
  99         try (CloseLoggingReader in = new CloseLoggingReader(input);
 100              CloseLoggingWriter out =
 101                      new CloseLoggingWriter(output)) {
 102             boolean thrown = false;
 103             try {
 104                 in.transferTo(out);
 105             } catch (IOException ignored) {
 106                 thrown = true;
 107             }
 108             if (!thrown)
 109                 throw new AssertionError();
 110 
 111             if (in.wasClosed() || out.wasClosed()) {
 112                 throw new AssertionError();
 113             }
 114         }
 115     }
 116 
 117     private static void onReturnNeitherSideIsClosed()
 118             throws IOException {
 119         try (CloseLoggingReader in =
 120                      new CloseLoggingReader(input(new char[]{1, 2, 3}));
 121              CloseLoggingWriter out =
 122                      new CloseLoggingWriter(output())) {
 123 
 124             in.transferTo(out);
 125 
 126             if (in.wasClosed() || out.wasClosed()) {
 127                 throw new AssertionError();
 128             }
 129         }
 130     }
 131 
 132     private static void onReturnInputIsAtEnd() throws IOException {
 133         try (Reader in = input(new char[]{1, 2, 3});
 134              Writer out = output()) {
 135 
 136             in.transferTo(out);
 137 
 138             if (in.read() != -1) {
 139                 throw new AssertionError();
 140             }
 141         }
 142     }
 143 
 144     private static void contents() throws IOException {
 145         checkTransferredContents(new char[0]);
 146         checkTransferredContents(createRandomChars(1024, 4096));
 147         // to span through several batches
 148         checkTransferredContents(createRandomChars(16384, 16384));
 149     }
 150 
 151     private static void checkTransferredContents(char[] chars)
 152             throws IOException {
 153         try (Reader in = input(chars);
 154              StringWriter out = new StringWriter()) {
 155             in.transferTo(out);
 156 
 157             char[] outChars = out.toString().toCharArray();
 158             if (!Arrays.equals(chars, outChars)) {
 159                 throw new AssertionError(
 160                         format("chars.length=%s, outChars.length=%s",
 161                                 chars.length, outChars.length));
 162             }
 163         }
 164     }
 165 
 166     private static char[] createRandomChars(int min, int maxRandomAdditive) {
 167         char[] chars = new char[min + generator.nextInt(maxRandomAdditive)];
 168         for (int index=0; index<chars.length; index++) {
 169             chars[index] = (char)generator.nextInt();
 170         }
 171         return chars;
 172     }
 173 
 174     private static Writer output() {
 175         return output(-1);
 176     }
 177 
 178     private static Writer output(int exceptionPosition) {
 179         return new Writer() {
 180 
 181             int pos;
 182 
 183             @Override
 184             public void write(int b) throws IOException {
 185                 if (pos++ == exceptionPosition)
 186                     throw new IOException();
 187             }
 188 
 189             @Override
 190             public void write(char[] chars, int off, int len) throws IOException {
 191                 for (int i=0; i<len; i++) {
 192                     write(chars[off + i]);
 193                 }
 194             }
 195 
 196             @Override
 197             public Writer append(CharSequence csq, int start, int end) throws IOException {
 198                 for (int i = start; i < end; i++) {
 199                     write(csq.charAt(i));
 200                 }
 201                 return this;
 202             }
 203 
 204             @Override
 205             public void flush() throws IOException {
 206             }
 207 
 208             @Override
 209             public void close() throws IOException {
 210             }
 211         };
 212     }
 213 
 214     private static Reader input(char... chars) {
 215         return input(-1, chars);
 216     }
 217 
 218     private static Reader input(int exceptionPosition, char... chars) {
 219         return new Reader() {
 220 
 221             int pos;
 222 
 223             @Override
 224             public int read() throws IOException {
 225                 if (pos == exceptionPosition) {
 226                     throw new IOException();
 227                 }
 228 
 229                 if (pos >= chars.length)
 230                     return -1;
 231                 return chars[pos++];
 232             }
 233 
 234             @Override
 235             public int read(char[] cbuf, int off, int len) throws IOException {
 236                 int c = read();
 237                 if (c == -1) {
 238                     return -1;
 239                 }
 240                 cbuf[off] = (char)c;
 241 
 242                 int i = 1;
 243                 for (; i < len ; i++) {
 244                     c = read();
 245                     if (c == -1) {
 246                         break;
 247                     }
 248                     cbuf[off + i] = (char)c;
 249                 }
 250                 return i;
 251             }
 252 
 253             @Override
 254             public void close() throws IOException {
 255             }
 256         };
 257     }
 258 
 259     private static class ThrowingReader extends Reader {
 260 
 261         boolean closed;
 262 
 263         @Override
 264         public int read(char[] b, int off, int len) throws IOException {
 265             throw new IOException();
 266         }
 267 
 268         @Override
 269         public void close() throws IOException {
 270             if (!closed) {
 271                 closed = true;
 272                 throw new IOException();
 273             }
 274         }
 275         @Override
 276         public int read() throws IOException {
 277             throw new IOException();
 278         }
 279     }
 280 
 281     private static class CloseLoggingReader extends FilterReader {
 282 
 283         boolean closed;
 284 
 285         CloseLoggingReader(Reader in) {
 286             super(in);
 287         }
 288 
 289         @Override
 290         public void close() throws IOException {
 291             closed = true;
 292             super.close();
 293         }
 294 
 295         boolean wasClosed() {
 296             return closed;
 297         }
 298     }
 299 
 300     private static class CloseLoggingWriter extends FilterWriter {
 301 
 302         boolean closed;
 303 
 304         CloseLoggingWriter(Writer out) {
 305             super(out);
 306         }
 307 
 308         @Override
 309         public void close() throws IOException {
 310             closed = true;
 311             super.close();
 312         }
 313 
 314         boolean wasClosed() {
 315             return closed;
 316         }
 317     }
 318 
 319     public interface Thrower {
 320         public void run() throws Throwable;
 321     }
 322 
 323     public static void assertThrowsNPE(Thrower thrower, String message) {
 324         assertThrows(thrower, NullPointerException.class, message);
 325     }
 326 
 327     public static <T extends Throwable> void assertThrows(Thrower thrower,
 328                                                           Class<T> throwable,
 329                                                           String message) {
 330         Throwable thrown;
 331         try {
 332             thrower.run();
 333             thrown = null;
 334         } catch (Throwable caught) {
 335             thrown = caught;
 336         }
 337 
 338         if (!throwable.isInstance(thrown)) {
 339             String caught = thrown == null ?
 340                     "nothing" : thrown.getClass().getCanonicalName();
 341             throw new AssertionError(
 342                     format("Expected to catch %s, but caught %s",
 343                             throwable, caught), thrown);
 344         }
 345 
 346         if (thrown != null && !message.equals(thrown.getMessage())) {
 347             throw new AssertionError(
 348                     format("Expected exception message to be '%s', but it's '%s'",
 349                             message, thrown.getMessage()));
 350         }
 351     }
 352 }