6516099: InputStream.skipFully(int k) to skip exactly k bytes
authorbpb
Wed, 05 Dec 2018 15:58:46 -0800
changeset 52860 8dd8965df7f6
parent 52859 413c28945e0f
child 52861 f7dee5d12632
6516099: InputStream.skipFully(int k) to skip exactly k bytes Reviewed-by: rriggs, bchristi, serb, dfuchs
src/java.base/share/classes/java/io/InputStream.java
test/jdk/java/io/InputStream/NullInputStream.java
test/jdk/java/io/InputStream/Skip.java
--- a/src/java.base/share/classes/java/io/InputStream.java	Wed Dec 05 19:17:22 2018 +0100
+++ b/src/java.base/share/classes/java/io/InputStream.java	Wed Dec 05 15:58:46 2018 -0800
@@ -64,8 +64,8 @@
      * <p> While the stream is open, the {@code available()}, {@code read()},
      * {@code read(byte[])}, {@code read(byte[], int, int)},
      * {@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
+     * {@code readNBytes(int)}, {@code skip(long)}, {@code skipNBytes(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}.
      *
@@ -139,6 +139,14 @@
             }
 
             @Override
+            public void skipNBytes(long n) throws IOException {
+                ensureOpen();
+                if (n > 0) {
+                    throw new EOFException();
+                }
+            }
+
+            @Override
             public long transferTo(OutputStream out) throws IOException {
                 Objects.requireNonNull(out);
                 ensureOpen();
@@ -513,11 +521,11 @@
      * For instance, the implementation may depend on the ability to seek.
      *
      * @param      n   the number of bytes to be skipped.
-     * @return     the actual number of bytes skipped.
+     * @return     the actual number of bytes skipped which might be zero.
      * @throws     IOException  if an I/O error occurs.
+     * @see        java.io.InputStream#skipNBytes(long)
      */
     public long skip(long n) throws IOException {
-
         long remaining = n;
         int nr;
 
@@ -539,6 +547,65 @@
     }
 
     /**
+     * Skips over and discards exactly {@code n} bytes of data from this input
+     * stream.  If {@code n} is zero, then no bytes are skipped.
+     * If {@code n} is negative, then no bytes are skipped.
+     * Subclasses may handle the negative value differently.
+     *
+     * <p> This method blocks until the requested number of bytes have been
+     * skipped, end of file is reached, or an exception is thrown.
+     *
+     * <p> If end of stream is reached before the stream is at the desired
+     * position, then an {@code EOFException} is thrown.
+     *
+     * <p> If an I/O error occurs, then the input stream may be
+     * in an inconsistent state. It is strongly recommended that the
+     * stream be promptly closed if an I/O error occurs.
+     *
+     * @implNote
+     * Subclasses are encouraged to provide a more efficient implementation
+     * of this method.
+     *
+     * @implSpec
+     * If {@code n} is zero or negative, then no bytes are skipped.
+     * If {@code n} is positive, the default implementation of this method
+     * invokes {@link #skip(long) skip()} with parameter {@code n}.  If the
+     * return value of {@code skip(n)} is non-negative and less than {@code n},
+     * then {@link #read()} is invoked repeatedly until the stream is {@code n}
+     * bytes beyond its position when this method was invoked or end of stream
+     * is reached.  If the return value of {@code skip(n)} is negative or
+     * greater than {@code n}, then an {@code IOException} is thrown.  Any
+     * exception thrown by {@code skip()} or {@code read()} will be propagated.
+     *
+     * @param      n   the number of bytes to be skipped.
+     * @throws     EOFException if end of stream is encountered before the
+     *             stream can be positioned {@code n} bytes beyond its position
+     *             when this method was invoked.
+     * @throws     IOException  if the stream cannot be positioned properly or
+     *             if an I/O error occurs.
+     * @see        java.io.InputStream#skip(long)
+     */
+    public void skipNBytes(long n) throws IOException {
+        if (n > 0) {
+            long ns = skip(n);
+            if (ns >= 0 && ns < n) { // skipped too few bytes
+                // adjust number to skip
+                n -= ns;
+                // read until requested number skipped or EOS reached
+                while (n > 0 && read() != -1) {
+                    n--;
+                }
+                // if not enough skipped, then EOFE
+                if (n != 0) {
+                    throw new EOFException();
+                }
+            } else if (ns != n) { // skipped negative or too many bytes
+                throw new IOException("Unable to skip exactly");
+            }
+        }
+    }
+
+    /**
      * Returns an estimate of the number of bytes that can be read (or skipped
      * over) from this input stream without blocking, which may be 0, or 0 when
      * end of stream is detected.  The read might be on the same thread or
--- a/test/jdk/java/io/InputStream/NullInputStream.java	Wed Dec 05 19:17:22 2018 +0100
+++ b/test/jdk/java/io/InputStream/NullInputStream.java	Wed Dec 05 15:58:46 2018 -0800
@@ -22,6 +22,7 @@
  */
 
 import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
 import java.io.InputStream;
 import java.io.IOException;
 import org.testng.annotations.AfterGroups;
@@ -31,7 +32,7 @@
 
 /*
  * @test
- * @bug 4358774 8139206
+ * @bug 4358774 6516099 8139206
  * @run testng NullInputStream
  * @summary Check for expected behavior of InputStream.nullInputStream().
  */
@@ -146,6 +147,21 @@
     }
 
     @Test(groups = "open")
+    public static void testSkipNBytes() {
+        try {
+            openStream.skipNBytes(-1);
+            openStream.skipNBytes(0);
+        } catch (IOException ioe) {
+            fail("Unexpected IOException");
+        }
+    }
+
+    @Test(groups = "open", expectedExceptions = EOFException.class)
+    public static void testSkipNBytesEOF() throws IOException {
+        openStream.skipNBytes(1);
+    }
+
+    @Test(groups = "open")
     public static void testTransferTo() {
         try {
             assertEquals(0, openStream.transferTo(new ByteArrayOutputStream(7)),
@@ -219,6 +235,15 @@
     }
 
     @Test(groups = "closed")
+    public static void testSkipNBytesClosed() {
+        try {
+            closedStream.skipNBytes(1);
+            fail("Expected IOException not thrown");
+        } catch (IOException e) {
+        }
+    }
+
+    @Test(groups = "closed")
     public static void testTransferToClosed() {
         try {
             closedStream.transferTo(new ByteArrayOutputStream(7));
--- a/test/jdk/java/io/InputStream/Skip.java	Wed Dec 05 19:17:22 2018 +0100
+++ b/test/jdk/java/io/InputStream/Skip.java	Wed Dec 05 15:58:46 2018 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
@@ -21,24 +21,22 @@
  * questions.
  */
 
-
-/* @test
-   @bug 4016710
-   @summary check for correct implementation of InputStream.skip
-   */
-
-import java.io.*;
-
+/**
+ * @test
+ * @bug 4016710 6516099
+ * @summary check for correct implementation of InputStream.skip{NBytes}
+ */
 
-public class Skip{
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.IOException;
 
-    private static void dotest(InputStream in , int curpos ,
-                               long total , long toskip , long expected)
-        throws Exception
-    {
+public class Skip {
+    private static final int EOF = -1;
 
+    private static void dotest(InputStream in, int curpos, long total,
+                               long toskip, long expected) throws Exception {
         try {
-
             System.err.println("\n\nCurrently at pos = " + curpos +
                                "\nTotal bytes in the Stream = " + total +
                                "\nNumber of bytes to skip = " + toskip +
@@ -50,43 +48,69 @@
             System.err.println("actual number skipped: "+ skipped);
 
             if ((skipped < 0) || (skipped > expected)) {
-                throw new RuntimeException("Unexpected number of bytes skipped");
+                throw new RuntimeException("Unexpected byte count skipped");
             }
-
         } catch (IOException e) {
-            System.err.println("IOException is thrown - possible result");
+            System.err.println("IOException is thrown: " + e);
         } catch (Throwable e) {
-            throw new RuntimeException("Unexpected "+e+" is thrown!");
+            throw new RuntimeException("Unexpected " + e + " is thrown!");
         }
+    }
 
+    private static void dotestExact(MyInputStream in, long curpos, long total,
+        long toskip, boolean expectIOE, boolean expectEOFE) {
+
+        System.err.println("\n\nCurrently at pos = " + curpos +
+                           "\nTotal bytes in the Stream = " + total +
+                           "\nNumber of bytes to skip = " + toskip);
+
+        try {
+            long pos = in.position();
+            assert pos == curpos : pos + " != " + curpos;
+            in.skipNBytes(toskip);
+            if (in.position() != pos + (toskip < 0 ? 0 : toskip)) {
+                throw new RuntimeException((in.position() - pos) +
+                    " bytes skipped; expected " + toskip);
+            }
+        } catch (EOFException eofe) {
+            if (!expectEOFE) {
+                throw new RuntimeException("Unexpected EOFException", eofe);
+            }
+            System.err.println("Caught expected EOFException");
+        } catch (IOException ioe) {
+            if (!expectIOE) {
+                throw new RuntimeException("Unexpected IOException", ioe);
+            }
+            System.err.println("Caught expected IOException");
+        }
     }
 
     public static void main( String argv[] ) throws Exception {
-
         MyInputStream in = new MyInputStream(11);
 
-        /* test for negative skip */
+        // test for negative skip
         dotest(in,  0, 11, -23,  0);
 
-        /* check for skip beyond EOF starting from before EOF */
+        // check for skip beyond EOF starting from before EOF
         dotest(in,  0, 11,  20, 11);
 
-        /* check for skip after EOF */
-        dotest(in, -1, 11,  20,  0);
+        // check for skip after EOF
+        dotest(in, EOF, 11,  20,  0);
 
         in = new MyInputStream(9000);
-        /* check for skip equal to the read chunk size in InputStream.java */
+
+        // check for skip equal to the read chunk size in InputStream.java
         dotest(in,  0, 9000, 2048, 2048);
 
-        /* check for skip greater than the read chunk size in InputStream.java */
+        // check for skip larger than the read chunk size in InputStream.java
         dotest(in, 2048, 9000, 5000, 5000);
 
-        /* check for skip beyond EOF starting from before EOF */
+        // check for skip beyond EOF starting from before EOF
         dotest(in, 7048, 9000, 5000, 1952);
 
         in = new MyInputStream(5000);
 
-        /* check for multiple chunk reads */
+        // check for multiple chunk reads
         dotest(in, 0, 5000, 6000, 5000);
 
         /*
@@ -98,23 +122,86 @@
          * dotest(in, 0, total, toskip, toskip);
          */
 
+        // tests for skipping an exact number of bytes
+
+        final long streamLength = Long.MAX_VALUE;
+        in = new MyInputStream(streamLength);
+
+        // negative skip: OK
+        dotestExact(in, 0, streamLength, -1, false, false);
+
+        // negative skip at EOF: OK
+        in.position(streamLength);
+        dotestExact(in, streamLength, streamLength, -1, false, false);
+        in.position(0);
+
+        // zero skip: OK
+        dotestExact(in, 0, streamLength, 0, false, false);
+
+        // zero skip at EOF: OK
+        in.position(streamLength);
+        dotestExact(in, streamLength, streamLength, 0, false, false);
+
+        // skip(1) at EOF: EOFE
+        dotestExact(in, streamLength, streamLength, 1, false, true);
+        in.position(0);
+
+        final long n = 31; // skip count
+        long pos = 0;
+
+        // skip(n) returns negative value: IOE
+        in.setState(-1, 100);
+        dotestExact(in, pos, streamLength, n, true, false);
+
+        // skip(n) returns n + 1: IOE
+        in.setState(n + 1, 100);
+        dotestExact(in, pos, streamLength, n, true, false);
+        pos += n + 1;
+
+        // skip(n) returns n/2 but only n/4 subsequent reads succeed: EOFE
+        in.setState(n/2, n/2 + n/4);
+        dotestExact(in, pos, streamLength, n, false, true);
+        pos += n/2 + n/4;
+
+        // skip(n) returns n/2 but n - n/2 subsequent reads succeed: OK
+        in.setState(n/2, n);
+        dotestExact(in, pos, streamLength, n, false, false);
+        pos += n;
     }
-
 }
 
 class MyInputStream extends InputStream {
+    private static final int EOF = -1;
 
-    private int readctr = 0;
-    private long endoffile;
+    private final long endoffile;
+
+    private long readctr = 0;
+
+    private boolean isStateSet = false;
+    private long skipReturn;
+    private long readLimit;
 
     public MyInputStream(long endoffile) {
         this.endoffile = endoffile;
     }
 
-    public int read() {
+    /**
+     * Limits the behavior of skip() and read().
+     *
+     * @param skipReturn the value to be returned by skip()
+     * @param maxReads   the maximum number of reads past the current position
+     *                   before EOF is reached
+     */
+    public void setState(long skipReturn, long maxReads) {
+        this.skipReturn = skipReturn;
+        this.readLimit = readctr + maxReads;
+        isStateSet = true;
+    }
 
-        if (readctr == endoffile) {
-            return -1;
+    public int read() {
+        if (readctr == endoffile ||
+            (isStateSet && readctr >= readLimit)) {
+            return EOF;
         }
         else {
             readctr++;
@@ -123,4 +210,19 @@
     }
 
     public int available() { return 0; }
+
+    public long position() { return readctr; }
+
+    public void position(long pos) {
+        readctr = pos < 0 ? 0 : Math.min(pos, endoffile);
+    }
+
+    public long skip(long n) throws IOException {
+        if (isStateSet) {
+            return skipReturn < 0 ? skipReturn : super.skip(skipReturn);
+        }
+
+        // InputStream skip implementation.
+        return super.skip(n); // readctr is implicitly incremented
+    }
 }