Fix *Channel to check that they're not trying to write into a read-only ByteBuffer.

Previously only DatagramChannel made the effort. Note that I also found another
customer for calculateTotalRemaining, and that the near-duplicate I'm removing
used an incorrect bound in its for loop.

Change-Id: Ia618ce271657a7fe7b3a0050dc089350d61e3398
diff --git a/luni/src/main/java/org/apache/harmony/nio/internal/DatagramChannelImpl.java b/luni/src/main/java/org/apache/harmony/nio/internal/DatagramChannelImpl.java
index b3b5fbd..e525657 100644
--- a/luni/src/main/java/org/apache/harmony/nio/internal/DatagramChannelImpl.java
+++ b/luni/src/main/java/org/apache/harmony/nio/internal/DatagramChannelImpl.java
@@ -44,7 +44,6 @@
 import org.apache.harmony.luni.platform.FileDescriptorHandler;
 import org.apache.harmony.luni.platform.INetworkSystem;
 import org.apache.harmony.luni.platform.Platform;
-//import org.apache.harmony.luni.util.ErrorCodeException; android-removed
 import org.apache.harmony.nio.AddressUtil;
 
 /*
@@ -60,8 +59,6 @@
     // default timeout used to nonblocking mode.
     private static final int DEFAULT_TIMEOUT = 1;
 
-    // android-removed: private static final int ERRCODE_SOCKET_NONBLOCKING_WOULD_BLOCK = -211;
-
     private static final byte[] stubArray = new byte[0];
 
     // The fd to interact with native code
@@ -207,14 +204,9 @@
         return this;
     }
 
-    /**
-     * @see java.nio.channels.DatagramChannel#receive(java.nio.ByteBuffer)
-     */
     @Override
     public SocketAddress receive(ByteBuffer target) throws IOException {
-        // must not null and not readonly
-        checkWritable(target);
-        // must open
+        FileChannelImpl.checkWritable(target);
         checkOpen();
 
         if (!isBound) {
@@ -414,18 +406,10 @@
         }
     }
 
-    /**
-     * @see java.nio.channels.DatagramChannel#read(java.nio.ByteBuffer)
-     */
     @Override
     public int read(ByteBuffer target) throws IOException {
-        if (null == target) {
-            throw new NullPointerException();
-        }
-        // status must be open and connected
+        FileChannelImpl.checkWritable(target);
         checkOpenConnected();
-        // target buffer must be not null and not readonly
-        checkWritable(target);
 
         if (!target.hasRemaining()) {
             return 0;
@@ -449,26 +433,17 @@
         return readCount;
     }
 
-    /**
-     * @see java.nio.channels.DatagramChannel#read(java.nio.ByteBuffer[], int,
-     *      int)
-     */
     @Override
-    public long read(ByteBuffer[] targets, int offset, int length)
-            throws IOException {
-        if (length < 0 || offset < 0
-                || (long) length + (long) offset > targets.length) {
+    public long read(ByteBuffer[] targets, int offset, int length) throws IOException {
+        if (length < 0 || offset < 0 || (long) length + (long) offset > targets.length) {
             throw new IndexOutOfBoundsException();
         }
 
         // status must be open and connected
         checkOpenConnected();
-
-        int totalCount = 0;
-        for (int val = offset; val < length; val++) {
-            // target buffer must be not null and not readonly
-            checkWritable(targets[val]);
-            totalCount += targets[val].remaining();
+        int totalCount = FileChannelImpl.calculateTotalRemaining(targets, offset, length, true);
+        if (totalCount == 0) {
+            return 0;
         }
 
         // read data to readBuffer, and then transfer data from readBuffer to
@@ -571,8 +546,7 @@
      *      int)
      */
     @Override
-    public long write(ByteBuffer[] sources, int offset, int length)
-            throws IOException {
+    public long write(ByteBuffer[] sources, int offset, int length) throws IOException {
         if (length < 0 || offset < 0
                 || (long) length + (long) offset > sources.length) {
             throw new IndexOutOfBoundsException();
@@ -580,8 +554,8 @@
 
         // status must be open and connected
         checkOpenConnected();
-        int count = calculateByteBufferArray(sources, offset, length);
-        if (0 == count) {
+        int count = FileChannelImpl.calculateTotalRemaining(sources, offset, length, false);
+        if (count == 0) {
             return 0;
         }
         ByteBuffer writeBuf = ByteBuffer.allocate(count);
@@ -627,7 +601,6 @@
                             .array(), start, length, isBound);
                 }
                 return result;
-                // android-removed: bogus catch (SocketException e) and use of ErrorCodeException.
             } finally {
                 end(result > 0);
             }
@@ -688,32 +661,12 @@
     }
 
     /*
-     * Buffer check, must not null and not read only buffer, for read and
-     * receive.
-     */
-    private void checkWritable(ByteBuffer target) {
-        // including checking of NPE.
-        if (target.isReadOnly()) {
-            throw new IllegalArgumentException();
-        }
-    }
-
-    /*
      * Get the fd for internal use.
      */
     public FileDescriptor getFD() {
         return fd;
     }
 
-    private int calculateByteBufferArray(ByteBuffer[] sources, int offset,
-            int length) {
-        int sum = 0;
-        for (int val = offset; val < offset + length; val++) {
-            sum += sources[val].remaining();
-        }
-        return sum;
-    }
-
     /*
      * The adapter class of DatagramSocket
      */
diff --git a/luni/src/main/java/org/apache/harmony/nio/internal/FileChannelImpl.java b/luni/src/main/java/org/apache/harmony/nio/internal/FileChannelImpl.java
index fcfbe5a..cdb8b3d 100644
--- a/luni/src/main/java/org/apache/harmony/nio/internal/FileChannelImpl.java
+++ b/luni/src/main/java/org/apache/harmony/nio/internal/FileChannelImpl.java
@@ -62,7 +62,6 @@
         } catch (IOException e) {
             throw new Error(e);
         }
-
     }
 
     // Handle to the open file
@@ -222,9 +221,7 @@
     }
 
     public int read(ByteBuffer buffer, long position) throws IOException {
-        if (null == buffer) {
-            throw new NullPointerException();
-        }
+        FileChannelImpl.checkWritable(buffer);
         if (position < 0) {
             throw new IllegalArgumentException();
         }
@@ -246,6 +243,7 @@
     }
 
     public int read(ByteBuffer buffer) throws IOException {
+        FileChannelImpl.checkWritable(buffer);
         openCheck();
         if (!buffer.hasRemaining()) {
             return 0;
@@ -293,7 +291,7 @@
             throw new IndexOutOfBoundsException();
         }
         openCheck();
-        int count = calculateTotalRemaining(buffers, offset, length);
+        int count = FileChannelImpl.calculateTotalRemaining(buffers, offset, length, true);
         if (count == 0) {
             return 0;
         }
@@ -547,13 +545,12 @@
         return bytesWritten;
     }
 
-    public long write(ByteBuffer[] buffers, int offset, int length)
-            throws IOException {
+    public long write(ByteBuffer[] buffers, int offset, int length) throws IOException {
         if (offset < 0 || length < 0 || (offset + length) > buffers.length) {
             throw new IndexOutOfBoundsException();
         }
         openCheck();
-        int count = calculateTotalRemaining(buffers, offset, length);
+        int count = FileChannelImpl.calculateTotalRemaining(buffers, offset, length, false);
         if (count == 0) {
             return 0;
         }
@@ -618,10 +615,24 @@
         return bytesWritten;
     }
 
-    static int calculateTotalRemaining(ByteBuffer[] buffers, int offset, int length) {
+    static void checkWritable(ByteBuffer buffer) {
+        if (buffer.isReadOnly()) {
+            throw new IllegalArgumentException("read-only buffer");
+        }
+    }
+
+    /**
+     * @param copyingIn true if we're copying data into the buffers (typically
+     * because the caller is a file/network read operation), false if we're
+     * copying data out of the buffers (for a file/network write operation).
+     */
+    static int calculateTotalRemaining(ByteBuffer[] buffers, int offset, int length, boolean copyingIn) {
         int count = 0;
         for (int i = offset; i < offset + length; ++i) {
             count += buffers[i].remaining();
+            if (copyingIn) {
+                checkWritable(buffers[i]);
+            }
         }
         return count;
     }
diff --git a/luni/src/main/java/org/apache/harmony/nio/internal/SocketChannelImpl.java b/luni/src/main/java/org/apache/harmony/nio/internal/SocketChannelImpl.java
index fecfcc7..3f1a4ab 100644
--- a/luni/src/main/java/org/apache/harmony/nio/internal/SocketChannelImpl.java
+++ b/luni/src/main/java/org/apache/harmony/nio/internal/SocketChannelImpl.java
@@ -50,7 +50,6 @@
 import org.apache.harmony.luni.platform.FileDescriptorHandler;
 import org.apache.harmony.luni.platform.INetworkSystem;
 import org.apache.harmony.luni.platform.Platform;
-//import org.apache.harmony.luni.util.ErrorCodeException; android-removed
 import org.apache.harmony.luni.util.Msg;
 import org.apache.harmony.nio.AddressUtil;
 
@@ -61,8 +60,6 @@
 
     private static final int EOF = -1;
 
-    // android-removed: private static final int ERRCODE_SOCKET_NONBLOCKING_WOULD_BLOCK = -211;
-
     // The singleton to do the native network operation.
     static final INetworkSystem networkSystem = Platform.getNetworkSystem();
 
@@ -359,9 +356,7 @@
      */
     @Override
     public int read(ByteBuffer target) throws IOException {
-        if (null == target) {
-            throw new NullPointerException();
-        }
+        FileChannelImpl.checkWritable(target);
         checkOpenConnected();
         if (!target.hasRemaining()) {
             return 0;
@@ -386,10 +381,6 @@
         return readCount;
     }
 
-    /**
-     * @see java.nio.channels.SocketChannel#read(java.nio.ByteBuffer[], int,
-     *      int)
-     */
     @Override
     public long read(ByteBuffer[] targets, int offset, int length) throws IOException {
         if (!isIndexValid(targets, offset, length)) {
@@ -397,7 +388,7 @@
         }
 
         checkOpenConnected();
-        int totalCount = FileChannelImpl.calculateTotalRemaining(targets, offset, length);
+        int totalCount = FileChannelImpl.calculateTotalRemaining(targets, offset, length, true);
         if (totalCount == 0) {
             return 0;
         }
@@ -480,10 +471,6 @@
         return writeImpl(source);
     }
 
-    /**
-     * @see java.nio.channels.SocketChannel#write(java.nio.ByteBuffer[], int,
-     *      int)
-     */
     @Override
     public long write(ByteBuffer[] sources, int offset, int length) throws IOException {
         if (!isIndexValid(sources, offset, length)) {
@@ -491,7 +478,7 @@
         }
 
         checkOpenConnected();
-        int count = FileChannelImpl.calculateTotalRemaining(sources, offset, length);
+        int count = FileChannelImpl.calculateTotalRemaining(sources, offset, length, false);
         if (count == 0) {
             return 0;
         }
@@ -532,23 +519,17 @@
                     begin();
                 }
                 if (source.isDirect()) {
-                    // BEGIN android-changed
-                    // changed address from long to int; split address and pos parameters
                     int address = AddressUtil.getDirectBufferAddress(source);
-                    writeCount = networkSystem.writeDirect(fd, address, pos,
-                            length);
-                    // END android-changed
+                    writeCount = networkSystem.writeDirect(fd, address, pos, length);
                 } else if (source.hasArray()) {
                     pos += source.arrayOffset();
-                    writeCount = networkSystem.write(fd, source.array(), pos,
-                            length);
+                    writeCount = networkSystem.write(fd, source.array(), pos, length);
                 } else {
                     byte[] array = new byte[length];
                     source.get(array);
                     writeCount = networkSystem.write(fd, array, 0, length);
                 }
                 source.position(pos + writeCount);
-                // android-removed: bogus catch (SocketException e) and use of ErrorCodeException.
             } finally {
                 if (isBlocking()) {
                     end(writeCount >= 0);
@@ -561,8 +542,7 @@
     /*
      * Status check, open and "connected", when read and write.
      */
-    synchronized private void checkOpenConnected()
-            throws ClosedChannelException {
+    synchronized private void checkOpenConnected() throws ClosedChannelException {
         if (!isOpen()) {
             throw new ClosedChannelException();
         }
diff --git a/luni/src/test/java/java/nio/channels/AllTests.java b/luni/src/test/java/java/nio/channels/AllTests.java
new file mode 100644
index 0000000..0b89f37
--- /dev/null
+++ b/luni/src/test/java/java/nio/channels/AllTests.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio.channels;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AllTests {
+    public static final Test suite() {
+        TestSuite suite = new TestSuite();
+        suite.addTestSuite(java.nio.channels.DatagramChannelTest.class);
+        suite.addTestSuite(java.nio.channels.FileChannelTest.class);
+        suite.addTestSuite(java.nio.channels.SocketChannelTest.class);
+        return suite;
+    }
+}
diff --git a/luni/src/test/java/java/nio/channels/DatagramChannelTest.java b/luni/src/test/java/java/nio/channels/DatagramChannelTest.java
new file mode 100644
index 0000000..c3a537e
--- /dev/null
+++ b/luni/src/test/java/java/nio/channels/DatagramChannelTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio.channels;
+
+import java.net.DatagramSocket;
+import java.nio.ByteBuffer;
+
+public class DatagramChannelTest extends junit.framework.TestCase {
+    public void test_read_intoReadOnlyByteArrays() throws Exception {
+        ByteBuffer readOnly = ByteBuffer.allocate(1).asReadOnlyBuffer();
+        DatagramSocket ds = new DatagramSocket(0);
+        DatagramChannel dc = DatagramChannel.open();
+        dc.connect(ds.getLocalSocketAddress());
+        try {
+            dc.read(readOnly);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            dc.read(new ByteBuffer[] { readOnly });
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            dc.read(new ByteBuffer[] { readOnly }, 0, 1);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+}
diff --git a/luni/src/test/java/java/nio/channels/FileChannelTest.java b/luni/src/test/java/java/nio/channels/FileChannelTest.java
new file mode 100644
index 0000000..92b3f25
--- /dev/null
+++ b/luni/src/test/java/java/nio/channels/FileChannelTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio.channels;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.ByteBuffer;
+
+public class FileChannelTest extends junit.framework.TestCase {
+    public void test_read_intoReadOnlyByteArrays() throws Exception {
+        ByteBuffer readOnly = ByteBuffer.allocate(1).asReadOnlyBuffer();
+        File tmp = File.createTempFile("empty", "tmp");
+        tmp.deleteOnExit();
+        FileChannel fc = new FileInputStream(tmp).getChannel();
+        try {
+            fc.read(readOnly);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            fc.read(new ByteBuffer[] { readOnly });
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            fc.read(new ByteBuffer[] { readOnly }, 0, 1);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            fc.read(readOnly, 0L);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+}
diff --git a/luni/src/test/java/java/nio/channels/SocketChannelTest.java b/luni/src/test/java/java/nio/channels/SocketChannelTest.java
new file mode 100644
index 0000000..71b4c81
--- /dev/null
+++ b/luni/src/test/java/java/nio/channels/SocketChannelTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.nio.channels;
+
+import java.net.ServerSocket;
+import java.nio.ByteBuffer;
+
+public class SocketChannelTest extends junit.framework.TestCase {
+    public void test_read_intoReadOnlyByteArrays() throws Exception {
+        ByteBuffer readOnly = ByteBuffer.allocate(1).asReadOnlyBuffer();
+        ServerSocket ss = new ServerSocket(0);
+        ss.setReuseAddress(true);
+        SocketChannel sc = SocketChannel.open(ss.getLocalSocketAddress());
+        try {
+            sc.read(readOnly);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            sc.read(new ByteBuffer[] { readOnly });
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            sc.read(new ByteBuffer[] { readOnly }, 0, 1);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+}
diff --git a/luni/src/test/java/tests/AllTests.java b/luni/src/test/java/tests/AllTests.java
index 4b3a484..035a2f5 100644
--- a/luni/src/test/java/tests/AllTests.java
+++ b/luni/src/test/java/tests/AllTests.java
@@ -61,6 +61,7 @@
         suite.addTest(java.lang.AllTests.suite());
         suite.addTest(java.lang.reflect.AllTests.suite());
         suite.addTest(java.net.AllTests.suite());
+        suite.addTest(java.nio.channels.AllTests.suite());
         suite.addTest(java.nio.charset.AllTests.suite());
         suite.addTest(java.text.AllTests.suite());
         suite.addTest(java.util.AllTests.suite());