/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Copyright (C) 2010 Illya Yalovyy. All rights reserved.
* Copyright (C) 2011 Peransin Nicolas. All rights reserved.
* Use is subject to license terms.
*/
package org.mypsycho.swing.app.task;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingUtilities;
import org.mypsycho.swing.app.SwingBean;
/**
* The service for executing tasks
* <p>
* Methods descriptions are copied from {@link ExecutorService}
* <p>
*/
public class TaskService extends SwingBean {
public static final String TASKS_PROPERTY = "tasks";
private final String name;
private final ExecutorService executorService;
private final List<Task<?, ?>> tasks;
private final PropertyChangeListener taskPCL;
/**
* Creates a new {@code TaskService}
* @param name the name of the task service
* @param executorService the executor to be used to run tasks.
*/
public TaskService(String name, ExecutorService executorService) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
if (executorService == null) {
throw new IllegalArgumentException("null executorService");
}
this.name = name;
this.executorService = executorService;
tasks = new ArrayList<Task<?, ?>>();
taskPCL = new TaskPCL();
}
/**
* Creates a new {@code TaskService} with default executor.
* The default executor is a ThreadPoolExecutor with core pool size = 3,
* maximum pool size = 10, threads live time = 1 second and queue of type
* {@link LinkedBlockingQueue}.
*/
public TaskService(String name) {
this(name, new ThreadPoolExecutor(
3, // corePool size
10, // maximumPool size
1L, TimeUnit.SECONDS, // non-core threads time to live
new LinkedBlockingQueue<Runnable>()));
}
/**
* Gets the name of this task service
* @return this task service's name
*/
public final String getName() {
return name;
}
private List<Task<?, ?>> copyTasksList() {
synchronized (tasks) {
if (tasks.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<Task<?, ?>>(tasks);
}
}
}
private class TaskPCL implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (!Task.PROP_DONE.equals(propertyName)) {
return; // sca
}
Task<?, ?> task = (Task<?, ?>) e.getSource();
if (task.isDone()) {
List<Task<?, ?>> oldTaskList, newTaskList;
synchronized (tasks) {
oldTaskList = copyTasksList();
tasks.remove(task);
task.removePropertyChangeListener(taskPCL);
newTaskList = copyTasksList();
}
firePropertyChange(TASKS_PROPERTY, oldTaskList, newTaskList);
Task.InputBlocker inputBlocker = task.getInputBlocker();
if (inputBlocker != null) {
inputBlocker.unblock();
}
}
}
}
private void maybeBlockTask(Task<?, ?> task) {
final Task.InputBlocker inputBlocker = task.getInputBlocker();
if ((inputBlocker == null) || (inputBlocker.getScope() == Task.BlockingScope.NONE)) {
return;
}
if (SwingUtilities.isEventDispatchThread()) {
inputBlocker.block();
} else {
Runnable doBlockTask = new Runnable() {
public void run() {
inputBlocker.block();
}
};
SwingUtilities.invokeLater(doBlockTask);
}
}
/* Log enough output for a developer to figure out
* what went wrong.
*/
public void failed(Object source, Throwable cause) {
System.err.println("Failure in " + name + " service during " + source);
if (cause != null) {
cause.printStackTrace(System.err);
}
}
/**
* Executes the task.
*
* @param task the task to be executed
*/
public void execute(Task<?, ?> task) {
if (task == null) {
throw new IllegalArgumentException("null task");
}
if (!task.isPending() || (task.getTaskService() != null)) {
throw new IllegalArgumentException("task has already been executed");
}
task.setTaskService(this);
// TBD: what if task has already been submitted?
List<Task<?, ?>> oldTaskList, newTaskList;
synchronized (tasks) { // out of EDT
oldTaskList = copyTasksList();
tasks.add(task);
newTaskList = copyTasksList();
task.addPropertyChangeListener(taskPCL);
}
firePropertyChange(TASKS_PROPERTY, oldTaskList, newTaskList);
maybeBlockTask(task);
executorService.execute(task);
}
/**
* Returns the list of tasks which are executing by this service
* @return the list of tasks which are executing by this service
*/
public List<Task<?, ?>> getTasks() {
return copyTasksList();
}
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* @throws SecurityException if a security manager exists and
* shutting down this ExecutorService may manipulate
* threads that the caller is not permitted to modify
* because it does not hold {@link
* java.lang.RuntimePermission}<tt>("modifyThread")</tt>,
* or the security manager's <tt>checkAccess</tt> method
* denies access.
*/
public final void shutdown() {
executorService.shutdown();
}
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks that were
* awaiting execution.
*
* <p>There are no guarantees beyond best-effort attempts to stop
* processing actively executing tasks. For example, typical
* implementations will cancel via {@link Thread#interrupt}, so any
* task that fails to respond to interrupts may never terminate.
*
* @return list of tasks that never commenced execution
* @throws SecurityException if a security manager exists and
* shutting down this ExecutorService may manipulate
* threads that the caller is not permitted to modify
* because it does not hold {@link
* java.lang.RuntimePermission}<tt>("modifyThread")</tt>,
* or the security manager's <tt>checkAccess</tt> method
* denies access.
*/
public final List<Runnable> shutdownNow() {
return executorService.shutdownNow();
}
/**
* Returns <tt>true</tt> if this executor has been shut down.
*
* @return <tt>true</tt> if this executor has been shut down
*/
public final boolean isShutdown() {
return executorService.isShutdown();
}
/**
* Returns <tt>true</tt> if all tasks have completed following shut down.
* Note that <tt>isTerminated</tt> is never <tt>true</tt> unless
* either <tt>shutdown</tt> or <tt>shutdownNow</tt> was called first.
* @return <tt>true</tt> if all tasks have completed following shut down
*/
public final boolean isTerminated() {
return executorService.isTerminated();
}
/**
* Blocks until all tasks have completed execution after a shutdown
* request, or the timeout occurs, or the current thread is
* interrupted, whichever happens first.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return <tt>true</tt> if this executor terminated and
* <tt>false</tt> if the timeout elapsed before termination
* @throws InterruptedException if interrupted while waiting
*/
public final boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return executorService.awaitTermination(timeout, unit);
}
}