Index: net/cronet/android/java/src/org/chromium/net/ChunkedWritableByteChannel.java |
diff --git a/net/cronet/android/java/src/org/chromium/net/ChunkedWritableByteChannel.java b/net/cronet/android/java/src/org/chromium/net/ChunkedWritableByteChannel.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..638f2cb5d2da87f8f5c898536dad047e79b0ddd7 |
--- /dev/null |
+++ b/net/cronet/android/java/src/org/chromium/net/ChunkedWritableByteChannel.java |
@@ -0,0 +1,125 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.net; |
+ |
+import java.io.IOException; |
+import java.nio.ByteBuffer; |
+import java.nio.channels.ClosedChannelException; |
+import java.nio.channels.WritableByteChannel; |
+import java.util.ArrayList; |
+ |
+/** |
+ * A writable byte channel that is optimized for chunked writing. Each call to |
+ * {@link #write} results in a ByteBuffer being created and remembered. Then all |
+ * of those byte buffers are combined on demand. This approach allows to avoid |
+ * the cost of reallocating a byte buffer. |
+ */ |
+public class ChunkedWritableByteChannel implements WritableByteChannel { |
+ |
+ private final ArrayList<ByteBuffer> mBuffers = new ArrayList<ByteBuffer>(); |
+ |
+ private ByteBuffer mInitialBuffer; |
+ |
+ private ByteBuffer mBuffer; |
+ |
+ private int mSize; |
+ |
+ private boolean mClosed; |
+ |
+ public void setCapacity(int capacity) { |
+ if (!mBuffers.isEmpty() || mInitialBuffer != null) { |
+ throw new IllegalStateException(); |
+ } |
+ |
+ mInitialBuffer = ByteBuffer.allocateDirect(capacity); |
+ } |
+ |
+ @Override |
+ public int write(ByteBuffer buffer) throws IOException { |
+ if (mClosed) { |
+ throw new ClosedChannelException(); |
+ } |
+ |
+ int size = buffer.remaining(); |
+ mSize += size; |
+ |
+ if (mInitialBuffer != null) { |
+ if (size <= mInitialBuffer.remaining()) { |
+ mInitialBuffer.put(buffer); |
+ return size; |
+ } |
+ |
+ // The supplied initial size was incorrect. Keep the accumulated |
+ // data |
+ // and switch to the usual "sequence of buffers" mode. |
+ mInitialBuffer.flip(); |
+ mBuffers.add(mInitialBuffer); |
+ mInitialBuffer = null; |
+ } |
+ |
+ // We can't hold a reference to this buffer, because it may wrap native |
+ // memory |
+ // and is not guaranteed to be immutable. |
+ ByteBuffer tmpBuf = ByteBuffer.allocateDirect(size); |
+ tmpBuf.put(buffer).rewind(); |
+ mBuffers.add(tmpBuf); |
+ return size; |
+ } |
+ |
+ /** |
+ * Returns the entire content accumulated by the channel as a ByteBuffer. |
+ */ |
+ public ByteBuffer getByteBuffer() { |
+ if (mInitialBuffer != null) { |
+ mInitialBuffer.flip(); |
+ mBuffer = mInitialBuffer; |
+ mInitialBuffer = null; |
+ } else if (mBuffer != null && mSize == mBuffer.capacity()) { |
+ // Cache hit |
+ } else if (mBuffer == null && mBuffers.size() == 1) { |
+ mBuffer = mBuffers.get(0); |
+ } else { |
+ mBuffer = ByteBuffer.allocateDirect(mSize); |
+ int count = mBuffers.size(); |
+ for (int i = 0; i < count; i++) { |
+ mBuffer.put(mBuffers.get(i)); |
+ } |
+ mBuffer.rewind(); |
+ } |
+ return mBuffer; |
+ } |
+ |
+ /** |
+ * Returns the entire content accumulated by the channel as a byte array. |
+ */ |
+ public byte[] getBytes() { |
+ byte[] bytes = new byte[mSize]; |
+ if (mInitialBuffer != null) { |
+ mInitialBuffer.flip(); |
+ mInitialBuffer.get(bytes); |
+ } else { |
+ int bufferCount = mBuffers.size(); |
+ int offset = 0; |
+ for (int i = 0; i < bufferCount; i++) { |
+ ByteBuffer buffer = mBuffers.get(i); |
+ int bufferSize = buffer.remaining(); |
+ buffer.get(bytes, offset, bufferSize); |
+ buffer.rewind(); |
+ offset += bufferSize; |
+ } |
+ } |
+ return bytes; |
+ } |
+ |
+ @Override |
+ public void close() { |
+ mClosed = true; |
+ } |
+ |
+ @Override |
+ public boolean isOpen() { |
+ return !mClosed; |
+ } |
+} |