Introducing buffer allocation API. (#235)

This has been borrowed from some experimental work in protobuf and
was heavily influenced by the Netty buffer API.

This will be needed to improve the performance of the Conscrypt
engine to more closely align with Netty when using heap-based buffers.
diff --git a/common/src/main/java/org/conscrypt/AllocatedBuffer.java b/common/src/main/java/org/conscrypt/AllocatedBuffer.java
new file mode 100644
index 0000000..d246aec
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/AllocatedBuffer.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2017 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 org.conscrypt;
+
+import static org.conscrypt.Preconditions.checkNotNull;
+import static org.conscrypt.Preconditions.checkPositionIndexes;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A buffer that was allocated by a {@link BufferAllocator}. For every buffer, it is guaranteed that
+ * at least one of {@link #hasArray()} or {@link #hasNioBuffer()} will be {@code true}.
+ */
+@ExperimentalApi
+abstract class AllocatedBuffer {
+    /**
+     * Indicates whether this buffer contains a backing {@link ByteBuffer} (i.e. it is safe to call
+     * {@link #nioBuffer()}).
+     */
+    public abstract boolean hasNioBuffer();
+
+    /**
+     * Indicates whether this buffer contains a backing array (i.e. it is safe to call {@link
+     * #array()}).
+     */
+    public abstract boolean hasArray();
+
+    /**
+     * Returns the {@link ByteBuffer} that backs this buffer <i>(optional operation)</i>.
+     *
+     * <p>Call {@link #hasNioBuffer()} before invoking this method in order to ensure that this
+     * buffer has a backing {@link ByteBuffer}.
+     *
+     * @return The {@link ByteBuffer} that backs this buffer
+     * @throws UnsupportedOperationException If this buffer is not backed by a {@link ByteBuffer}.
+     */
+    public abstract ByteBuffer nioBuffer();
+
+    /**
+     * Returns the byte array that backs this buffer <i>(optional operation)</i>.
+     *
+     * <p>Call {@link #hasArray()} before invoking this method in order to ensure that this buffer
+     * has an accessible backing array.
+     *
+     * @return The array that backs this buffer
+     * @throws java.nio.ReadOnlyBufferException If this buffer is backed by an array but is
+     * read-only
+     * @throws UnsupportedOperationException If this buffer is not backed by an accessible array
+     */
+    public abstract byte[] array();
+
+    /**
+     * Returns the offset within this buffer's backing array of the first element of the buffer
+     * <i>(optional operation)</i>.
+     *
+     * <p>If this buffer is backed by an array then {@link #position()} corresponds to the array
+     * index
+     * {@link #position()} {@code +} {@link #arrayOffset()}.
+     *
+     * <p>Invoke the {@link #hasArray hasArray} method before invoking this method in order to
+     * ensure that this buffer has an accessible backing array.
+     *
+     * @return The offset within this buffer's array of the first element of the buffer
+     * @throws java.nio.ReadOnlyBufferException If this buffer is backed by an array but is
+     * read-only
+     * @throws UnsupportedOperationException If this buffer is not backed by an accessible array
+     */
+    public abstract int arrayOffset();
+
+    /**
+     * Returns this buffer's position.
+     *
+     * @return The position of this buffer
+     */
+    public abstract int position();
+
+    /**
+     * Sets this buffer's position.
+     *
+     * @param position The new position value; must be non-negative and no larger than the current
+     *     limit
+     * @return This buffer
+     * @throws IllegalArgumentException If the preconditions on {@code position} do not hold
+     */
+    public abstract AllocatedBuffer position(int position);
+
+    /**
+     * Returns this buffer's limit.
+     *
+     * @return The limit of this buffer
+     */
+    public abstract int limit();
+
+    /**
+     * Returns the number of elements between the current {@link #position()} and the {@link
+     * #limit()}
+     * .
+     *
+     * @return The number of elements remaining in this buffer
+     */
+    public abstract int remaining();
+
+    /**
+     * Creates a new {@link AllocatedBuffer} that is backed by the given array. The returned buffer
+     * will have {@link #hasArray} == {@code true}, {@link #arrayOffset()} == {@code 0}, {@link
+     * #position()} == {@code 0} and {@link #limit()} equal to the length of {@code bytes}.
+     */
+    public static AllocatedBuffer wrap(byte[] bytes) {
+        return wrap(bytes, 0, bytes.length);
+    }
+
+    /**
+     * Creates a new {@link AllocatedBuffer} that is backed by the given array. The returned buffer
+     * will have {@link #hasArray} == {@code true}, {@link #arrayOffset()} == {@code offset}, {@link
+     * #position()} == {@code 0} and {@link #limit()} == {@code length}.
+     */
+    public static AllocatedBuffer wrap(final byte[] bytes, final int offset, final int length) {
+        checkNotNull(bytes, "bytes");
+        checkPositionIndexes(offset, offset + length, bytes.length);
+        return new AllocatedBuffer() {
+            // Relative to offset.
+            private int position;
+
+            @Override
+            public boolean hasNioBuffer() {
+                return false;
+            }
+
+            @Override
+            public ByteBuffer nioBuffer() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean hasArray() {
+                return true;
+            }
+
+            @Override
+            public byte[] array() {
+                return bytes;
+            }
+
+            @Override
+            public int arrayOffset() {
+                return offset;
+            }
+
+            @Override
+            public int position() {
+                return position;
+            }
+
+            @Override
+            public AllocatedBuffer position(int position) {
+                if (position < 0 || position > length) {
+                    throw new IllegalArgumentException("Invalid position: " + position);
+                }
+                this.position = position;
+                return this;
+            }
+
+            @Override
+            public int limit() {
+                // Relative to offset.
+                return length;
+            }
+
+            @Override
+            public int remaining() {
+                return length - position;
+            }
+        };
+    }
+
+    /**
+     * Creates a new {@link AllocatedBuffer} that is backed by the given {@link ByteBuffer}. The
+     * returned buffer will have {@link #hasNioBuffer} == {@code true}.
+     */
+    public static AllocatedBuffer wrap(final ByteBuffer buffer) {
+        checkNotNull(buffer, "buffer");
+
+        return new AllocatedBuffer() {
+
+            @Override
+            public boolean hasNioBuffer() {
+                return true;
+            }
+
+            @Override
+            public ByteBuffer nioBuffer() {
+                return buffer;
+            }
+
+            @Override
+            public boolean hasArray() {
+                return buffer.hasArray();
+            }
+
+            @Override
+            public byte[] array() {
+                return buffer.array();
+            }
+
+            @Override
+            public int arrayOffset() {
+                return buffer.arrayOffset();
+            }
+
+            @Override
+            public int position() {
+                return buffer.position();
+            }
+
+            @Override
+            public AllocatedBuffer position(int position) {
+                buffer.position(position);
+                return this;
+            }
+
+            @Override
+            public int limit() {
+                return buffer.limit();
+            }
+
+            @Override
+            public int remaining() {
+                return buffer.remaining();
+            }
+        };
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/BufferAllocator.java b/common/src/main/java/org/conscrypt/BufferAllocator.java
new file mode 100644
index 0000000..f178965
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/BufferAllocator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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 org.conscrypt;
+
+import java.nio.ByteBuffer;
+
+/**
+ * An object responsible for allocation of buffers. This is an extension point to enable buffer
+ * pooling within an application.
+ */
+@ExperimentalApi
+abstract class BufferAllocator {
+    private static BufferAllocator UNPOOLED = new BufferAllocator() {
+        @Override
+        public AllocatedBuffer allocateHeapBuffer(int capacity) {
+            return AllocatedBuffer.wrap(new byte[capacity]);
+        }
+
+        @Override
+        public AllocatedBuffer allocateDirectBuffer(int capacity) {
+            return AllocatedBuffer.wrap(ByteBuffer.allocateDirect(capacity));
+        }
+    };
+
+    /**
+     * Returns an unpooled buffer allocator, which will create a new buffer for each request.
+     */
+    public static BufferAllocator unpooled() {
+        return UNPOOLED;
+    }
+
+    /**
+     * Allocates a buffer with the given capacity that is backed by an array on the heap.
+     */
+    public abstract AllocatedBuffer allocateHeapBuffer(int capacity);
+
+    /**
+     * Allocates a direct (i.e. non-heap) buffer with the given capacity.
+     */
+    public abstract AllocatedBuffer allocateDirectBuffer(int capacity);
+}
diff --git a/common/src/main/java/org/conscrypt/Preconditions.java b/common/src/main/java/org/conscrypt/Preconditions.java
index 719f790..39fe712 100644
--- a/common/src/main/java/org/conscrypt/Preconditions.java
+++ b/common/src/main/java/org/conscrypt/Preconditions.java
@@ -40,6 +40,10 @@
 
     /**
      * Ensures the truth of an expression involving one or more parameters to the calling method.
+     *
+     * @param condition to condition to be tested
+     * @param errorMessage the exception message to use if the check fails.
+     * @throws IllegalArgumentException if the condition is {@code false}
      */
     static void checkArgument(boolean condition, String errorMessage) {
         if (!condition) {
@@ -49,10 +53,56 @@
 
     /**
      * Ensures the truth of an expression involving one or more parameters to the calling method.
+     *
+     * @param condition to condition to be tested
+     * @param errorMessageTemplate the format string to be passed to {@link String#format(String,
+     * Object...)}
+     * @param arg the format argument to be passed to {@link String#format(String, Object...)}
+     * @throws IllegalArgumentException if the condition is {@code false}
      */
     static void checkArgument(boolean condition, String errorMessageTemplate, Object arg) {
         if (!condition) {
             throw new IllegalArgumentException(String.format(errorMessageTemplate, arg));
         }
     }
+
+    /**
+     * Ensures that {@code start} and {@code end} specify a valid <i>positions</i> in an array, list
+     * or string of size {@code size}, and are in order. A position index may range from zero to
+     * {@code size}, inclusive.
+     *
+     * @param start a user-supplied index identifying a starting position in an array, list or string
+     * @param end a user-supplied index identifying a ending position in an array, list or string
+     * @param size the size of that array, list or string
+     * @throws IndexOutOfBoundsException if either index is negative or is greater than {@code size},
+     *     or if {@code end} is less than {@code start}
+     * @throws IllegalArgumentException if {@code size} is negative
+     */
+    static void checkPositionIndexes(int start, int end, int size) {
+        // Carefully optimized for execution by hotspot (explanatory comment above)
+        if (start < 0 || end < start || end > size) {
+            throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
+        }
+    }
+
+    private static String badPositionIndexes(int start, int end, int size) {
+        if (start < 0 || start > size) {
+            return badPositionIndex(start, size, "start index");
+        }
+        if (end < 0 || end > size) {
+            return badPositionIndex(end, size, "end index");
+        }
+        // end < start
+        return String.format("end index (%s) must not be less than start index (%s)", end, start);
+    }
+
+    private static String badPositionIndex(int index, int size, String desc) {
+        if (index < 0) {
+            return String.format("%s (%s) must not be negative", desc, index);
+        } else if (size < 0) {
+            throw new IllegalArgumentException("negative size: " + size);
+        } else { // index > size
+            return String.format("%s (%s) must not be greater than size (%s)", desc, index, size);
+        }
+    }
 }