package com.koushikdutta.async; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Iterator; import java.util.LinkedList; import junit.framework.Assert; public class ByteBufferList implements Iterable<ByteBuffer> { LinkedList<ByteBuffer> mBuffers = new LinkedList<ByteBuffer>(); ByteOrder order = ByteOrder.BIG_ENDIAN; public ByteOrder order() { return order; } public ByteBufferList order(ByteOrder order) { this.order = order; return this; } public ByteBuffer peek() { return mBuffers.peek(); } public ByteBufferList() { } public ByteBufferList(ByteBuffer... b) { for (ByteBuffer bb: b) add(bb); } public ByteBufferList(byte[] buf) { super(); ByteBuffer b = ByteBuffer.wrap(buf); add(b); } public ByteBuffer[] toArray() { ByteBuffer[] ret = new ByteBuffer[mBuffers.size()]; ret = mBuffers.toArray(ret); return ret; } public int remaining() { int ret = 0; for (ByteBuffer bb: mBuffers) { ret += bb.remaining(); } return ret; } public int getInt() { return read(4).getInt(); } public char getByteChar() { return (char)read(1).get(); } public int getShort() { return read(2).getShort(); } public byte get() { return read(1).get(); } public long getLong() { return read(8).getLong(); } public void get(byte[] bytes) { read(bytes.length).get(bytes); } public ByteBufferList get(int length) { Assert.assertTrue(remaining() >= length); ByteBufferList ret = new ByteBufferList(); int offset = 0; for (ByteBuffer b: mBuffers) { int remaining = b.remaining(); if (remaining == 0) continue; // done if (offset > length) break; if (offset + remaining > length) { int need = length - offset; // this is shared between both ret.add(ByteBuffer.wrap(b.array(), b.arrayOffset() + b.position(), need)); b.position(b.position() + need); } else { // this belongs to the new list ret.add(ByteBuffer.wrap(b.array(), b.arrayOffset() + b.position(), remaining)); b.position(b.limit()); } offset += remaining; } return ret.order(order); } public ByteBuffer read(int count) { Assert.assertTrue(count <= remaining()); ByteBuffer first = mBuffers.peek(); while (first != null && first.position() == first.limit()) { mBuffers.remove(); first = mBuffers.peek(); } if (first == null) { return ByteBuffer.wrap(new byte[0]).order(order); } if (first.remaining() >= count) { return first.order(order); } else { // reallocate the count into a single buffer, and return it byte[] bytes = new byte[count]; 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; } Assert.assertNotNull(bb); // if there was still data left in the last buffer we popped // toss it back into the head if (bb.position() < bb.limit()) mBuffers.add(0, bb); ByteBuffer ret = ByteBuffer.wrap(bytes); mBuffers.add(0, ret); return ret.order(order); } } public void trim() { // this clears out buffers that are empty in the beginning of the list read(0); if (remaining() == 0) mBuffers = new LinkedList<ByteBuffer>(); } public void add(ByteBuffer b) { if (b.remaining() <= 0) return; mBuffers.add(b); trim(); } public void add(int location, ByteBuffer b) { mBuffers.add(location, b); } public void add(ByteBufferList b) { if (b.remaining() <= 0) return; mBuffers.addAll(b.mBuffers); trim(); } public void clear() { mBuffers.clear(); } public ByteBuffer remove() { return mBuffers.remove(); } public int size() { return mBuffers.size(); } @Override public Iterator<ByteBuffer> iterator() { return mBuffers.iterator(); } 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: this) { builder.append(new String(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining())); } return builder.toString(); } }