package jalse.actions; import static jalse.actions.Actions.requireNotShutdown; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * An abstract implementation of {@link ActionEngine} that is designed to be used with * {@link ExecutorService}. This is a convenience class for creating an {@link ActionEngine}. <br> * <br> * If the engine is paused the incoming resumed state change can be awaited ( * {@link #awaitResumed()}). * * @author Elliot Ford * * @see #TERMINATION_TIMEOUT * @see DefaultActionBindings */ public abstract class AbstractActionEngine implements ActionEngine { /** * How long the engine will wait until it times out and interrupts running threads on shutdown * (configured via {@code jalse.actions.termination_timeout} system property). */ public static final long TERMINATION_TIMEOUT = Long .valueOf(System.getProperty("jalse.actions.termination_timeout", "2000")); private static final Logger logger = Logger.getLogger(AbstractActionEngine.class.getName()); /** * Executor service to be used for action scheduling. */ protected final ExecutorService executorService; private final ActionBindings bindings; private final Lock lock; private final Condition resumed; private final AtomicBoolean paused; /** * Creates a new instance of AbstractActionEngine with the supplied executor service. * * @param executorService * Service to use. * * @see Actions#requireNotShutdown(ExecutorService) */ protected AbstractActionEngine(final ExecutorService executorService) { this.executorService = requireNotShutdown(executorService); bindings = new DefaultActionBindings(); lock = new ReentrantLock(); resumed = lock.newCondition(); paused = new AtomicBoolean(); } /** * Will wait until the engine has resumed (or stopped). * * @throws InterruptedException * If the current thread was interrupted. */ protected void awaitResumed() throws InterruptedException { lock.lockInterruptibly(); try { while (isPaused()) { resumed.await(); } } finally { lock.unlock(); } } @Override public ActionBindings getBindings() { return bindings; } @Override public boolean isPaused() { return paused.get() && !isStopped(); } @Override public boolean isStopped() { return executorService.isShutdown(); } @Override public void pause() { requireNotShutdown(executorService); if (!paused.getAndSet(true)) { logger.info("Engine paused"); } } @Override public void resume() { requireNotShutdown(executorService); if (paused.getAndSet(false)) { lock.lock(); try { resumed.signalAll(); // Wake up and work! } finally { lock.unlock(); } logger.info("Engine resumed"); } } @Override public void stop() { requireNotShutdown(executorService); executorService.shutdown(); try { if (!executorService.awaitTermination(TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) { executorService.shutdownNow(); // Uh-oh } } catch (final InterruptedException e) { logger.log(Level.WARNING, "Error terminating executor", e); Thread.currentThread().interrupt(); } logger.info("Engine shutdown"); } }