/*
* $Id$
*
* Copyright (c) 2009 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools.swing;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import VASSAL.tools.concurrent.RunnableFuture;
/**
* An {@link ExecutorService} which submits to Event Dispatch Thread.
*
* @author Joel Uckelman
* @since 3.2.0
* @see EDTRunnableFuture
*/
public class EDTExecutorService extends AbstractExecutorService {
private final AtomicBoolean shutdown = new AtomicBoolean(false);
private final ReentrantLock lock = new ReentrantLock();
/** {@inheritDoc} */
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
// Wait for the poison pill to finish.
lock.lock();
try {
if (isTerminated()) return true;
poison_pill.get(timeout, unit);
return true;
}
catch (TimeoutException e) {
return false;
}
catch (CancellationException e) {
// Should not happen, the poision pill is never cancelled.
throw new IllegalStateException(e);
}
catch (ExecutionException e) {
// Should not happen, the poision pill runs no code.
throw new IllegalStateException(e);
}
finally {
lock.unlock();
}
}
/** {@inheritDoc} */
public boolean isShutdown() {
return shutdown.get();
}
/** {@inheritDoc} */
public boolean isTerminated() {
return shutdown.get() && poison_pill.isDone();
}
/** {@inheritDoc} */
public void shutdown() {
lock.lock();
try {
if (shutdown.get()) return;
submit(poison_pill);
shutdown.set(true);
}
finally {
lock.unlock();
}
}
/** {@inheritDoc} */
public List<Runnable> shutdownNow() {
shutdown();
return Collections.<Runnable>emptyList();
}
// FIXME: rename to newTaskFor(), mark as @Override in Java 1.6+
/** {@inheritDoc} */
protected <T> RunnableFuture<T> newTask(final Callable<T> cable) {
return new EDTRunnableFuture<T>() {
protected void runOnEDT() throws Exception {
result = cable.call();
}
};
}
// FIXME: rename to newTaskFor(), mark as @Override in Java 1.6+
/** {@inheritDoc} */
protected <T> RunnableFuture<T> newTask(final Runnable rable, T result) {
return new EDTRunnableFuture<T>(result) {
protected void runOnEDT() {
rable.run();
}
};
}
// FIXME: remove for Java 1.6+
/** {@inheritDoc} */
@Override
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
final RunnableFuture<Void> ftask = newTask(task, null);
execute(ftask);
return ftask;
}
// FIXME: remove for Java 1.6+
/** {@inheritDoc} */
@Override
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
final RunnableFuture<T> ftask = newTask(task, result);
execute(ftask);
return ftask;
}
// FIXME: remove for Java 1.6+
/** {@inheritDoc} */
@Override
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
final RunnableFuture<T> ftask = newTask(task);
execute(ftask);
return ftask;
}
/**
* Submits a {@code EDTRunnableFuture} task for execution and returns it.
*
* @param task the task to submit
* @return the task which was submitted
* @throws RejectedExecutionException if the task cannot be scheduled for
* execution
* @throws NullPointerException if the task is {@code null}
*/
public <T> EDTRunnableFuture<T> submit(EDTRunnableFuture<T> task) {
execute(task);
return task;
}
// FIXME: remove for Java 1.6+
/** {@inheritDoc} */
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null) throw new NullPointerException();
final List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) {
final RunnableFuture<T> f = newTask(t);
futures.add(f);
execute(f);
}
for (Future<T> f : futures) {
if (!f.isDone()) {
try {
f.get();
}
catch (CancellationException ignore) {
}
catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
}
finally {
if (!done) {
for (Future<T> f : futures) f.cancel(true);
}
}
}
// FIXME: remove for Java 1.6+
/** {@inheritDoc} */
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
if (tasks == null) throw new NullPointerException();
if (unit == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
final List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) futures.add(newTask(t));
long lastTime = System.nanoTime();
// Interleave time checks and calls to execute in case
// executor doesn't have any/much parallelism.
final Iterator<Future<T>> it = futures.iterator();
while (it.hasNext()) {
execute((Runnable)(it.next()));
final long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
if (nanos <= 0) return futures;
}
for (Future<T> f : futures) {
if (!f.isDone()) {
if (nanos <= 0) return futures;
try {
f.get(nanos, TimeUnit.NANOSECONDS);
}
catch (CancellationException ignore) {
}
catch (ExecutionException ignore) {
}
catch (TimeoutException toe) {
return futures;
}
final long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
}
done = true;
return futures;
}
finally {
if (!done) {
for (Future<T> f : futures) f.cancel(true);
}
}
}
/** {@inheritDoc} */
public void execute(Runnable r) {
if (r == null) throw new NullPointerException();
lock.lock();
try {
if (shutdown.get()) throw new RejectedExecutionException();
EventQueue.invokeLater(r);
}
finally {
lock.unlock();
}
}
// The poision pill task used for shutting down the ExecutorService
protected final EDTRunnableFuture<Void> poison_pill =
new EDTRunnableFuture<Void>() {
protected void runOnEDT() {}
};
}