8227080: (fs) Files.newInputStream(...).skip(n) is slow

Reviewed-by: sbordet, rriggs, fweimer
diff --git a/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java b/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java
index 19d2c62..3037c3c 100644
--- a/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java
+++ b/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2002, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2019, 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
@@ -29,7 +29,7 @@
 import java.nio.*;
 import java.nio.channels.*;
 import java.nio.channels.spi.*;
-
+import java.util.Objects;
 
 /**
  * This class is defined here rather than in java.nio.channels.Channels
@@ -87,10 +87,8 @@
     public synchronized int read(byte[] bs, int off, int len)
         throws IOException
     {
-        if ((off < 0) || (off > bs.length) || (len < 0) ||
-            ((off + len) > bs.length) || ((off + len) < 0)) {
-            throw new IndexOutOfBoundsException();
-        } else if (len == 0)
+        Objects.checkFromIndexSize(off, len, bs.length);
+        if (len == 0)
             return 0;
 
         ByteBuffer bb = ((this.bs == bs)
@@ -119,6 +117,26 @@
         return 0;
     }
 
+    public synchronized long skip(long n) throws IOException {
+        // special case where the channel is to a file
+        if (ch instanceof SeekableByteChannel && n > 0) {
+            SeekableByteChannel sbc = (SeekableByteChannel)ch;
+            try {
+                long pos = sbc.position();
+                long size = sbc.size();
+                if (pos >= size) {
+                   return 0L;
+                }
+                n = Math.min(n, size - pos);
+                sbc.position(pos + n);
+                return sbc.position() - pos;
+            } catch (ClosedChannelException cce) {
+                throw new IOException(cce);
+            }
+        }
+        return super.skip(n);
+    }
+
     public void close() throws IOException {
         ch.close();
     }
diff --git a/test/jdk/java/nio/file/Files/Misc.java b/test/jdk/java/nio/file/Files/Misc.java
index 553f891..8e45ab1 100644
--- a/test/jdk/java/nio/file/Files/Misc.java
+++ b/test/jdk/java/nio/file/Files/Misc.java
@@ -22,11 +22,14 @@
  */
 
 /* @test
- * @bug 4313887 6838333 8005566 8032220 8215467
+ * @bug 4313887 6838333 8005566 8032220 8215467 8227080
  * @summary Unit test for miscellenous methods in java.nio.file.Files
  * @library ..
  */
 
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.ClosedChannelException;
 import java.nio.file.*;
 import static java.nio.file.Files.*;
 import static java.nio.file.LinkOption.*;
@@ -44,6 +47,7 @@
             testIsSameFile(dir);
             testFileTypeMethods(dir);
             testAccessMethods(dir);
+            testSkip(dir);
         } finally {
              TestUtil.removeAll(dir);
         }
@@ -372,6 +376,38 @@
         }
     }
 
+    /**
+     * Tests Files.newInputStream(Path).skip().
+     */
+    static void testSkip(Path tmpdir) throws IOException {
+        Path file = createFile(tmpdir.resolve("foo"));
+        try (OutputStream out = Files.newOutputStream(file)) {
+            byte[] blah = new byte[8192];
+            Arrays.fill(blah, (byte)42);
+            out.write(blah);
+            out.close();
+            try (InputStream in = Files.newInputStream(file)) {
+                assertTrue(in.skip(-1) == 0);
+                assertTrue(in.skip(0) == 0);
+                assertTrue(in.skip(blah.length/4) == blah.length/4);
+                assertTrue(in.skip(blah.length/2) == blah.length/2);
+                assertTrue(in.skip(Long.MAX_VALUE) == blah.length/4);
+                in.close();
+                try {
+                    long n = in.skip(1);
+                    throw new RuntimeException("skip() did not fail");
+                } catch (IOException ioe) {
+                    if (!(ioe.getCause() instanceof ClosedChannelException)) {
+                        throw new RuntimeException
+                            ("IOException not caused by ClosedChannelException");
+                    }
+                }
+            }
+        } finally {
+            delete(file);
+        }
+    }
+
     static void assertTrue(boolean okay) {
         if (!okay)
             throw new RuntimeException("Assertion Failed");