changeset 13788:b9c7d81405ab

8139206: Add InputStream readNBytes(int len) Reviewed-by: mbalao
author andrew
date Fri, 03 Jan 2020 01:36:35 +0000
parents 730db2faa06d
children efd68acff50d
files src/share/classes/sun/misc/IOUtils.java test/sun/misc/IOUtils/ReadNBytes.java
diffstat 2 files changed, 129 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/misc/IOUtils.java	Fri Jan 03 01:08:34 2020 +0000
+++ b/src/share/classes/sun/misc/IOUtils.java	Fri Jan 03 01:36:35 2020 +0000
@@ -93,24 +93,6 @@
     }
 
     /**
-     * Read {@code length} of bytes from {@code in}. An exception is
-     * thrown if there are not enough bytes in the stream.
-     *
-     * @param is input stream, must not be null
-     * @param length number of bytes to read, must not be negative
-     * @return bytes read
-     * @throws IOException if any IO error or a premature EOF is detected, or
-     *      if {@code length} is negative since this length is usually also
-     *      read from {@code is}.
-     */
-    public static byte[] readNBytes(InputStream is, int length) throws IOException {
-        if (length < 0) {
-            throw new IOException("length cannot be negative: " + length);
-        }
-        return readFully(is, length, true);
-    }
-
-    /**
      * Reads all remaining bytes from the input stream. This method blocks until
      * all remaining bytes have been read and end of stream is detected, or an
      * exception is thrown. This method does not close the input stream.
@@ -132,27 +114,87 @@
      * It is strongly recommended that the stream be promptly closed if an I/O
      * error occurs.
      *
+     * @implSpec
+     * This method invokes {@link #readNBytes(int)} with a length of
+     * {@link Integer#MAX_VALUE}.
+     *
      * @param is input stream, must not be null
      * @return a byte array containing the bytes read from this input stream
      * @throws IOException if an I/O error occurs
      * @throws OutOfMemoryError if an array of the required size cannot be
-     *         allocated. For example, if an array larger than {@code 2GB} would
-     *         be required to store the bytes.
+     *         allocated.
      *
      * @since 1.9
      */
     public static byte[] readAllBytes(InputStream is) throws IOException {
+        return readNBytes(is, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Reads up to a specified number of bytes from the input stream. This
+     * method blocks until the requested number of bytes have been read, end
+     * of stream is detected, or an exception is thrown. This method does not
+     * close the input stream.
+     *
+     * <p> The length of the returned array equals the number of bytes read
+     * from the stream. If {@code len} is zero, then no bytes are read and
+     * an empty byte array is returned. Otherwise, up to {@code len} bytes
+     * are read from the stream. Fewer than {@code len} bytes may be read if
+     * end of stream is encountered.
+     *
+     * <p> When this stream reaches end of stream, further invocations of this
+     * method will return an empty byte array.
+     *
+     * <p> Note that this method is intended for simple cases where it is
+     * convenient to read the specified number of bytes into a byte array. The
+     * total amount of memory allocated by this method is proportional to the
+     * number of bytes read from the stream which is bounded by {@code len}.
+     * Therefore, the method may be safely called with very large values of
+     * {@code len} provided sufficient memory is available.
+     *
+     * <p> The behavior for the case where the input stream is <i>asynchronously
+     * closed</i>, or the thread interrupted during the read, is highly input
+     * stream specific, and therefore not specified.
+     *
+     * <p> If an I/O error occurs reading from the input stream, then it may do
+     * so after some, but not all, bytes have been read. Consequently the input
+     * stream may not be at end of stream and may be in an inconsistent state.
+     * It is strongly recommended that the stream be promptly closed if an I/O
+     * error occurs.
+     *
+     * @implNote
+     * The number of bytes allocated to read data from this stream and return
+     * the result is bounded by {@code 2*(long)len}, inclusive.
+     *
+     * @param is input stream, must not be null
+     * @param len the maximum number of bytes to read
+     * @return a byte array containing the bytes read from this input stream
+     * @throws IllegalArgumentException if {@code length} is negative
+     * @throws IOException if an I/O error occurs
+     * @throws OutOfMemoryError if an array of the required size cannot be
+     *         allocated.
+     *
+     * @since 11
+     */
+    public static byte[] readNBytes(InputStream is, int len) throws IOException {
+        if (len < 0) {
+            throw new IllegalArgumentException("len < 0");
+        }
+
         List<byte[]> bufs = null;
         byte[] result = null;
         int total = 0;
+        int remaining = len;
         int n;
         do {
-            byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
+            byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)];
             int nread = 0;
 
             // read to EOF which may read more or less than buffer size
-            while ((n = is.read(buf, nread, buf.length - nread)) > 0) {
+            while ((n = is.read(buf, nread,
+                    Math.min(buf.length - nread, remaining))) > 0) {
                 nread += n;
+                remaining -= n;
             }
 
             if (nread > 0) {
@@ -170,7 +212,9 @@
                     bufs.add(buf);
                 }
             }
-        } while (n >= 0); // if the last call to read returned -1, then break
+            // if the last call to read returned -1 or the number of bytes
+            // requested have been read then break
+        } while (n >= 0 && remaining > 0);
 
         if (bufs == null) {
             if (result == null) {
@@ -182,12 +226,12 @@
 
         result = new byte[total];
         int offset = 0;
-        int remaining = total;
+        remaining = total;
         for (byte[] b : bufs) {
-            int len = Math.min(b.length, remaining);
-            System.arraycopy(b, 0, result, offset, len);
-            offset += len;
-            remaining -= len;
+            int count = Math.min(b.length, remaining);
+            System.arraycopy(b, 0, result, offset, count);
+            offset += count;
+            remaining -= count;
         }
 
         return result;
--- a/test/sun/misc/IOUtils/ReadNBytes.java	Fri Jan 03 01:08:34 2020 +0000
+++ b/test/sun/misc/IOUtils/ReadNBytes.java	Fri Jan 03 01:36:35 2020 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, 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
@@ -33,7 +33,7 @@
 
 /*
  * @test
- * @bug 8080835
+ * @bug 8080835 8139206
  * @library /lib/testlibrary
  * @build jdk.testlibrary.*
  * @run main ReadNBytes
@@ -48,15 +48,19 @@
     public static void main(String[] args) throws IOException {
         test(new byte[]{1, 2, 3});
         test(createRandomBytes(1024));
-        test(createRandomBytes((1 << 13) - 1));
-        test(createRandomBytes((1 << 13)));
-        test(createRandomBytes((1 << 13) + 1));
-        test(createRandomBytes((1 << 15) - 1));
-        test(createRandomBytes((1 << 15)));
-        test(createRandomBytes((1 << 15) + 1));
-        test(createRandomBytes((1 << 17) - 1));
-        test(createRandomBytes((1 << 17)));
-        test(createRandomBytes((1 << 17) + 1));
+        for (int shift : new int[] {13, 15, 17}) {
+            for (int offset : new int[] {-1, 0, 1}) {
+                test(createRandomBytes((1 << shift) + offset));
+            }
+        }
+
+        test(-1);
+        test(0);
+        for (int shift : new int[] {13, 15, 17}) {
+            for (int offset : new int[] {-1, 0, 1}) {
+                test((1 << shift) + offset);
+            }
+        }
     }
 
     static void test(byte[] inputBytes) throws IOException {
@@ -93,6 +97,48 @@
         check(!in.isClosed(), "Stream unexpectedly closed");
     }
 
+    static void test(int max) throws IOException {
+        byte[] subset1, subset2;
+        byte[] inputBytes = max <= 0 ? new byte[0] : createRandomBytes(max);
+        WrapperInputStream in =
+            new WrapperInputStream(new ByteArrayInputStream(inputBytes));
+
+        if (max < 0) {
+            try {
+                IOUtils.readNBytes(in, max);
+                check(false, "Expected IllegalArgumentException not thrown");
+            } catch (IllegalArgumentException iae) {
+                return;
+            }
+        } else if (max == 0) {
+            int x;
+            check((x = IOUtils.readNBytes(in, max).length) == 0,
+                  "Expected zero bytes, got " + x);
+            return;
+        }
+
+        int off = Math.toIntExact(in.skip(generator.nextInt(max/2)));
+        int len = generator.nextInt(max - 1 - off);
+        byte[] readBytes = IOUtils.readNBytes(in, len);
+        check(readBytes.length == len,
+              "Expected " + len + " bytes, got " + readBytes.length);
+        subset1 = Arrays.copyOfRange(inputBytes, off, off + len);
+        subset2 = Arrays.copyOfRange(readBytes, 0, len);
+        check(Arrays.equals(subset1, subset2), "Expected[" + subset1 +
+              "], got:[" + readBytes + "]");
+
+        int remaining = max - (off + len);
+        readBytes = IOUtils.readNBytes(in, remaining);
+        check(readBytes.length == remaining,
+              "Expected " + remaining + "bytes, got " + readBytes.length);
+        subset1 = Arrays.copyOfRange(inputBytes, off + len, max);
+        subset2 = Arrays.copyOfRange(readBytes, 0, remaining);
+        check(Arrays.equals(subset1, subset2), "Expected[" + subset1 +
+          "], got:[" + readBytes + "]");
+
+        check(!in.isClosed(), "Stream unexpectedly closed");
+    }
+
     static byte[] createRandomBytes(int size) {
         byte[] bytes = new byte[size];
         generator.nextBytes(bytes);