1 /*
   2  * Copyright (c) 2014, 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.ByteArrayOutputStream;
  25 import java.io.FilterInputStream;
  26 import java.io.FilterOutputStream;
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.io.OutputStream;
  30 import java.util.Arrays;
  31 import java.util.Random;
  32 
  33 import static java.lang.String.format;
  34 
  35 /*
  36  * @test
  37  * @bug 8066867
  38  * @summary tests whether java.io.InputStream.transferTo conforms to its
  39  *          contract defined in the javadoc
  40  * @key randomness
  41  */
  42 public class TransferTo {
  43 
  44     public static void main(String[] args) throws IOException {
  45         ifOutIsNullThenNpeIsThrown();
  46         ifExceptionInInputNeitherStreamIsClosed();
  47         ifExceptionInOutputNeitherStreamIsClosed();
  48         onReturnNeitherStreamIsClosed();
  49         onReturnInputIsAtEnd();
  50         contents();
  51     }
  52 
  53     private static void ifOutIsNullThenNpeIsThrown() throws IOException {
  54         try (InputStream in = input()) {
  55             assertThrowsNPE(() -> in.transferTo(null), "out");
  56         }
  57 
  58         try (InputStream in = input((byte) 1)) {
  59             assertThrowsNPE(() -> in.transferTo(null), "out");
  60         }
  61 
  62         try (InputStream in = input((byte) 1, (byte) 2)) {
  63             assertThrowsNPE(() -> in.transferTo(null), "out");
  64         }
  65 
  66         InputStream in = null;
  67         try {
  68             InputStream fin = in = new ThrowingInputStream();
  69             // null check should precede everything else:
  70             // InputStream shouldn't be touched if OutputStream is null
  71             assertThrowsNPE(() -> fin.transferTo(null), "out");
  72         } finally {
  73             if (in != null)
  74                 try {
  75                     in.close();
  76                 } catch (IOException ignored) { }
  77         }
  78     }
  79 
  80     private static void ifExceptionInInputNeitherStreamIsClosed()
  81             throws IOException {
  82         transferToThenCheckIfAnyClosed(input(0, new byte[]{1, 2, 3}), output());
  83         transferToThenCheckIfAnyClosed(input(1, new byte[]{1, 2, 3}), output());
  84         transferToThenCheckIfAnyClosed(input(2, new byte[]{1, 2, 3}), output());
  85     }
  86 
  87     private static void ifExceptionInOutputNeitherStreamIsClosed()
  88             throws IOException {
  89         transferToThenCheckIfAnyClosed(input(new byte[]{1, 2, 3}), output(0));
  90         transferToThenCheckIfAnyClosed(input(new byte[]{1, 2, 3}), output(1));
  91         transferToThenCheckIfAnyClosed(input(new byte[]{1, 2, 3}), output(2));
  92     }
  93 
  94     private static void transferToThenCheckIfAnyClosed(InputStream input,
  95                                                        OutputStream output)
  96             throws IOException {
  97         try (CloseLoggingInputStream in = new CloseLoggingInputStream(input);
  98              CloseLoggingOutputStream out =
  99                      new CloseLoggingOutputStream(output)) {
 100             boolean thrown = false;
 101             try {
 102                 in.transferTo(out);
 103             } catch (IOException ignored) {
 104                 thrown = true;
 105             }
 106             if (!thrown)
 107                 throw new AssertionError();
 108 
 109             if (in.wasClosed() || out.wasClosed()) {
 110                 throw new AssertionError();
 111             }
 112         }
 113     }
 114 
 115     private static void onReturnNeitherStreamIsClosed()
 116             throws IOException {
 117         try (CloseLoggingInputStream in =
 118                      new CloseLoggingInputStream(input(new byte[]{1, 2, 3}));
 119              CloseLoggingOutputStream out =
 120                      new CloseLoggingOutputStream(output())) {
 121 
 122             in.transferTo(out);
 123 
 124             if (in.wasClosed() || out.wasClosed()) {
 125                 throw new AssertionError();
 126             }
 127         }
 128     }
 129 
 130     private static void onReturnInputIsAtEnd() throws IOException {
 131         try (InputStream in = input(new byte[]{1, 2, 3});
 132              OutputStream out = output()) {
 133 
 134             in.transferTo(out);
 135 
 136             if (in.read() != -1) {
 137                 throw new AssertionError();
 138             }
 139         }
 140     }
 141 
 142     private static void contents() throws IOException {
 143         checkTransferredContents(new byte[0]);
 144         checkTransferredContents(createRandomBytes(1024, 4096));
 145         // to span through several batches
 146         checkTransferredContents(createRandomBytes(16384, 16384));
 147     }
 148 
 149     private static void checkTransferredContents(byte[] bytes)
 150             throws IOException {
 151         try (InputStream in = input(bytes);
 152              ByteArrayOutputStream out = new ByteArrayOutputStream()) {
 153             in.transferTo(out);
 154 
 155             byte[] outBytes = out.toByteArray();
 156             if (!Arrays.equals(bytes, outBytes)) {
 157                 throw new AssertionError(
 158                         format("bytes.length=%s, outBytes.length=%s",
 159                                 bytes.length, outBytes.length));
 160             }
 161         }
 162     }
 163 
 164     private static byte[] createRandomBytes(int min, int maxRandomAdditive) {
 165         Random rnd = new Random();
 166         byte[] bytes = new byte[min + rnd.nextInt(maxRandomAdditive)];
 167         rnd.nextBytes(bytes);
 168         return bytes;
 169     }
 170 
 171     private static OutputStream output() {
 172         return output(-1);
 173     }
 174 
 175     private static OutputStream output(int exceptionPosition) {
 176         return new OutputStream() {
 177 
 178             int pos;
 179 
 180             @Override
 181             public void write(int b) throws IOException {
 182                 if (pos++ == exceptionPosition)
 183                     throw new IOException();
 184             }
 185         };
 186     }
 187 
 188     private static InputStream input(byte... bytes) {
 189         return input(-1, bytes);
 190     }
 191 
 192     private static InputStream input(int exceptionPosition, byte... bytes) {
 193         return new InputStream() {
 194 
 195             int pos;
 196 
 197             @Override
 198             public int read() throws IOException {
 199                 if (pos == exceptionPosition) {
 200                     // because of the pesky IOException swallowing in
 201                     // java.io.InputStream.read(byte[], int, int)
 202                     // pos++;
 203                     throw new IOException();
 204                 }
 205 
 206                 if (pos >= bytes.length)
 207                     return -1;
 208                 return bytes[pos++] & 0xff;
 209             }
 210         };
 211     }
 212 
 213     private static class ThrowingInputStream extends InputStream {
 214 
 215         boolean closed;
 216 
 217         @Override
 218         public int read(byte[] b) throws IOException {
 219             throw new IOException();
 220         }
 221 
 222         @Override
 223         public int read(byte[] b, int off, int len) throws IOException {
 224             throw new IOException();
 225         }
 226 
 227         @Override
 228         public long skip(long n) throws IOException {
 229             throw new IOException();
 230         }
 231 
 232         @Override
 233         public int available() throws IOException {
 234             throw new IOException();
 235         }
 236 
 237         @Override
 238         public void close() throws IOException {
 239             if (!closed) {
 240                 closed = true;
 241                 throw new IOException();
 242             }
 243         }
 244 
 245         @Override
 246         public void reset() throws IOException {
 247             throw new IOException();
 248         }
 249 
 250         @Override
 251         public int read() throws IOException {
 252             throw new IOException();
 253         }
 254     }
 255 
 256     private static class CloseLoggingInputStream extends FilterInputStream {
 257 
 258         boolean closed;
 259 
 260         CloseLoggingInputStream(InputStream in) {
 261             super(in);
 262         }
 263 
 264         @Override
 265         public void close() throws IOException {
 266             closed = true;
 267             super.close();
 268         }
 269 
 270         boolean wasClosed() {
 271             return closed;
 272         }
 273     }
 274 
 275     private static class CloseLoggingOutputStream extends FilterOutputStream {
 276 
 277         boolean closed;
 278 
 279         CloseLoggingOutputStream(OutputStream out) {
 280             super(out);
 281         }
 282 
 283         @Override
 284         public void close() throws IOException {
 285             closed = true;
 286             super.close();
 287         }
 288 
 289         boolean wasClosed() {
 290             return closed;
 291         }
 292     }
 293 
 294     public interface Thrower {
 295         public void run() throws Throwable;
 296     }
 297 
 298     public static void assertThrowsNPE(Thrower thrower, String message) {
 299         assertThrows(thrower, NullPointerException.class, message);
 300     }
 301 
 302     public static <T extends Throwable> void assertThrows(Thrower thrower,
 303                                                           Class<T> throwable,
 304                                                           String message) {
 305         Throwable thrown;
 306         try {
 307             thrower.run();
 308             thrown = null;
 309         } catch (Throwable caught) {
 310             thrown = caught;
 311         }
 312 
 313         if (!throwable.isInstance(thrown)) {
 314             String caught = thrown == null ?
 315                     "nothing" : thrown.getClass().getCanonicalName();
 316             throw new AssertionError(
 317                     format("Expected to catch %s, but caught %s",
 318                             throwable, caught), thrown);
 319         }
 320 
 321         if (thrown != null && !message.equals(thrown.getMessage())) {
 322             throw new AssertionError(
 323                     format("Expected exception message to be '%s', but it's '%s'",
 324                             message, thrown.getMessage()));
 325         }
 326     }
 327 }