/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.synapse.commons.executors;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.*;
/**
* This queue implements the BlockingQueue interface. The element should implement the
* Importance interface. </p>
*
* <p> Internally Queue is implemented as a set of multiple queues corresponding to some
* fixed priorities. When inserting an element, it will be put in to one of these queues
* depending on its importance.</p>
*
* @param <E> E should implement the Importance interface.
*/
public class MultiPriorityBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E> {
/** List of queues corresponding to different priorities */
private List<InternalQueue<E>> queues;
/** Number of items in the queue */
private int count = 0;
/** Lock held by take, poll, etc */
private final ReentrantLock lock = new ReentrantLock();
/** Waiting queue for takes */
private final Condition notEmpty = lock.newCondition();
private int capacity = Integer.MAX_VALUE;
/** Algorithm for determining next queue */
private NextQueueAlgorithm<E> nextQueueAlgorithm;
/** whether fixed size queues are used */
private boolean isFixedSizeQueues;
/**
* Create a queue with the given queues. </p>
*
* <p> This method will create a Queue that accepts objects with only the priorities specified.
* If a object is submitted with a different priority it will result in an
* IllegalArgumentException. If the algorithm is null, this queue will use the
* PRRNextQueueAlgorithm.</p>
*
* @param queues list of InternalQueue to be used
* @param isFixedQueues weather fixed size queues are used
* @param algorithm algorithm for calculating next queue
*/
public MultiPriorityBlockingQueue(List<InternalQueue<E>> queues,
boolean isFixedQueues, NextQueueAlgorithm<E> algorithm) {
this.queues = queues;
this.isFixedSizeQueues = isFixedQueues;
capacity = Integer.MAX_VALUE;
if (isFixedQueues) {
capacity = 0;
for (InternalQueue<E> q : queues) {
capacity += q.getCapacity();
}
}
Collections.sort(this.queues, new Comparator<InternalQueue<E>>() {
public int compare(InternalQueue<E> o1, InternalQueue<E> o2) {
return o2.getPriority() - o1.getPriority();
}
});
for (InternalQueue<E> queue : this.queues) {
queue.setNotFullCond(lock.newCondition());
}
if (algorithm == null) {
nextQueueAlgorithm = new PRRNextQueueAlgorithm<E>();
} else {
nextQueueAlgorithm = algorithm;
}
// initialize the algorithm
nextQueueAlgorithm.init(queues);
}
/**
* Put the specified value in to the queue. The put will block until space available
* in the corresponding internal queue.
*
* @param e object that implements the Importance interface
* @throws InterruptedException
*/
public void put(E e) throws InterruptedException {
Importance i = (Importance) e;
InternalQueue<E> internalQueue = getQueueForPriority(i.getPriority());
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (internalQueue.remainingCapacity() == 0) {
internalQueue.getNotFullCond().await();
}
} catch (InterruptedException ie) {
internalQueue.getNotFullCond().signal();
throw ie;
}
internalQueue.offer(e);
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
/**
* Add the element if space available in the internal queue corresponding to the
* priority of the object.
*
* @param e element to be added
* @return true if element is added
*/
public boolean offer(E e) {
Importance i = (Importance) e;
InternalQueue<E> internalQueue = getQueueForPriority(i.getPriority());
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (internalQueue.remainingCapacity() > 0) {
internalQueue.offer(e);
count++;
notEmpty.signal();
return true;
} else {
return false;
}
} finally {
lock.unlock();
}
}
/**
* Try to add the element within the given time period. Wait the specified time for
* space to be available. This method will put the object in to the internal queue with the
* corresponding priority. This method blocks only if that internal queue is full.
*
* @param e element to be added
* @param timeout time to wait if space not available
* @param unit time unit
* @return true if the element is added
* @throws InterruptedException if the thread is interrupted
*/
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
Importance i = (Importance) e;
InternalQueue<E> internalQueue = getQueueForPriority(i.getPriority());
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
if (internalQueue.remainingCapacity() > 0) {
internalQueue.offer(e);
count++;
notEmpty.signal();
return true;
}
if (nanos <= 0)
return false;
try {
nanos = internalQueue.getNotFullCond().awaitNanos(nanos);
} catch (InterruptedException ie) {
internalQueue.getNotFullCond().signal();
throw ie;
}
}
} finally {
lock.unlock();
}
}
/**
* Get an element. Block until an element is available
* @return an element
* @throws InterruptedException if the thread is interrupted
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
InternalQueue<E> internalQueue = nextQueueAlgorithm.getNextQueue();
try {
while (internalQueue == null) {
notEmpty.await();
internalQueue = nextQueueAlgorithm.getNextQueue();
}
} catch (InterruptedException ie) {
notEmpty.signal();
throw ie;
}
E e = internalQueue.poll();
count--;
internalQueue.getNotFullCond().signal();
return e;
} finally {
lock.unlock();
}
}
/**
* Get the element from the top of the queue. If an element is not available wait
* the specified timeout.
*
* @param timeout waiting time for element to be available
* @param unit time unit
* @return an object
* @throws InterruptedException
*/
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
InternalQueue<E> internalQueue = nextQueueAlgorithm.getNextQueue();
if (internalQueue != null) {
E e = internalQueue.poll();
count--;
internalQueue.getNotFullCond().signal();
return e;
}
if (nanos <= 0)
return null;
try {
nanos = notEmpty.awaitNanos(nanos);
} catch (InterruptedException ie) {
notEmpty.signal();
throw ie;
}
}
} finally {
lock.unlock();
}
}
/**
* We always give high priority to highest priority elements. We try to drain all the
* high priority items first.
*
* @param c collection to drain the items
* @return number of elements copied
*/
public int drainTo(Collection<? super E> c) {
int count = 0;
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (InternalQueue<E> internalQueue : queues) {
count += internalQueue.drainTo(c);
}
this.count = this.count - count;
} finally {
lock.unlock();
}
return count;
}
/**
* We always give high priority to highest priotiry elements. We try to drain all the
* high priority items first.
*
* @param c collection to drain the itemd
* @param maxElements maximum elements to copy
* @return number of elements copied
*/
public int drainTo(Collection<? super E> c, int maxElements) {
int elementsCopied = 0;
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (InternalQueue<E> internalQueue : queues) {
elementsCopied += internalQueue.drainTo(c,
internalQueue.size() > (maxElements - elementsCopied) ?
(maxElements - elementsCopied) : internalQueue.size());
}
count = count - elementsCopied;
} finally {
lock.unlock();
}
return elementsCopied;
}
/**
* Block indefinitely until a object is available for retrieval.
*
* @return an object
*/
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
InternalQueue<E> internalQueue = nextQueueAlgorithm.getNextQueue();
if (internalQueue != null) {
count--;
E e = internalQueue.poll();
internalQueue.getNotFullCond().signal();
return e;
} else {
return null;
}
} finally {
lock.unlock();
}
}
public int remainingCapacity() {
return capacity - count;
}
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
InternalQueue<E> internalQueue = nextQueueAlgorithm.getNextQueue();
if (internalQueue != null) {
return internalQueue.peek();
} else {
return null;
}
} finally {
lock.unlock();
}
}
public Iterator<E> iterator() {
return new QueueIterator(toArray());
}
public int size() {
return count;
}
public boolean isEmpty() {
return count == 0;
}
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (InternalQueue<E> internalQueue : queues) {
if (internalQueue.remove(o)) {
count--;
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
public boolean contains(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (InternalQueue<E> internalQueue : queues) {
if (internalQueue.contains(o)) return true;
}
return false;
} finally {
lock.unlock();
}
}
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
StringBuffer s = new StringBuffer();
for (InternalQueue<E> internalQueue : queues) {
s.append(internalQueue.toString());
}
return s.toString();
} finally {
lock.unlock();
}
}
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (InternalQueue<E> intQueue : queues) {
intQueue.clear();
}
count = 0;
} finally {
lock.unlock();
}
}
@SuppressWarnings({"SuspiciousToArrayCall"})
public <T> T[] toArray(T[] a) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
List<E> list = new ArrayList<E>();
for (InternalQueue<E> internalQueue : queues) {
list.addAll(internalQueue);
}
return list.toArray(a);
} finally {
lock.unlock();
}
}
public Object[] toArray() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
List<E> list = new ArrayList<E>();
for (InternalQueue<E> internalQueue : queues) {
list.addAll(internalQueue);
}
return list.toArray();
} finally {
lock.unlock();
}
}
private InternalQueue<E> getQueueForPriority(int priority) {
for (InternalQueue<E> q : queues) {
if (q.getPriority() == priority) {
return q;
}
}
throw new IllegalArgumentException();
}
private class QueueIterator implements Iterator<E> {
final Object[] array;
int cursor;
int lastRet;
QueueIterator(Object[] array) {
lastRet = -1;
this.array = array;
}
public boolean hasNext() {
return cursor < array.length;
}
public E next() {
if (cursor >= array.length)
throw new NoSuchElementException();
lastRet = cursor;
return (E) array[cursor++];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
Object x = array[lastRet];
lastRet = -1;
lock.lock();
try {
for (InternalQueue<E> internalQueue : queues) {
for (Iterator<E> it = internalQueue.iterator(); it.hasNext();) {
if (it.next() == x) {
it.remove();
return;
}
}
}
} finally {
lock.unlock();
}
}
}
public List<InternalQueue<E>> getQueues() {
return queues;
}
public NextQueueAlgorithm<E> getNextQueueAlgorithm() {
return nextQueueAlgorithm;
}
public boolean isFixedSizeQueues() {
return isFixedSizeQueues;
}
}