package com.koushikdutta.async;
import android.os.Looper;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.PriorityQueue;
public class ByteBufferList {
ArrayDeque<ByteBuffer> mBuffers = new ArrayDeque<ByteBuffer>();
ByteOrder order = ByteOrder.BIG_ENDIAN;
public ByteOrder order() {
return order;
}
public ByteBufferList order(ByteOrder order) {
this.order = order;
return this;
}
public ByteBufferList() {
}
public ByteBufferList(ByteBuffer... b) {
addAll(b);
}
public ByteBufferList(byte[] buf) {
super();
ByteBuffer b = ByteBuffer.wrap(buf);
add(b);
}
public void addAll(ByteBuffer... bb) {
for (ByteBuffer b: bb)
add(b);
}
public byte[] getAllByteArray() {
// fast path to return the contents of the first and only byte buffer,
// if that's what we're looking for. avoids allocation.
if (mBuffers.size() == 1 && mBuffers.peek().capacity() == remaining()) {
remaining = 0;
return mBuffers.remove().array();
}
byte[] ret = new byte[remaining()];
get(ret);
return ret;
}
public ByteBuffer[] getAllArray() {
ByteBuffer[] ret = new ByteBuffer[mBuffers.size()];
ret = mBuffers.toArray(ret);
mBuffers.clear();
remaining = 0;
return ret;
}
public boolean isEmpty() {
return remaining == 0;
}
private int remaining = 0;
public int remaining() {
return remaining;
}
public boolean hasRemaining() {
return remaining() > 0;
}
public int getInt() {
int ret = read(4).getInt();
remaining -= 4;
return ret;
}
public char getByteChar() {
char ret = (char)read(1).get();
remaining--;
return ret;
}
public int getShort() {
int ret = read(2).getShort();
remaining -= 2;
return ret;
}
public byte get() {
byte ret = read(1).get();
remaining--;
return ret;
}
public long getLong() {
long ret = read(8).getLong();
remaining -= 8;
return ret;
}
public void get(byte[] bytes) {
get(bytes, 0, bytes.length);
}
public void get(byte[] bytes, int offset, int length) {
if (remaining() < length)
throw new IllegalArgumentException("length");
int need = length;
while (need > 0) {
ByteBuffer b = mBuffers.peek();
int read = Math.min(b.remaining(), need);
b.get(bytes, offset, read);
need -= read;
offset += read;
if (b.remaining() == 0) {
ByteBuffer removed = mBuffers.remove();
assert b == removed;
reclaim(b);
}
}
remaining -= length;
}
public void get(ByteBufferList into, int length) {
if (remaining() < length)
throw new IllegalArgumentException("length");
int offset = 0;
while (offset < length) {
ByteBuffer b = mBuffers.remove();
int remaining = b.remaining();
if (remaining == 0) {
reclaim(b);
continue;
}
if (offset + remaining > length) {
int need = length - offset;
// this is shared between both
ByteBuffer subset = obtain(need);
subset.limit(need);
b.get(subset.array(), 0, need);
into.add(subset);
mBuffers.addFirst(b);
break;
}
else {
// this belongs to the new list
into.add(b);
}
offset += remaining;
}
remaining -= length;
}
public void get(ByteBufferList into) {
get(into, remaining());
}
public ByteBufferList get(int length) {
ByteBufferList ret = new ByteBufferList();
get(ret, length);
return ret.order(order);
}
public ByteBuffer getAll() {
if (remaining() == 0)
return EMPTY_BYTEBUFFER;
read(remaining());
return remove();
}
private ByteBuffer read(int count) {
if (remaining() < count)
throw new IllegalArgumentException("count");
ByteBuffer first = mBuffers.peek();
while (first != null && first.position() == first.limit()) {
reclaim(mBuffers.remove());
first = mBuffers.peek();
}
if (first == null) {
return EMPTY_BYTEBUFFER;
}
if (first.remaining() >= count) {
return first.order(order);
}
ByteBuffer ret = null;
int retOffset = 0;
int allocSize = 0;
// attempt to find a buffer that can fit this, and the necessary
// alloc size to not leave anything leftover in the final buffer.
for (ByteBuffer b: mBuffers) {
if (allocSize >= count)
break;
// see if this fits...
if ((ret == null || b.capacity() > ret.capacity()) && b.capacity() >= count) {
ret = b;
retOffset = allocSize;
}
allocSize += b.remaining();
}
if (ret != null && ret.capacity() > allocSize) {
// move the current contents of the target bytebuffer around to its final position
System.arraycopy(ret.array(), ret.arrayOffset() + ret.position(), ret.array(), ret.arrayOffset() + retOffset, ret.remaining());
int retRemaining = ret.remaining();
ret.position(0);
ret.limit(allocSize);
allocSize = 0;
while (allocSize < count) {
ByteBuffer b = mBuffers.remove();
if (b != ret) {
System.arraycopy(b.array(), b.arrayOffset() + b.position(), ret.array(), ret.arrayOffset() + allocSize, b.remaining());
allocSize += b.remaining();
reclaim(b);
}
else {
allocSize += retRemaining;
}
}
mBuffers.addFirst(ret);
return ret;
}
ret = obtain(count);
ret.limit(count);
byte[] bytes = ret.array();
int offset = 0;
ByteBuffer bb = null;
while (offset < count) {
bb = mBuffers.remove();
int toRead = Math.min(count - offset, bb.remaining());
bb.get(bytes, offset, toRead);
offset += toRead;
if (bb.remaining() == 0) {
reclaim(bb);
bb = null;
}
}
// if there was still data left in the last buffer we popped
// toss it back into the head
if (bb != null && bb.remaining() > 0)
mBuffers.addFirst(bb);
mBuffers.addFirst(ret);
return ret.order(order);
}
public void trim() {
// this clears out buffers that are empty in the beginning of the list
read(0);
}
public void add(ByteBuffer b) {
if (b.remaining() <= 0) {
reclaim(b);
return;
}
addRemaining(b.remaining());
mBuffers.add(b);
trim();
}
public void addFirst(ByteBuffer b) {
if (b.remaining() <= 0) {
reclaim(b);
return;
}
addRemaining(b.remaining());
mBuffers.addFirst(b);
}
private void addRemaining(int remaining) {
if (this.remaining() >= 0)
this.remaining += remaining;
}
public void recycle() {
while (mBuffers.size() > 0) {
reclaim(mBuffers.remove());
}
assert mBuffers.size() == 0;
remaining = 0;
}
public ByteBuffer remove() {
ByteBuffer ret = mBuffers.remove();
remaining -= ret.remaining();
return ret;
}
public int size() {
return mBuffers.size();
}
public void spewString() {
System.out.println(peekString());
}
// not doing toString as this is really nasty in the debugger...
public String peekString() {
StringBuilder builder = new StringBuilder();
for (ByteBuffer bb: mBuffers) {
builder.append(new String(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining()));
}
return builder.toString();
}
public String readString() {
StringBuilder builder = new StringBuilder();
while (mBuffers.size() > 0) {
ByteBuffer bb = mBuffers.remove();
builder.append(new String(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining()));
reclaim(bb);
}
remaining = 0;
return builder.toString();
}
static class Reclaimer implements Comparator<ByteBuffer> {
@Override
public int compare(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) {
// keep the smaller ones at the head, so they get tossed out quicker
if (byteBuffer.capacity() == byteBuffer2.capacity())
return 0;
if (byteBuffer.capacity() > byteBuffer2.capacity())
return 1;
return -1;
}
}
static PriorityQueue<ByteBuffer> reclaimed = new PriorityQueue<ByteBuffer>(8, new Reclaimer());
private static PriorityQueue<ByteBuffer> getReclaimed() {
if (Thread.currentThread() == Looper.getMainLooper().getThread())
return null;
return reclaimed;
}
private static final int MAX_SIZE = 1024 * 1024;
static int currentSize = 0;
static int maxItem = 0;
public static void reclaim(ByteBuffer b) {
if (b.arrayOffset() != 0 || b.array().length != b.capacity()) {
return;
}
if (b.capacity() < 8192)
return;
if (b.capacity() > 1024 * 256)
return;
PriorityQueue<ByteBuffer> r = getReclaimed();
if (r == null)
return;
synchronized (r) {
while (currentSize > MAX_SIZE && r.size() > 0 && r.peek().capacity() < b.capacity()) {
// System.out.println("removing for better: " + b.capacity());
ByteBuffer head = r.remove();
currentSize -= head.capacity();
}
if (currentSize > MAX_SIZE) {
// System.out.println("too full: " + b.capacity());
return;
}
b.position(0);
b.limit(b.capacity());
currentSize += b.capacity();
r.add(b);
assert r.size() != 0 ^ currentSize == 0;
maxItem = Math.max(maxItem, b.capacity());
}
}
public static ByteBuffer obtain(int size) {
if (size <= maxItem) {
assert Thread.currentThread() != Looper.getMainLooper().getThread();
PriorityQueue<ByteBuffer> r = getReclaimed();
if (r != null) {
synchronized (r) {
while (r.size() > 0) {
ByteBuffer ret = r.remove();
if (r.size() == 0)
maxItem = 0;
currentSize -= ret.capacity();
assert r.size() != 0 ^ currentSize == 0;
if (ret.capacity() >= size) {
// System.out.println("using " + ret.capacity());
return ret;
}
// System.out.println("dumping " + ret.capacity());
}
}
}
}
// System.out.println("alloc for " + size);
ByteBuffer ret = ByteBuffer.allocate(Math.max(8192, size));
return ret;
}
public static void obtainArray(ByteBuffer[] arr, int size) {
PriorityQueue<ByteBuffer> r = getReclaimed();
int index = 0;
int total = 0;
if (r != null) {
synchronized (r) {
while (r.size() > 0 && total < size && index < arr.length - 1) {
ByteBuffer b = r.remove();
currentSize -= b.capacity();
assert r.size() != 0 ^ currentSize == 0;
int needed = Math.min(size - total, b.capacity());
total += needed;
arr[index++] = b;
}
}
}
if (total < size) {
ByteBuffer b = ByteBuffer.allocate(Math.max(8192, size - total));
arr[index++] = b;
}
for (int i = index; i < arr.length; i++) {
arr[i] = EMPTY_BYTEBUFFER;
}
}
public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
}