package org.jscsi.utils;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.concurrent.GuardedBy;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
import com.carrotsearch.hppc.KTypeArrayDeque;
import com.carrotsearch.hppc.predicates.KTypePredicate;
/**
* Handles allocation and reuse of {@link ByteBuffer}. Allocates direct or non-direct {@link ByteBuffer}s depending on
* the size of the buffer.
*
* @author llambert
*
*/
public final class ByteBufferCache {
// private static final Logger LOGGER = LoggerFactory.getLogger(ByteBufferCache.class);
/**
* Associate a queue of {@link ByteBuffer} and an exclusive lock.
*
* @author llambert
*
*/
private static final class ByteBufferStack {
private final int capacity;
private final KTypeArrayDeque<ByteBuffer> stack;
private final Lock lock;
/**
* New instance.
*
* @param capacity
* capacity of the {@link ByteBuffer}s.
*/
ByteBufferStack(final int capacity) {
super();
this.capacity = capacity;
this.stack = new KTypeArrayDeque<>();
this.lock = new ReentrantLock();
}
/**
* Gets an old buffer or create a new one.
*
* @return a usage {@link ByteBuffer}
*/
final ByteBuffer pop() {
lock.lock();
try {
if (!stack.isEmpty()) {
// LOGGER.warn("Reuse capacity=" + capacity);
final ByteBuffer result = (ByteBuffer) stack.removeFirst().clear();
return result;
}
}
finally {
lock.unlock();
}
// LOGGER.warn("Allocate capacity=" + capacity);
return capacity >= DIRECT_BUFFER_MIN_CAPACITY ? ByteBuffer.allocateDirect(capacity) : ByteBuffer
.allocate(capacity);
}
final void push(final ByteBuffer buffer) {
lock.lock();
try {
assert !containsObject(buffer);
stack.addLast(buffer);
}
finally {
lock.unlock();
}
}
/**
* Check if the stack contains the buffer. <code>Do NOT</code> call <code>stack.contains()</code> which compares
* the contents of the buffers.
*
* @param buffer
* @return true if stack contains the buffer
*/
private final boolean containsObject(final ByteBuffer buffer) {
final AtomicBoolean result = new AtomicBoolean();
stack.forEach(new KTypePredicate<ByteBuffer>() {
@Override
public final boolean apply(final ByteBuffer value) {
if (value == buffer) {
result.set(true);
return false;
}
return true;
}
});
return result.get();
}
}
// Allocate byte arrays for small buffers
private static final int DIRECT_BUFFER_MIN_CAPACITY = 4 * 1024;
private static ReadWriteLock buffersLock = new ReentrantReadWriteLock();
@GuardedBy(value = "buffersLock")
private static IntObjectOpenHashMap<ByteBufferStack> buffers = new IntObjectOpenHashMap<>();
static {
// Most requested sizes
buffers.put(48, new ByteBufferStack(48));
buffers.put(16, new ByteBufferStack(16));
}
/**
* No instance.
*/
private ByteBufferCache() {
throw new Error();
}
/** Singleton for an empty ByteBuffer */
private static final ByteBuffer ZERO = ByteBuffer.allocate(0);
/**
* Allocate a new buffer and release the given one.
*
* @param releaseBuffer
* buffer to release
* @param capacity
* capacity of the buffer to allocate
* @return the new allocated or reused buffer. May not be filled with 0.
*/
public static final ByteBuffer allocate(final ByteBuffer releaseBuffer, final int capacity) {
release(releaseBuffer);
return allocate(capacity);
}
/**
* Allocate a new buffer
*
* @param capacity
* @return the new allocated or reused buffer. May not be filled with 0.
*/
public static final ByteBuffer allocate(final int capacity) {
if (capacity == 0) {
return ZERO;
}
// Look for the associated stack
ByteBufferStack stack;
buffersLock.readLock().lock();
try {
stack = buffers.get(capacity);
}
finally {
buffersLock.readLock().unlock();
}
if (stack == null) {
// Need to allocate a new stack
buffersLock.writeLock().lock();
try {
stack = buffers.get(capacity);
if (stack == null) {
stack = new ByteBufferStack(capacity);
buffers.put(capacity, stack);
}
}
finally {
buffersLock.writeLock().unlock();
}
}
return stack.pop();
}
/**
* Release a buffer and make it available for reuse.
*
* @param buffer
* buffer to release
*/
public static final void release(final ByteBuffer buffer) {
if (buffer != null && buffer != ZERO) {
final int capacity = buffer.capacity();
// LOGGER.warn("Release capacity=" + capacity);
final ByteBufferStack stack;
buffersLock.readLock().lock();
try {
stack = buffers.get(capacity);
}
finally {
buffersLock.readLock().unlock();
}
assert stack != null;
stack.push(buffer);
}
}
}