diff --git a/src/share/classes/sun/misc/IOUtils.java b/src/share/classes/sun/misc/IOUtils.java
index 5cba87982e058bd2a9b50d99a8b11004fb19d6b6..3877b03a25ccbbd19f648bfedd61ee39183fadc2 100644
--- a/src/share/classes/sun/misc/IOUtils.java
+++ b/src/share/classes/sun/misc/IOUtils.java
@@ -92,24 +92,6 @@ public class IOUtils {
return output;
}
- /**
- * 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
@@ -132,27 +114,87 @@ public class IOUtils {
* 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.
+ *
+ *
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.
+ *
+ *
When this stream reaches end of stream, further invocations of this
+ * method will return an empty byte array.
+ *
+ *
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.
+ *
+ *
The behavior for the case where the input stream is asynchronously
+ * closed, or the thread interrupted during the read, is highly input
+ * stream specific, and therefore not specified.
+ *
+ *
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 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 @@ public class IOUtils {
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 @@ public class IOUtils {
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;
diff --git a/test/sun/misc/IOUtils/ReadNBytes.java b/test/sun/misc/IOUtils/ReadNBytes.java
index afe943fefbabead1acd75b653201c8319402931e..96ae6c367ae524eb3470a37f5800b063f4cdb46a 100644
--- a/test/sun/misc/IOUtils/ReadNBytes.java
+++ b/test/sun/misc/IOUtils/ReadNBytes.java
@@ -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 @@ import sun.misc.IOUtils;
/*
* @test
- * @bug 8080835
+ * @bug 8080835 8139206
* @library /lib/testlibrary
* @build jdk.testlibrary.*
* @run main ReadNBytes
@@ -48,15 +48,19 @@ public class ReadNBytes {
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 @@ public class ReadNBytes {
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);