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