package com.laytonsmith.PureUtilities;
import com.laytonsmith.PureUtilities.Common.StreamUtils;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* This class manages execution queues. A task added to a queue
* is guaranteed to be sequential with respect to other tasks in that
* queue, but not necessarily with respect to other tasks on other queues.
* Tasks will block the queue.
*
*/
public class ExecutionQueue {
private ExecutorService service;
private static int threadCount = 0;
private Map<String, Deque<Runnable>> queues;
private final Map<String, Object> locks;
private Map<String, Boolean> runningQueues;
private String defaultQueueName;
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null;
private ThreadFactory threadFactory;
public ExecutionQueue(String threadPrefix, String defaultQueueName){
this(threadPrefix, defaultQueueName, null);
}
/**
* Creates a new ExecutionQueue instance.
* @param threadPrefix The prefix to use when naming the threads
* @param defaultQueueName The name of the default queue
* @param exceptionHandler The uncaught exception handler for these queues
* @throws NullPointerException if either threadPrefix or defaultQueueName are null
*/
public ExecutionQueue(final String threadPrefix, String defaultQueueName, final Thread.UncaughtExceptionHandler exceptionHandler){
if(threadPrefix == null || defaultQueueName == null){
throw new NullPointerException();
}
uncaughtExceptionHandler = exceptionHandler;
threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, threadPrefix + "-" + (++threadCount));
t.setDaemon(false);
return t;
}
};
queues = new HashMap<String, Deque<Runnable>>();
this.defaultQueueName = defaultQueueName;
locks = new HashMap<String, Object>();
runningQueues = new HashMap<String, Boolean>();
}
public final void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler exceptionHandler){
this.uncaughtExceptionHandler = exceptionHandler;
}
/**
* Pushes a new runnable onto the end of the specified queue
* @param queue The named queue
* @param r
*/
public final void push(DaemonManager dm, String queue, Runnable r){
queue = prepareLock(queue);
synchronized(locks.get(queue)){
Deque<Runnable> q = prepareQueue(queue);
q.addLast(r);
startQueue(dm, queue);
}
}
/**
* Pushes a new element to the front of the queue, barring other calls
* to pushFront, this runnable will go next.
* @param queue
* @param r
*/
public final void pushFront(DaemonManager dm, String queue, Runnable r){
queue = prepareLock(queue);
synchronized(locks.get(queue)){
Deque<Runnable> q = prepareQueue(queue);
q.addFirst(r);
startQueue(dm, queue);
}
}
/**
* Removes the last element added to the back of the queue
* @param queue
*/
public final void remove(String queue){
queue = prepareLock(queue);
synchronized(locks.get(queue)){
Deque<Runnable> q = prepareQueue(queue);
try{
q.removeLast();
} catch(NoSuchElementException e){
//
}
}
}
/**
* Removes the front element from the queue
* @param queue
*/
public final void removeFront(String queue){
try{
pop(queue);
} catch(NoSuchElementException e){
//
}
}
/**
* Clears all elements from this queue
* @param queue
*/
public final void clear(String queue){
queue = prepareLock(queue);
synchronized(locks.get(queue)){
prepareQueue(queue).clear();
}
}
/**
* Returns true if this queue has elements on the queue,
* or is currently running one.
* @param queue
* @return
*/
public final boolean isRunning(String queue){
queue = prepareLock(queue);
synchronized(locks.get(queue)){
return runningQueues.containsKey(queue) && runningQueues.get(queue).equals(true);
}
}
/**
* Returns a list of active queues; that is, isRunning will
* return true for all these queues.
* @return
*/
public final List<String> activeQueues(){
List<String> q = new ArrayList<String>();
for(String queue : queues.keySet()){
synchronized(locks.get(queue)){
if(queues.containsKey(queue) && !queues.get(queue).isEmpty()){
q.add(queue);
}
}
}
return q;
}
/**
* Sets up a queue initially
* @param queueName
*/
private Deque<Runnable> prepareQueue(String queueName){
if(!queues.containsKey(queueName)){
queues.put(queueName, new ArrayDeque<Runnable>());
}
return queues.get(queueName);
}
private String prepareLock(String queueName){
if(queueName == null){
queueName = defaultQueueName;
}
if(!locks.containsKey(queueName)){
locks.put(queueName, new Object());
}
return queueName;
}
/**
* Destroys a no-longer-in-use queue
* @param queueName
*/
private void destroyQueue(String queueName){
synchronized(locks.get(queueName)){
queues.remove(queueName);
}
}
/**
* This method actually runs the queue management
* @param queueName
*/
private void pumpQueue(String queueName){
while(true){
Runnable r;
synchronized(locks.get(queueName)){
r = pop(queueName);
}
r.run();
synchronized(locks.get(queueName)){
if(queues.get(queueName).isEmpty()){
runningQueues.put(queueName, false);
destroyQueue(queueName);
break;
}
}
}
}
private synchronized void startQueue(final DaemonManager dm, final String queue){
synchronized(locks.get(queue)){
if(!isRunning(queue)){
//We need to create a new thread
runningQueues.put(queue, true);
if(dm != null){
dm.activateThread(null);
}
if(service == null){
service = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
50L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
service.submit(new Runnable() {
@Override
public void run() {
try{
pumpQueue(queue);
} catch(RuntimeException t){
if(uncaughtExceptionHandler != null){
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), t);
} else {
StreamUtils.GetSystemErr().println("The queue \"" + queue + "\" threw an exception, and it was not handled.");
t.printStackTrace(StreamUtils.GetSystemErr());
}
} finally {
if(dm != null){
dm.deactivateThread(null);
}
}
}
});
}
}
}
private Runnable pop(String queue) throws NoSuchElementException{
queue = prepareLock(queue);
synchronized(locks.get(queue)){
Deque<Runnable> q = queues.get(queue);
if (q == null) {
throw new NoSuchElementException("The given queue does not exist.");
}
return q.removeFirst();
}
}
/**
* Stops all executing tasks on a best effort basis.
*/
public synchronized void stopAllNow(){
if(service != null){
service.shutdownNow();
service = null;
}
}
/**
* Attempts an orderly shutdown of all existing tasks.
*/
public synchronized void stopAll(){
service.shutdown();
service = null;
}
}