Remove UnsafeByteSequence.

Replace it with a smaller, less general class for reading
the contents of a file into a byte array or a string.

Remove the need for an additional buffer and instead read
the contents of the file directly into a buffer owned by
FileReader. Optimise for the case where the length
of the file is known.

This imposes the additional restriction on callers that the
path of the file they're reading is an absolute path. I've
checked that all callers obey that currently.

(Cherry picked from b0674a77ed67e30f2510064b89151002d7f2eb2a)

Bug: b/11411129
Change-Id: Ic020a45051416f2de9a2f78bd5f99a21feff17df
diff --git a/luni/src/main/java/java/lang/UnsafeByteSequence.java b/luni/src/main/java/java/lang/UnsafeByteSequence.java
deleted file mode 100644
index 228bb01..0000000
--- a/luni/src/main/java/java/lang/UnsafeByteSequence.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.lang;
-
-import java.nio.charset.Charset;
-
-/**
- * A cheaper ByteArrayOutputStream for internal use. This class is unsynchronized,
- * and returns its internal array if it's the right size. This makes String.getBytes("UTF-8")
- * 10x faster than the baseline non-fast-path implementation instead of 8x faster when using
- * ByteArrayOutputStream. When GC and uncontended synchronization become cheap, we should be
- * able to get rid of this class. In the meantime, if you need to add further API, please try
- * to keep it plug-compatible with ByteArrayOutputStream with an eye to future obsolescence.
- *
- * @hide
- */
-public class UnsafeByteSequence {
-    private byte[] bytes;
-    private int count;
-
-    public UnsafeByteSequence(int initialCapacity) {
-        this.bytes = new byte[initialCapacity];
-    }
-
-    public int size() {
-        return count;
-    }
-
-    /**
-     * Moves the write pointer back to the beginning of the sequence,
-     * but without resizing or reallocating the buffer.
-     */
-    public void rewind() {
-        count = 0;
-    }
-
-    public void write(byte[] buffer, int offset, int length) {
-        if (count + length >= bytes.length) {
-            byte[] newBytes = new byte[(count + length) * 2];
-            System.arraycopy(bytes, 0, newBytes, 0, count);
-            bytes = newBytes;
-        }
-        System.arraycopy(buffer, offset, bytes, count, length);
-        count += length;
-    }
-
-    public void write(int b) {
-        if (count == bytes.length) {
-            byte[] newBytes = new byte[count * 2];
-            System.arraycopy(bytes, 0, newBytes, 0, count);
-            bytes = newBytes;
-        }
-        bytes[count++] = (byte) b;
-    }
-
-    @FindBugsSuppressWarnings("EI_EXPOSE_REP")
-    public byte[] toByteArray() {
-        if (count == bytes.length) {
-            return bytes;
-        }
-        byte[] result = new byte[count];
-        System.arraycopy(bytes, 0, result, 0, count);
-        return result;
-    }
-
-    public String toString(Charset cs) {
-        return new String(bytes, 0, count, cs);
-    }
-}
diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java
index f7cc41f..10ef671 100644
--- a/luni/src/main/java/libcore/io/IoUtils.java
+++ b/luni/src/main/java/libcore/io/IoUtils.java
@@ -18,10 +18,11 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InterruptedIOException;
-import java.io.RandomAccessFile;
 import java.net.Socket;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.Random;
 import static libcore.io.OsConstants.*;
@@ -102,33 +103,15 @@
     /**
      * Returns the contents of 'path' as a byte array.
      */
