/*
* Copyright (C) 2013 Google Inc.
*
* 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 interactivespaces.util.concurrency;
import com.google.common.collect.Queues;
import interactivespaces.system.InteractiveSpacesEnvironment;
import org.apache.commons.logging.Log;
import java.util.concurrent.Future;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
/**
* An event queue which will run its event handlers in First in, First Out
* order. Events can be given a priority. Lower priority values are handled
* before higher priority values.
*
* <p>
* The event queue can also be set to accepting new events or not.
*
* <p>
* Integrates into Interactive Spaces thread pools.
*
* @author Keith M. Hughes
*/
public class AcceptingPriorityEventQueue {
/**
* The base priority for the event.
*/
public static final int DEFAULT_PRIORITY = 10;
/**
* {@code true} if the queue is accepting new events.
*/
private boolean accepting;
/**
* The event queue.
*/
private PriorityBlockingQueue<Event> events = Queues.newPriorityBlockingQueue();
/**
* The queue future, used for shutting the queue down.
*/
private Future<?> queueFuture;
/**
* The space environment to run under.
*/
private InteractiveSpacesEnvironment spaceEnvironment;
/**
* The logger for errors.
*/
private Log log;
/**
* Lock for adding items to the queue.
*/
private Object acceptingMutex = new Object();
/**
* Lock for starting and stopping the queue.
*/
private Object runningMutex = new Object();
public AcceptingPriorityEventQueue(InteractiveSpacesEnvironment spaceEnvironment, Log log) {
this.spaceEnvironment = spaceEnvironment;
this.log = log;
}
/**
* Start the event queue processing events which come in.
*
* <p>
* This is independent from accepting new events.
*/
public void startup() {
synchronized (runningMutex) {
if (queueFuture == null) {
queueFuture = spaceEnvironment.getExecutorService().submit(new Runnable() {
@Override
public void run() {
processEvents();
}
});
}
}
}
/**
* Stop the event queue from processing events which come in.
*
* <p>
* This is independent from accepting new events.
*/
public void shutdown() {
synchronized (runningMutex) {
if (queueFuture != null) {
queueFuture.cancel(true);
queueFuture = null;
}
}
}
/**
* Stop accepting new events and shut down the queue.
*/
public void stopAcceptingAndShutdown() {
synchronized (runningMutex) {
setAccepting(false);
shutdown();
}
}
/**
* Is the event queue running?
*
* @return {@code true} if it is running.
*/
public boolean isRunning() {
synchronized (runningMutex) {
return queueFuture != null;
}
}
/**
* Set whether the queue is accepting new events or not.
*
* @param accepting
* {@code true} if accepting new events
*/
public void setAccepting(boolean accepting) {
synchronized (acceptingMutex) {
this.accepting = accepting;
}
}
/**
* Add a new event to the queue with the default priority
* {@link #DEFAULT_PRIORITY}.
*
* @param event
* the new event
*/
public void addEvent(Runnable event) {
addEvent(event, DEFAULT_PRIORITY);
}
/**
* Add a new event to the queue.
*
* @param event
* the new event
* @param priority
* priority of the event, lower values run first
*/
public void addEvent(Runnable event, int priority) {
synchronized (acceptingMutex) {
if (accepting) {
events.put(new Event(event, priority));
}
}
}
/**
* Process events until the event processing thread is interrupted.
*/
private void processEvents() {
try {
while (!Thread.interrupted()) {
processNextEvent();
}
} catch (InterruptedException e) {
// Don't care
}
}
/**
* Process the next event.
*
* @throws InterruptedException
*/
private void processNextEvent() throws InterruptedException {
try {
Event event = events.take();
event.run();
} catch (InterruptedException e) {
throw e;
} catch (Exception e) {
log.error("Error during event processing", e);
}
}
/**
* An event in the queue.
*
* @author Keith M. Hughes
*/
private static class Event implements Comparable<Event> {
/**
* The sequence of events to support FIFO ordering.
*/
private static final AtomicLong sequence = new AtomicLong();
/**
* The runnable for the event.
*/
private Runnable runnable;
/**
* The priority of the event.
*/
private int priority;
/**
* The sequence number to force FIFO.
*/
private long sequenceNumber;
public Event(Runnable runnable, int priority) {
this.runnable = runnable;
this.priority = priority;
this.sequenceNumber = sequence.getAndIncrement();
}
/**
* Run the wrapped runnable.
*/
public void run() {
runnable.run();
}
@Override
public int compareTo(Event o) {
int res = priority - o.priority;
if (res == 0 && this != o) {
res = (priority < o.priority) ? -1 : 1;
}
return res;
}
}
}