package org.threadly.litesockets.buffers;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* This allows you to used MergedByteBuffers in a transactional way. This can be useful when reading
* from the socket using a protocol that is not framed.
*
* NOTE: If you are modifying the data in the underlying byteArrays that will not be reverted
*
* @author lwahlmeier
*
*/
public class TransactionalByteBuffers extends ReuseableMergedByteBuffers {
private static final String ACCESS_ERROR = "Can not call method from different thread then the transaction begain with";
private final ReentrantLock lock = new ReentrantLock();
private final ArrayDeque<ByteBuffer> consumedBuffers = new ArrayDeque<ByteBuffer>(8);
private int consumedSinceBegin;
public TransactionalByteBuffers() {
super();
}
public TransactionalByteBuffers(boolean readOnly) {
super(readOnly);
}
/**
* This mark the beginning of a new transaction. anything done from this point can either
* be committed or rolled back and forgotten.
*
* If an operation is done with out using begin it is committed immediately on completion.
*
*/
public void begin() {
lock.lock();
consumedSinceBegin = 0;
consumedBuffers.clear();
}
/**
* Commit a already started transaction. Until this is done anything pulled out of the consolidator can be reverted.
* once this happens all changes are permanent and forever.
*
*
*/
public void commit() {
if(!lock.isLocked()) {
return;
}
if (! lock.isHeldByCurrentThread()) {
throw new IllegalStateException("Must call by same Thread as begin!");
}
consumedSinceBegin = 0;
consumedBuffers.clear();
lock.unlock();
}
/**
* This allows you to roll back all operations done on the consolidator since the begin was called.
*
* NOTE: If you are modifying the data in the underlying byteArrays that will not be reverted
*/
public void rollback() {
if(!lock.isLocked()) {
return;
}
if (! lock.isHeldByCurrentThread()) {
throw new IllegalStateException("Must call by same Thread as begin!");
}
try {
currentSize += consumedSinceBegin;
final ByteBuffer firstAvailable = availableBuffers.peek();
if (firstAvailable != null && firstAvailable.position() != 0) {
final int firstRollbackAmount = Math.min(consumedSinceBegin, firstAvailable.position());
firstAvailable.position(firstAvailable.position() - firstRollbackAmount);
consumedSinceBegin -= firstRollbackAmount;
}
while (consumedSinceBegin > 0) {
final ByteBuffer buf = consumedBuffers.removeLast();
final int rollBackAmount = Math.min(consumedSinceBegin, buf.capacity());
buf.position(buf.capacity() - rollBackAmount);
availableBuffers.addFirst(buf);
consumedSinceBegin -= rollBackAmount;
}
if(! consumedBuffers.isEmpty()) {
throw new IllegalStateException("Problems when trying to roll back ByteBuffers");
}
} finally {
lock.unlock();
}
}
@Override
public byte get() {
if(lock.isLocked()) {
if(lock.isHeldByCurrentThread()) {
final byte b = super.get();
consumedSinceBegin+=1;
return b;
} else {
throw new IllegalStateException(ACCESS_ERROR);
}
} else {
return super.get();
}
}
@Override
public void get(final byte[] destBytes) {
if(lock.isLocked()) {
if(lock.isHeldByCurrentThread()) {
super.get(destBytes);
consumedSinceBegin+=destBytes.length;
} else {
throw new IllegalStateException(ACCESS_ERROR);
}
} else {
super.get(destBytes);
}
}
@Override
public ByteBuffer pullBuffer(final int size) {
if(lock.isLocked()) {
if(lock.isHeldByCurrentThread()) {
final ByteBuffer bb = super.pullBuffer(size);
consumedSinceBegin+=size;
return bb;
} else {
throw new IllegalStateException(ACCESS_ERROR);
}
} else {
return super.pullBuffer(size);
}
}
@Override
public void discard(final int size) {
if(lock.isLocked()) {
if(lock.isHeldByCurrentThread()) {
super.discard(size);
consumedSinceBegin+=size;
} else {
throw new IllegalStateException(ACCESS_ERROR);
}
} else {
super.discard(size);
}
}
@Override
protected ByteBuffer removeFirstBuffer() {
final ByteBuffer bb = super.removeFirstBuffer();
if(lock.isLocked() && lock.isHeldByCurrentThread()) {
this.consumedBuffers.add(bb);
}
return bb;
}
}