/**
* Copyright 2014 Ricardo Padilha
*
* 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.
*/
package net.dsys.snio.impl.buffer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
/**
* Behaves like an ArrayBlockingQueue, but both {@link #put(Object)} and
* {@link #take()} can be interrupted. Loosely follows the contract of
* {@link BlockingQueue}.
*
* @author Ricardo Padilha
*/
final class BlockingBuffer<T> {
private final int mask;
private final Tuple<T>[] values;
private final Lock lock;
private final Condition notEmpty;
private final Condition notFull;
private long putIndex;
private long takeIndex;
private int count;
private InterruptedException interruptPut;
private InterruptedException interruptTake;
/**
* Creates a buffer with the given (fixed) capacity and default access
* policy.
*
* @param capacity
* the capacity of this queue
* @throws IllegalArgumentException
* if {@code capacity < 1}
*/
BlockingBuffer(@Nonnegative final int capacity) {
this(capacity, false);
}
/**
* Creates a buffer with the given (fixed) capacity and the specified access
* policy.
*
* @param capacity
* the capacity of this buffer
* @param fair
* if {@code true} then queue accesses for threads blocked on
* insertion or removal, are processed in FIFO order; if
* {@code false} the access order is unspecified.
* @throws IllegalArgumentException
* if {@code capacity < 1}
*/
BlockingBuffer(@Nonnegative final int capacity, final boolean fair) {
if (capacity < 1) {
throw new IllegalArgumentException("capacity < 1");
}
if (Integer.bitCount(capacity) > 1) {
// not a power of two
throw new IllegalArgumentException("capacity must be a power of two");
}
this.mask = capacity - 1;
@SuppressWarnings("unchecked")
final Tuple<T>[] values = new Tuple[capacity];
this.values = values;
this.lock = new ReentrantLock(fair);
this.notEmpty = lock.newCondition();
this.notFull = lock.newCondition();
}
/**
* @see BlockingQueue#put(Object)
*/
void put(@Nonnull final Tuple<T> value) throws InterruptedException {
if (value == null) {
throw new NullPointerException("value == null");
}
lock.lockInterruptibly();
try {
while (count == values.length && interruptPut == null) {
notFull.await();
}
if (interruptPut != null) {
final InterruptedException ex = interruptPut;
interruptPut = null;
Thread.currentThread().interrupt();
throw ex;
}
final int index = (int) (putIndex & mask);
values[index] = value;
++putIndex;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
/**
* Interrupt threads blocked in {@link #put(Object)} or
* {@link #put(Object, long, TimeUnit)} and throws the given exception.
*/
void interruptPut(@Nonnull final InterruptedException e) {
if (e == null) {
throw new NullPointerException("e == null");
}
lock.lock();
try {
interruptPut = e;
notFull.signalAll();
} finally {
lock.unlock();
}
}
/**
* @see BlockingQueue#take()
*/
@Nonnull
Tuple<T> take() throws InterruptedException {
lock.lockInterruptibly();
try {
while (count == 0 && interruptTake == null) {
notEmpty.await();
}
if (interruptTake != null) {
final InterruptedException ex = interruptTake;
interruptTake = null;
Thread.currentThread().interrupt();
throw ex;
}
final int index = (int) (takeIndex & mask);
final Tuple<T> value = values[index];
values[index] = null;
++takeIndex;
--count;
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
/**
* Interrupt threads blocked in {@link #take()} or
* {@link #poll(long, TimeUnit)} and throws the given exception.
*/
void interruptTake(@Nonnull final InterruptedException e) {
if (e == null) {
throw new NullPointerException("e == null");
}
lock.lock();
try {
interruptTake = e;
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
/**
* @return the capacity of this buffer
*/
@Nonnegative
int capacity() {
return values.length;
}
/**
* @return the number of elements in this buffer
*/
@Nonnegative
int size() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
/**
* @return {@link #size()} == 0
* @see java.util.Collection#isEmpty()
*/
boolean isEmpty() {
return size() == 0;
}
/**
* @see BlockingQueue#remainingCapacity()
*/
@Nonnegative
int remainingCapacity() {
lock.lock();
try {
return values.length - count;
} finally {
lock.unlock();
}
}
/**
* @author Ricardo Padilha
*/
static final class Tuple<T> {
private static final Object DUMMY_ATTACHMENT = new Object();
private final T value;
private Object attachment;
Tuple(@Nonnull final T value) {
if (value == null) {
throw new NullPointerException("value == null");
}
this.value = value;
this.attachment = DUMMY_ATTACHMENT;
}
Tuple(@Nonnull final T value, @Nonnull final Object attachment) {
if (value == null) {
throw new NullPointerException("value == null");
}
if (attachment == null) {
throw new NullPointerException("attachment == null");
}
this.value = value;
this.attachment = attachment;
}
@Nonnull T getValue() {
return value;
}
@Nonnull Object getAttachment() {
return attachment;
}
void setAttachment(@Nonnull final Object attachment) {
this.attachment = attachment;
}
}
}