--- old/src/java.base/share/classes/java/io/InputStream.java 2018-01-24 11:34:00.000000000 -0800 +++ new/src/java.base/share/classes/java/io/InputStream.java 2018-01-24 11:34:00.000000000 -0800 @@ -63,7 +63,8 @@ * *

While the stream is open, the {@code available()}, {@code read()}, * {@code read(byte[])}, {@code read(byte[], int, int)}, - * {@code readAllBytes()}, {@code readNBytes()}, {@code skip()}, and + * {@code readAllBytes()}, {@code readNBytes(byte[], int, int)}, + * {@code readNBytes(int)}, {@code skip(long)}, and * {@code transferTo()} methods all behave as if end of stream has been * reached. After the stream has been closed, these methods all throw * {@code IOException}. @@ -123,6 +124,15 @@ } @Override + public byte[] readNBytes(int len) throws IOException { + if (len < 0) { + throw new IllegalArgumentException("len < 0"); + } + ensureOpen(); + return new byte[0]; + } + + @Override public long skip(long n) throws IOException { ensureOpen(); return 0L; @@ -233,8 +243,8 @@ * b and the number of bytes read before the exception * occurred is returned. The default implementation of this method blocks * until the requested amount of input data len has been read, - * end of file is detected, or an exception is thrown. Subclasses are encouraged - * to provide a more efficient implementation of this method. + * end of file is detected, or an exception is thrown. Subclasses are + * encouraged to provide a more efficient implementation of this method. * * @param b the buffer into which the data is read. * @param off the start offset in array b @@ -308,26 +318,85 @@ * 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}. + * * @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 9 */ public byte[] readAllBytes() throws IOException { + return readNBytes(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 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 byte[] readNBytes(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 = read(buf, nread, buf.length - nread)) > 0) { + while ((n = read(buf, nread, + Math.min(buf.length - nread, remaining))) > 0) { nread += n; + remaining -= n; } if (nread > 0) { @@ -345,7 +414,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) { @@ -357,12 +428,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; --- old/test/jdk/java/io/InputStream/NullInputStream.java 2018-01-24 11:34:01.000000000 -0800 +++ new/test/jdk/java/io/InputStream/NullInputStream.java 2018-01-24 11:34:00.000000000 -0800 @@ -31,7 +31,7 @@ /* * @test - * @bug 4358774 + * @bug 4358774 8139206 * @run testng NullInputStream * @summary Check for expected behavior of InputStream.nullInputStream(). */ @@ -107,7 +107,7 @@ } @Test(groups = "open") - public static void testreadNBytes() { + public static void testReadNBytes() { try { assertEquals(0, openStream.readNBytes(new byte[1], 0, 1), "readNBytes(byte[],int,int) != 0"); @@ -117,6 +117,26 @@ } @Test(groups = "open") + public static void testReadNBytesWithLength() { + try { + assertEquals(0, openStream.readNBytes(-1).length, + "readNBytes(-1) != 0"); + fail("Expected IllegalArgumentException not thrown"); + } catch (IllegalArgumentException iae) { + } catch (IOException ioe) { + fail("Unexpected IOException"); + } + try { + assertEquals(0, openStream.readNBytes(0).length, + "readNBytes(0, false) != 0"); + assertEquals(0, openStream.readNBytes(1).length, + "readNBytes(1, false) != 0"); + } catch (IOException ioe) { + fail("Unexpected IOException"); + } + } + + @Test(groups = "open") public static void testSkip() { try { assertEquals(0, openStream.skip(1), "skip() != 0"); @@ -181,6 +201,15 @@ } @Test(groups = "closed") + public static void testReadNBytesWithLengthClosed() { + try { + closedStream.readNBytes(1); + fail("Expected IOException not thrown"); + } catch (IOException e) { + } + } + + @Test(groups = "closed") public static void testSkipClosed() { try { closedStream.skip(1); --- old/test/jdk/java/io/InputStream/ReadNBytes.java 2018-01-24 11:34:01.000000000 -0800 +++ new/test/jdk/java/io/InputStream/ReadNBytes.java 2018-01-24 11:34:01.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, 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 @@ -31,7 +31,7 @@ /* * @test - * @bug 8080835 + * @bug 8080835 8139206 * @library /test/lib * @build jdk.test.lib.RandomFactory * @run main ReadNBytes @@ -46,15 +46,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 { @@ -91,6 +95,46 @@ check(!in.isClosed(), "Stream unexpectedly closed"); } + static void test(int max) throws IOException { + byte[] inputBytes = max <= 0 ? new byte[0] : createRandomBytes(max); + WrapperInputStream in = + new WrapperInputStream(new ByteArrayInputStream(inputBytes)); + + if (max < 0) { + try { + in.readNBytes(max); + check(false, "Expected IllegalArgumentException not thrown"); + } catch (IllegalArgumentException iae) { + return; + } + } else if (max == 0) { + int x; + check((x = in.readNBytes(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 = in.readNBytes(len); + check(readBytes.length == len, + "Expected " + len + " bytes, got " + readBytes.length); + check(Arrays.equals(inputBytes, off, off + len, readBytes, 0, len), + "Expected[" + Arrays.copyOfRange(inputBytes, off, off + len) + + "], got:[" + readBytes + "]"); + + int remaining = max - (off + len); + readBytes = in.readNBytes(remaining); + check(readBytes.length == remaining, + "Expected " + remaining + "bytes, got " + readBytes.length); + check(Arrays.equals(inputBytes, off + len, max, + readBytes, 0, remaining), + "Expected[" + Arrays.copyOfRange(inputBytes, off + len, max) + + "], got:[" + readBytes + "]"); + + check(!in.isClosed(), "Stream unexpectedly closed"); + } + static byte[] createRandomBytes(int size) { byte[] bytes = new byte[size]; generator.nextBytes(bytes);