-    public static byte[] readFileAsByteArray(String path) throws IOException {
-        return readFileAsBytes(path).toByteArray();
+    public static byte[] readFileAsByteArray(String absolutePath) throws IOException {
+        return new FileReader(absolutePath).readFully().toByteArray();
     }
 
     /**
      * Returns the contents of 'path' as a string. The contents are assumed to be UTF-8.
      */
-    public static String readFileAsString(String path) throws IOException {
-        return readFileAsBytes(path).toString(StandardCharsets.UTF_8);
-    }
-
-    private static UnsafeByteSequence readFileAsBytes(String path) throws IOException {
-        RandomAccessFile f = null;
-        try {
-            f = new RandomAccessFile(path, "r");
-            UnsafeByteSequence bytes = new UnsafeByteSequence((int) f.length());
-            byte[] buffer = new byte[8192];
-            while (true) {
-                int byteCount = f.read(buffer);
-                if (byteCount == -1) {
-                    return bytes;
-                }
-                bytes.write(buffer, 0, byteCount);
-            }
-        } finally {
-            IoUtils.closeQuietly(f);
-        }
+    public static String readFileAsString(String absolutePath) throws IOException {
+        return new FileReader(absolutePath).readFully().toString(StandardCharsets.UTF_8);
     }
 
     /**
@@ -193,4 +176,100 @@
         // TODO: set InterruptedIOException.bytesTransferred
         throw new InterruptedIOException();
     }
+
+    /**
+     * A convenience class for reading the contents of a file into a {@code String}
+     * or a {@code byte[]}. This class attempts to minimize the number of allocations
+     * and copies required to read this data.
+     *
+     * For the case where we know the "true" length of a file (most ordinary files)
+     * we allocate exactly one byte[] and copy data into that. Calls to
+     * {@link #toByteArray} will then return the internal array and <b>not</b> a copy.
+     *
+     * <b>Note that an absolute path must be supplied. Expect your reads to fail
+     * if one isn't.</b>
+     */
+    private static class FileReader {
+        private FileDescriptor fd;
+        private boolean unknownLength;
+
+        private byte[] bytes;
+        private int count;
+
+        public FileReader(String absolutePath) throws IOException {
+            // We use IoBridge.open because callers might differentiate
+            // between a FileNotFoundException and a general IOException.
+            //
+            // NOTE: This costs us an additional call to fstat(2) to test whether
+            // "absolutePath" is a directory or not. We can eliminate it
+            // at the cost of copying some code from IoBridge.open.
+            try {
+                fd = IoBridge.open(absolutePath, O_RDONLY);
+            } catch (FileNotFoundException fnfe) {
+                throw fnfe;
+            }
+
+            int capacity;
+            try {
+                final StructStat stat = Libcore.os.fstat(fd);
+                // Like RAF & other APIs, we assume that the file size fits
+                // into a 32 bit integer.
+                capacity = (int) stat.st_size;
+                if (capacity == 0) {
+                    unknownLength = true;
+                    capacity = 8192;
+                }
+            } catch (ErrnoException exception) {
+                closeQuietly(fd);
+                throw exception.rethrowAsIOException();
+            }
+
+            bytes = new byte[capacity];
+        }
+
+        public FileReader readFully() throws IOException {
+            int read;
+            int capacity = bytes.length;
+            try {
+                while ((read = Libcore.os.read(fd, bytes, count, capacity - count)) != 0) {
+                    count += read;
+                    if (count == capacity) {
+                        if (unknownLength) {
+                            // If we don't know the length of this file, we need to continue
+                            // reading until we reach EOF. Double the capacity in preparation.
+                            final int newCapacity = capacity * 2;
+                            byte[] newBytes = new byte[newCapacity];
+                            System.arraycopy(bytes, 0, newBytes, 0, capacity);
+                            bytes = newBytes;
+                            capacity = newCapacity;
+                        } else {
+                            // We know the length of this file and we've read the right number
+                            // of bytes from it, return.
+                            break;
+                        }
+                    }
+                }
+
+                return this;
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            } finally {
+                closeQuietly(fd);
+            }
+        }
+
+        @FindBugsSuppressWarnings("EI_EXPOSE_REP")
+        public byte[] toByteArray() {
+            if (count == bytes.length) {
+                return bytes;
+            }
+            byte[] result = new byte[count];
+            System.arraycopy(bytes, 0, result, 0, count);
+            return result;
+        }
+
+        public String toString(Charset cs) {
+            return new String(bytes, 0, count, cs);
+        }
+    }
 }