package org.andengine.util.adt.queue.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.andengine.util.adt.list.CircularList; import org.andengine.util.adt.list.IList; import android.util.SparseArray; import android.util.SparseIntArray; /** * The {@link PriorityBlockingAggregatorQueue} is a thread-safe queue that internally holds multiple queues each having their own priority. * {@link #peek()}, {@link #poll()} and {@link #take()} always return the head of the highest priority internal queue. * The {@link PriorityBlockingAggregatorQueue} is i.e. useful in networking situations where different {@link Thread}s (producers) put different messages of different priority and the network {@link Thread} (consumer) should always send the highest priority message first. * * (c) 2013 Nicolas Gramlich * * @author Nicolas Gramlich * @since 21:55:23 - 08.05.2013 */ public class PriorityBlockingAggregatorQueue<T> { // =========================================================== // Constants // =========================================================== private static final int QUEUE_INITIAL_CAPACITY_DEFAULT = 10; // =========================================================== // Fields // =========================================================== private final ReentrantLock mLock; private final SparseArray<IList<T>> mQueues = new SparseArray<IList<T>>(); private final SparseIntArray mQueueCapacities = new SparseIntArray(); private final SparseArray<Condition> mNotFullConditions = new SparseArray<Condition>(); private final Condition mNotEmptyCondition; private int mSize; // =========================================================== // Constructors // =========================================================== public PriorityBlockingAggregatorQueue(final boolean pFair) { this.mLock = new ReentrantLock(pFair); this.mNotEmptyCondition = this.mLock.newCondition(); } // =========================================================== // Getter & Setter // =========================================================== public int size() { final ReentrantLock lock = this.mLock; lock.lock(); try { return this.mSize; } finally { lock.unlock(); } } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public String toString() { final ReentrantLock lock = this.mLock; lock.lock(); try { final StringBuilder stringBuilder = new StringBuilder(); if (this.mQueues.size() > 0) { final SparseArray<IList<T>> queues = this.mQueues; final SparseIntArray queueCapacities = this.mQueueCapacities; stringBuilder.append(" [\n"); final int queueCount = queues.size(); for (int i = 0; i < queueCount; i++) { final int priority = queues.keyAt(i); final IList<T> queue = queues.valueAt(i); final int queueCapacity = queueCapacities.valueAt(i); final int queueSize = queue.size(); stringBuilder.append("\tPriority: ").append(priority).append(" (Capacity: ").append(queueSize).append('/').append(queueCapacity).append("): "); stringBuilder.append(queue.toString()); if (i < (queueCount - 1)) { stringBuilder.append(','); } stringBuilder.append('\n'); } stringBuilder.append(']'); } return stringBuilder.toString(); } finally { lock.unlock(); } } // =========================================================== // Methods // =========================================================== private void insert(final int pPrority, final T pItem) { // TODO Causes another (potentially unnecessary) lookup for the queue final IList<T> queue = this.mQueues.get(pPrority); queue.add(pItem); this.mSize++; this.mNotEmptyCondition.signal(); } private T extract() { final SparseArray<IList<T>> queues = this.mQueues; final int queueCount = queues.size(); for (int i = 0; i < queueCount; i++) { final IList<T> queue = this.mQueues.valueAt(i); if (queue.size() > 0) { final int priority = this.mQueues.keyAt(i); return this.extract(priority); } } return null; } private T extract(final int pPriority) { // TODO Causes another (potentially unnecessary) lookup for the queue and the condition final Condition notFullCondition = this.mNotFullConditions.get(pPriority); final IList<T> queue = this.mQueues.get(pPriority); final T item = queue.remove(0); this.mSize--; notFullCondition.signal(); return item; } public void addQueue(final int pPriority) { this.addQueue(pPriority, Integer.MAX_VALUE); } public void addQueue(final int pPriority, final int pCapacity) { this.addQueue(pPriority, pCapacity, QUEUE_INITIAL_CAPACITY_DEFAULT); } public void addQueue(final int pPriority, final int pCapacity, final int pInitialCapacity) { if (pCapacity <= 0) { throw new IllegalArgumentException("pCapacity must be greater than 0."); } if (pInitialCapacity <= 0) { throw new IllegalArgumentException("pInitialCapacity must be greater than 0."); } final ReentrantLock lock = this.mLock; lock.lock(); try { this.mQueues.put(pPriority, new CircularList<T>(pInitialCapacity)); this.mQueueCapacities.put(pPriority, pCapacity); this.mNotFullConditions.put(pPriority, this.mLock.newCondition()); } finally { lock.unlock(); } } public T peek() { final ReentrantLock lock = this.mLock; lock.lock(); try { if (this.mSize == 0) { return null; } else { final SparseArray<IList<T>> queues = this.mQueues; final int queueCount = queues.size(); for (int i = 0; i < queueCount; i++) { final IList<T> queue = this.mQueues.valueAt(i); if (queue.size() > 0) { return queue.get(0); } } return null; } } finally { lock.unlock(); } } // public T peek(final int pPriority) { // final ReentrantLock lock = this.mLock; // lock.lock(); // // try { // final IList<T> queue = this.mQueues.get(pPriority); // if (queue == null) { // throw new IllegalArgumentException("No queue found for pPriority: '" + pPriority + "'."); // } // final int queueCapacity = this.mQueueCapacities.get(pPriority); // // if (queueCapacity == 0) { // return null; // } else { // return queue.get(0); // } // } finally { // lock.unlock(); // } // } public T poll() { final ReentrantLock lock = this.mLock; lock.lock(); try { if (this.mSize == 0) { return null; } else { return extract(); } } finally { lock.unlock(); } } // public T poll(final int pPriority) { // final ReentrantLock lock = this.mLock; // lock.lock(); // // try { // final int queueCapacity = this.mQueueCapacities.get(pPriority, -1); // if (queueCapacity == 0) { // return null; // } else { // return this.extract(pPriority); // } // } finally { // lock.unlock(); // } // } public T take() throws InterruptedException { final ReentrantLock lock = this.mLock; lock.lockInterruptibly(); try { try { while (this.mSize == 0) { this.mNotEmptyCondition.await(); } } catch (final InterruptedException e) { /* Propagate to non-interrupted thread. */ this.mNotEmptyCondition.signal(); throw e; } return this.extract(); } finally { lock.unlock(); } } // public T take(final int pPriority) throws InterruptedException { // final ReentrantLock lock = this.mLock; // lock.lockInterruptibly(); // // try { // final IList<T> queue = this.mQueues.get(pPriority); // if (queue == null) { // throw new IllegalArgumentException("No queue found for pPriority: '" + pPriority + "'."); // } // // try { // while (queue.size() == 0) { // this.mNotEmptyCondition.await(); // } // } catch (final InterruptedException e) { // /* Propagate to non-interrupted thread. */ // this.mNotEmptyCondition.signal(); // throw e; // } // return this.extract(pPriority); // } finally { // lock.unlock(); // } // } /** * Inserts the specified element at the tail of this queue with the given priority, waiting for space to become available if the queue is full. * * @throws IllegalArgumentException if pItem is <code>null</code> * @throws InterruptedException */ public void put(final int pPriority, final T pItem) throws IllegalArgumentException, InterruptedException { if (pItem == null) { throw new IllegalArgumentException("pItem must not be null."); } final ReentrantLock lock = this.mLock; final Condition notFullCondition = this.mNotFullConditions.get(pPriority); lock.lockInterruptibly(); try { final IList<T> queue = this.mQueues.get(pPriority); if (queue == null) { throw new IllegalArgumentException("No queue found for pPriority: '" + pPriority + "'."); } final int queueCapacity = this.mQueueCapacities.get(pPriority); try { while (queue.size() == queueCapacity) { notFullCondition.await(); } } catch (final InterruptedException e) { /* Propagate to non-interrupted thread. */ notFullCondition.signal(); throw e; } insert(pPriority, pItem); } finally { lock.unlock(); } } /** * Inserts the specified element at the tail of this queue with the given priority, if it is possible without exceeding the capacity of the queue with the given priority. * * @throws IllegalArgumentException if pItem is <code>null</code> * @return <code>true</code> if the */ public boolean offer(final int pPriority, final T pItem) throws IllegalArgumentException { if (pItem == null) { throw new IllegalArgumentException("pItem must not be null."); } final ReentrantLock lock = this.mLock; lock.lock(); try { final IList<T> queue = this.mQueues.get(pPriority); if (queue == null) { throw new IllegalArgumentException("No queue found for pPriority: '" + pPriority + "'."); } final int queueCapacity = this.mQueueCapacities.get(pPriority); if (queue.size() == queueCapacity) { return false; } else { insert(pPriority, pItem); return true; } } finally { lock.unlock(); } } public void clear() { final ReentrantLock lock = this.mLock; lock.lock(); try { if (this.mSize > 0) { final SparseArray<IList<T>> queues = this.mQueues; final int queueCount = queues.size(); for (int i = 0; i < queueCount; i++) { final int priority = this.mQueues.keyAt(i); final IList<T> queue = this.mQueues.valueAt(i); queue.clear(); final Condition notFullCondition = this.mNotFullConditions.get(priority); notFullCondition.signal(); } this.mSize = 0; } } finally { lock.unlock(); } } public void clear(final int pPriority) { final ReentrantLock lock = this.mLock; lock.lock(); try { final IList<T> queue = this.mQueues.get(pPriority); if (queue == null) { throw new IllegalArgumentException("No queue found for pPriority: '" + pPriority + "'."); } final int queueSize = queue.size(); queue.clear(); final Condition notFullCondition = this.mNotFullConditions.get(pPriority); notFullCondition.signal(); this.mSize -= queueSize; } finally { lock.unlock(); } } public void clearAndPut(final int pPriority, final T pItem) throws IllegalArgumentException, InterruptedException { final ReentrantLock lock = this.mLock; lock.lock(); try { this.clear(pPriority); this.put(pPriority, pItem); } finally { lock.unlock(); } } public boolean clearAndOffer(final int pPriority, final T pItem) { final ReentrantLock lock = this.mLock; lock.lock(); try { this.clear(pPriority); return this.offer(pPriority, pItem); } finally { lock.unlock(); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }