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);