/*
* Copyright (c) 2011 Stiftung Deutsches Elektronen-Synchrotron,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
*
* THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
* WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
* IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
* CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
* NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
* DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
* OR MODIFICATIONS.
* THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
* USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
* PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
* AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
*/
package org.csstudio.domain.common.collection;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* A non-blocking circular buffer queue with limited size and random access
* to its elements.
*
* Copied from {@link java.util.concurrent.ArrayBlockingQueue}, but is
* non blocking (all blocking methods are removed or replaced by their
* non-blocking counterparts, hence building a circular buffer,
* adhering to the {@link Queue} interface.
*
* Additionally a public accessor to the underlying
* array is provided to permit random access on contained elements.
*
* @author Doug Lea (of the original {@link java.util.concurrent.ArrayBlockingQueue}
* class.
* @param <E> the type of elements held in this collection
*/
public class LimitedArrayCircularQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
/**
* Serialization ID. This class relies on default serialization
* even for the _items array, which is default-serialized, even if
* it is empty. Otherwise it could not be declared final, which is
* necessary here.
*/
private static final long serialVersionUID = -5231254513294942267L;
/** The queued _items */
private E[] _items;
/** _items index for next take, poll or remove */
private int _takeIndex;
/** _items index for next put, offer, or add. */
private int _putIndex;
/** Number of _items in the queue */
private int _count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main _lock guarding all access */
private final ReentrantLock _lock;
/** Condition for waiting takes */
private final Condition _notEmpty;
/** Condition for waiting puts */
private final Condition _notFull;
/**
* Creates an <code>{@link LimitedArrayCircularQueue}</code> with the given (fixed)
* capacity and default access policy.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if <code>capacity</code> is less than 1
*/
public LimitedArrayCircularQueue(final int capacity) {
this(capacity, false);
}
/**
* Creates an <code>{@link LimitedArrayCircularQueue}</code> with the given (fixed)
* capacity and the specified access policy.
*
* @param capacity the capacity of this queue
* @param fair if <code>true</code> then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if <code>false</code> the access order is unspecified.
* @throws IllegalArgumentException if <code>capacity</code> is less than 1
*/
@SuppressWarnings("unchecked")
public LimitedArrayCircularQueue(final int capacity, final boolean fair) {
if (capacity <= 0) {
throw new IllegalArgumentException();
}
_items = (E[]) new Object[capacity];
_lock = new ReentrantLock(fair);
_notEmpty = _lock.newCondition();
_notFull = _lock.newCondition();
}
/**
* Creates an <code>{@link LimitedArrayCircularQueue}</code> with the given (fixed)
* capacity, the specified access policy and initially containing the
* elements of the given collection,
* added in traversal order of the collection's iterator.
*
* @param capacity the capacity of this queue
* @param fair if <code>true</code> then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if <code>false</code> the access order is unspecified.
* @param c the collection of elements to initially contain
* @throws IllegalArgumentException if <code>capacity</code> is less than
* <code>c.size()</code>, or less than 1.
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
public LimitedArrayCircularQueue(final int capacity,
final boolean fair,
@Nonnull final Collection<? extends E> c) {
this(capacity, fair);
if (capacity < c.size()) {
throw new IllegalArgumentException();
}
for (final E name : c) {
add(name);
}
}
/**
* Circularly increment i.
*/
private int inc(final int i) {
final int ipp = i+1;
return ipp == _items.length ? 0 : ipp;
}
/**
* Inserts element at current put position, perhaps overriding the
* element on tail, advances, and signals.
* Call only when holding _lock.
*/
private void insert(@Nonnull final E x) {
if (_putIndex == _takeIndex) {
if (_count == 0) {
++_count; // the yet empty corner case
} else {
_takeIndex = inc(_takeIndex); // the circular override corner case
}
} else {
++_count; // the normal case
}
_items[_putIndex] = x;
_putIndex = inc(_putIndex);
_notEmpty.signal();
}
/**
* Extracts element at current take position, advances, and signals.
* Call only when holding _lock.
*/
@Nonnull
private E extract() {
final E[] items = _items;
final E x = items[_takeIndex];
items[_takeIndex] = null;
_takeIndex = inc(_takeIndex);
--_count;
_notFull.signal();
return x;
}
public int getCapacity() {
return _items.length;
}
public void setCapacity(final int newCapacity) {
_lock.lock();
try {
@SuppressWarnings("unchecked")
final E[] newItems = (E[]) new Object[newCapacity];
while (_count > newCapacity) {
extract(); // remove all old samples that wouldn't have space in new items array
}
final int min = Math.min(_count, newCapacity);
for (int i = 0; i < min; i++) {
newItems[i] = extract();
}
_items = newItems;
_count = min;
_putIndex = min % _items.length;
_takeIndex = 0;
} finally {
_lock.unlock();
}
}
/**
* Inserts the specified element at the tail of this queue, potentially overriding
* another element - it's a non-blocking circular buffer!
*
* @param e the element to add
* @return <code>true</code> if adding succeeded
*/
@Override
public boolean add(@Nonnull final E e) {
return offer(e);
}
/**
* Inserts the specified element at the tail of this queue, potentially overriding
* another element - it's a non-blocking circular buffer!
*
* @param e the element to add
* @return <code>true</code> if adding/offering succeeded
*/
@Override
public boolean offer(@Nonnull final E e) {
_lock.lock();
try {
insert(e);
return true;
} finally {
_lock.unlock();
}
}
@Override
@CheckForNull
public E poll() {
_lock.lock();
try {
if (_count == 0) {
return null;
}
return extract();
} finally {
_lock.unlock();
}
}
@Override
@CheckForNull
public E peek() {
_lock.lock();
try {
return get(0);
} finally {
_lock.unlock();
}
}
/**
* Retrieves the element on position i.
* @param i
* @return the element on the position or <code>null</code> when i > size
* @throws IllegalArgumentException on i smaller 0
*/
@CheckForNull
public E get(final int i) {
if (i < 0) {
throw new IllegalArgumentException("Index is smaller than 0.");
}
_lock.lock();
try {
if (i >= _count) {
return null;
}
final int circI = (_takeIndex + i) % _items.length;
return _items[circI];
} finally {
_lock.unlock();
}
}
/**
* Returns the number of elements in this queue.
*
* @return the number of elements in this queue
*/
@Override
public int size() {
_lock.lock();
try {
return _count;
} finally {
_lock.unlock();
}
}
/**
* Guarantueed to throw an {@link UnsupportedOperationException}.
*/
@Override
public boolean removeAll(@Nonnull final Collection<?> c) {
throw new UnsupportedOperationException("Circular buffer mustn't remove arbitrary elements.");
}
/**
* Guarantueed to throw an {@link UnsupportedOperationException}.
*/
@Override
public boolean remove(@Nonnull final Object o) {
throw new UnsupportedOperationException("Circular buffer mustn't remove arbitrary elements.");
}
/**
* Guarantueed to throw an {@link UnsupportedOperationException}.
*/
@Override
public boolean retainAll(@Nonnull final Collection<?> c) {
throw new UnsupportedOperationException("Circular buffer mustn't remove arbitrary elements.");
}
/**
* Returns the number of additional elements that this queue can ideally
* (in the absence of memory or resource constraints) accept without
* overriding. This is always equal to the initial capacity of this queue
* less the current <code>size</code> of this queue.
*
* <p>Note that you <em>cannot</em> always tell if an attempt to insert
* an element will succeed by inspecting <code>remainingCapacity</code>
* because it may be the case that another thread is about to
* insert or remove an element.
*/
public int remainingCapacity() {
_lock.lock();
try {
return _items.length - _count;
} finally {
_lock.unlock();
}
}
/**
* Returns <code>true</code> if this queue contains the specified element.
* More formally, returns <code>true</code> if and only if this queue contains
* at least one element <code>e</code> such that <code>o.equals(e)</code>.
*
* @param o object to be checked for containment in this queue
* @return <code>true</code> if this queue contains the specified element
*/
@Override
public boolean contains(@Nonnull final Object o) {
final E[] items = _items;
_lock.lock();
if (_count <= 0 || !o.getClass().isAssignableFrom(items[_takeIndex].getClass())) {
return false;
}
try {
int i = _takeIndex;
int k = 0;
while (k++ < _count) {
if (o.equals(items[i])) {
return true;
}
i = inc(i);
}
return false;
} finally {
_lock.unlock();
}
}
/**
* Returns an array containing all of the elements in this queue, in
* proper sequence.
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this queue. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this queue
*/
@Override
@Nonnull
public Object[] toArray() {
final E[] items = _items;
_lock.lock();
try {
final Object[] a = new Object[_count];
int k = 0;
int i = _takeIndex;
while (k < _count) {
a[k++] = items[i];
i = inc(i);
}
return a;
} finally {
_lock.unlock();
}
}
/**
* Returns an array containing all of the elements in this queue, in
* proper sequence; the runtime type of the returned array is that of
* the specified array. If the queue fits in the specified array, it
* is returned therein. Otherwise, a new array is allocated with the
* runtime type of the specified array and the size of this queue.
*
* <p>If this queue fits in the specified array with room to spare
* (i.e., the array has more elements than this queue), the element in
* the array immediately following the end of the queue is set to
* <code>null</code>.
*
* <p>Like the {@link #toArray()} method, this method acts as bridge between
* array-based and collection-based APIs. Further, this method allows
* precise control over the runtime type of the output array, and may,
* under certain circumstances, be used to save allocation costs.
*
* <p>Suppose <code>x</code> is a queue known to contain only strings.
* The following code can be used to dump the queue into a newly
* allocated array of <code>String</code>:
*
* <pre>
* String[] y = x.toArray(new String[0]);</pre>
*
* Note that <code>toArray(new Object[0])</code> is identical in function to
* <code>toArray()</code>.
*
* @param a the array into which the elements of the queue are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose
* @return an array containing all of the elements in this queue
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in
* this queue
*/
@SuppressWarnings("unchecked")
@Override
@Nonnull
public <T> T[] toArray(@Nonnull final T[] a) {
final E[] items = _items;
_lock.lock();
try {
T[] array = a;
if (array.length < _count) {
array = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(),
_count);
}
int k = 0;
int i = _takeIndex;
while (k < _count) {
array[k++] = (T) items[i];
i = inc(i);
}
if (array.length > _count) {
array[_count] = null;
}
return array;
} finally {
_lock.unlock();
}
}
@Override
@Nonnull
public String toString() {
_lock.lock();
try {
return super.toString();
} finally {
_lock.unlock();
}
}
/**
* Atomically removes all of the elements from this queue.
* The queue will be empty after this call returns.
*/
@Override
public void clear() {
final E[] items = _items;
_lock.lock();
try {
int i = _takeIndex;
int k = _count;
while (k-- > 0) {
items[i] = null;
i = inc(i);
}
_count = 0;
_putIndex = 0;
_takeIndex = 0;
_notFull.signalAll();
} finally {
_lock.unlock();
}
}
/**
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
public int drainTo(@Nonnull final Collection<? super E> c) {
if (c == this) {
throw new IllegalArgumentException();
}
final E[] items = _items;
_lock.lock();
try {
int i = _takeIndex;
int n = 0;
final int max = _count;
while (n < max) {
c.add(items[i]);
items[i] = null;
i = inc(i);
++n;
}
if (n > 0) {
_count = 0;
_putIndex = 0;
_takeIndex = 0;
_notFull.signalAll();
}
return n;
} finally {
_lock.unlock();
}
}
/**
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
public int drainTo(@Nonnull final Collection<? super E> c,
final int maxElements) {
if (c == this) {
throw new IllegalArgumentException();
}
if (maxElements <= 0) {
return 0;
}
final E[] items = _items;
_lock.lock();
try {
int i = _takeIndex;
int n = 0;
final int max = maxElements < _count ? maxElements : _count;
while (n < max) {
c.add(items[i]);
items[i] = null;
i = inc(i);
++n;
}
if (n > 0) {
_count -= n;
_takeIndex = i;
_notFull.signalAll();
}
return n;
} finally {
_lock.unlock();
}
}
/**
* Returns an iterator over the elements in this queue in proper sequence.
* The returned <code>Iterator</code> is a "weakly consistent" iterator that
* will never throw {@link java.util.ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon
* construction of the iterator, and may (but is not guaranteed to)
* reflect any modifications subsequent to construction.
*
* @return an iterator over the elements in this queue in proper sequence
*/
@Override
@Nonnull
public Iterator<E> iterator() {
_lock.lock();
try {
return new Itr();
} finally {
_lock.unlock();
}
}
/**
* Iterator for {@link LimitedArrayCircularQueue}.
* see {@link java.util.concurrent.ArrayBlockingQueue.Itr}
*/
@SuppressWarnings("synthetic-access")
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by next,
* or a negative number if no such.
*/
private int _nextIndex;
/**
* _nextItem holds on to item fields because once we claim
* that an element exists in hasNext(), we must return it in
* the following next() call even if it was in the process of
* being removed when hasNext() was called.
*/
private E _nextItem;
/**
* Constructor.
*/
Itr() {
if (_count == 0) {
_nextIndex = -1;
} else {
_nextIndex = _takeIndex;
_nextItem = _items[_takeIndex];
}
}
@Override
public boolean hasNext() {
/*
* No sync. We can return true by mistake here
* only if this iterator passed across threads,
* which we don't support anyway.
*/
return _nextIndex >= 0;
}
/**
* Checks whether _nextIndex is valid; if so setting _nextItem.
* Stops iterator when either hits _putIndex or sees null item.
*/
private void checkNext() {
if (_nextIndex == _putIndex) {
_nextIndex = -1;
_nextItem = null;
} else {
_nextItem = _items[_nextIndex];
if (_nextItem == null) {
_nextIndex = -1;
}
}
}
@Override
@Nonnull
public E next() {
final ReentrantLock lock = LimitedArrayCircularQueue.this._lock;
lock.lock();
try {
if (_nextIndex < 0) {
throw new NoSuchElementException();
}
final E x = _nextItem;
_nextIndex = inc(_nextIndex);
checkNext();
return x;
} finally {
lock.unlock();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("Removing from the circular buffer is not permitted.");
}
}
}