package io.eguan.utils;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
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 oodrive
* @author ebredzinski
* @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.
*
*
*/
private static final class ByteBufferStack {
private final int capacity;
private final KTypeArrayDeque<ByteBuffer> stack;
private final Lock lock;
private final int directBufferMinCapacity;
private final ByteOrder byteOrder;
/**
* New instance.
*
* @param capacity
* capacity of the {@link ByteBuffer}s.
*/
ByteBufferStack(final int capacity, final int directBufferMinCapacity, final ByteOrder byteOrder) {
super();
this.capacity = capacity;
this.stack = new KTypeArrayDeque<>();
this.lock = new ReentrantLock();
this.directBufferMinCapacity = directBufferMinCapacity;
this.byteOrder = byteOrder;
}
/**
* 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 >= directBufferMinCapacity ? ByteBuffer.allocateDirect(capacity) : ByteBuffer
.allocate(capacity)).order(byteOrder);
}
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 final int directBufferMinCapacity;
private final ByteOrder byteOrder;
private final ReadWriteLock buffersLock = new ReentrantReadWriteLock();
@GuardedBy(value = "buffersLock")
private final IntObjectOpenHashMap<ByteBufferStack> buffers = new IntObjectOpenHashMap<>();
/** Singleton for an empty ByteBuffer */
private static final ByteBuffer ZERO = ByteBuffer.allocate(0);
/**
* Create a new {@link ByteBuffer} cache. The smallest buffers are not direct.
*
* @param directBufferMinCapacity
* minimal capacity of a direct buffer. Set 0 to allocate only direct buffers.
*/
public ByteBufferCache(final int directBufferMinCapacity) {
this(directBufferMinCapacity, ByteOrder.nativeOrder());
}
/**
* Create a new {@link ByteBuffer} cache. The smallest buffers are not direct.
*
* @param directBufferMinCapacity
* minimal capacity of a direct buffer. Set 0 to allocate only direct buffers.
* @param byteOrder
* order of the created {@link ByteBuffer}.
*/
public ByteBufferCache(final int directBufferMinCapacity, final ByteOrder byteOrder) {
super();
this.directBufferMinCapacity = directBufferMinCapacity;
this.byteOrder = byteOrder;
}
/**
* Allocate a new buffer
*
* @param capacity
* @return the new allocated or reused buffer. May not be filled with 0.
*/
public 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, directBufferMinCapacity, byteOrder);
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 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);
}
}
}