package org.threadly.litesockets.buffers;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import org.threadly.util.ArgumentVerifier;
/**
* This class is used to combine multiple ByteBuffers into 1 simplish to use interface.
* It provides most of the features of a single ByteBuffer, but with the ability to perform those
* operations spanning many ByteBuffers.
*
* The idea here is to keep from having to copy around and merge ByteBuffers as much as possible.
*
* NOTE: This is not threadSafe. It should only be accessed by 1 thread at a time.
* A single Client's onRead() callback is only called on 1 thread at a time.
*
*/
public class ReuseableMergedByteBuffers extends AbstractMergedByteBuffers {
protected final ArrayDeque<ByteBuffer> availableBuffers = new ArrayDeque<ByteBuffer>(8);
protected int currentSize;
protected long consumedSize;
public ReuseableMergedByteBuffers() {
this(true);
}
public ReuseableMergedByteBuffers(boolean readOnly) {
super(readOnly);
}
public ReuseableMergedByteBuffers(boolean readOnly, ByteBuffer ...bbs) {
super(readOnly, bbs);
}
@Override
protected void doAppend(final ByteBuffer bb) {
availableBuffers.add(bb);
currentSize+=bb.remaining();
}
@Override
public ReuseableMergedByteBuffers duplicate() {
final ReuseableMergedByteBuffers mbb = new ReuseableMergedByteBuffers(markReadOnly);
for(final ByteBuffer bb: this.availableBuffers) {
mbb.add(bb.duplicate());
}
return mbb;
}
@Override
public ReuseableMergedByteBuffers duplicateAndClean() {
final ReuseableMergedByteBuffers mbb = new ReuseableMergedByteBuffers(markReadOnly);
mbb.add(this);
return mbb;
}
@Override
protected void addToFront(ByteBuffer bb) {
this.availableBuffers.addFirst(bb.duplicate());
this.currentSize+=bb.remaining();
}
@Override
public int remaining() {
return currentSize;
}
@Override
public boolean hasRemaining() {
return currentSize > 0;
}
@Override
public byte get() {
if(currentSize == 0){
throw new BufferUnderflowException();
}
final ByteBuffer buf = availableBuffers.peek();
// we assume that we have at least one byte in any available buffers
final byte result = buf.get();
if (! buf.hasRemaining()) {
removeFirstBuffer();
}
currentSize--;
consumedSize++;
return result;
}
@Override
public void get(byte[] destBytes, int start, int length) {
ArgumentVerifier.assertNotNull(destBytes, "byte[]");
if (currentSize < length) {
throw new BufferUnderflowException();
}
doGet(destBytes);
consumedSize += destBytes.length;
currentSize -= destBytes.length;
}
@Override
public int nextBufferSize() {
if (currentSize == 0) {
return 0;
}
return availableBuffers.peekFirst().remaining();
}
@Override
public ByteBuffer popBuffer() {
if (currentSize == 0) {
return EMPTY_BYTEBUFFER;
}
return pullBuffer(availableBuffers.peekFirst().remaining());
}
@Override
public ByteBuffer pullBuffer(final int size) {
ArgumentVerifier.assertNotNegative(size, "size");
if (size == 0) {
return EMPTY_BYTEBUFFER;
}
if (currentSize < size) {
throw new BufferUnderflowException();
}
consumedSize += size;
currentSize -= size;
final ByteBuffer first = availableBuffers.peek();
if(first.remaining() == size) {
return removeFirstBuffer().slice();
} else if(first.remaining() > size) {
final ByteBuffer bb = first.duplicate().slice();
bb.limit(bb.position()+size);
first.position(first.position()+size);
return bb;
} else {
final byte[] result = new byte[size];
doGet(result);
return ByteBuffer.wrap(result);
}
}
@Override
public void discard(final int size) {
ArgumentVerifier.assertNotNegative(size, "size");
if (currentSize < size) {
throw new BufferUnderflowException();
}
//We have logic here since we dont need to do any copying and we just drop the bytes
int toRemoveAmount = size;
while (toRemoveAmount > 0) {
final ByteBuffer buf = availableBuffers.peek();
final int bufRemaining = buf.remaining();
if (bufRemaining > toRemoveAmount) {
buf.position(buf.position() + toRemoveAmount);
toRemoveAmount = 0;
} else {
removeFirstBuffer();
toRemoveAmount -= bufRemaining;
}
}
consumedSize += size;
currentSize -= size;
}
protected ByteBuffer removeFirstBuffer() {
return this.availableBuffers.pollFirst();
}
private void doGet(final byte[] destBytes) {
doGet(destBytes, 0, destBytes.length);
}
private void doGet(final byte[] destBytes, int start, int len) {
int remainingToCopy = len;
while (remainingToCopy > 0) {
final ByteBuffer buf = availableBuffers.peek();
final int toCopy = Math.min(buf.remaining(), remainingToCopy);
buf.get(destBytes, start+destBytes.length-remainingToCopy, toCopy);
remainingToCopy -= toCopy;
if (! buf.hasRemaining()) {
removeFirstBuffer();
}
}
}
@Override
public long getTotalConsumedBytes() {
return consumedSize;
}
@Override
public boolean isAppendable() {
return true;
}
@Override
public String toString() {
return "MergedByteBuffer size:"+currentSize+": queueSize"+availableBuffers.size()+": consumed:"+consumedSize;
}
@Override
protected byte get(int pos) {
int currentPos = 0;
for(ByteBuffer bb: this.availableBuffers) {
if(bb.remaining() > pos-currentPos) {
return bb.get(pos-currentPos);
} else {
currentPos+=bb.remaining();
}
}
return 0;
}
}