/*
* Copyright (C) 2011 Peransin Nicolas. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (C) 2006-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (C) 2011 Peransin Nicolas. All rights reserved.
* Use is subject to license terms.
*/
package org.mypsycho.swing.app.beans;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.mypsycho.swing.app.ApplicationContext;
import org.mypsycho.swing.app.SwingBean;
import org.mypsycho.swing.app.task.Task;
import org.mypsycho.swing.app.task.TaskService;
/**
* This class is intended to serve as the model for GUI components,
* like status bars, that display the state of an application's
* background tasks. {@code TaskMonitor} provides an overview of all
* the ApplicationContext's Tasks, as well as the state of a single
* {@code foreground} Task.
*
* <p>
* The value of {@link #getTasks getTasks()} is a list of all of the
* {@code Tasks} whose state is not {@link
* Task#isDone DONE} for all of the
* ApplicationContext's {@code TaskServices}. In other words: all of
* the ApplicationContext's background tasks that haven't finished
* executing. Each time a new TaskService Task is executed it's added
* to the list; when the Task finishes it's removed. Each time the
* list changes {@code PropertyChangeListeners} are fired.
* Applications that wish to create a detailed visualization of all
* Tasks should monitor the TaskMonitor {@code "tasks"} property.
*
* <p>
* Users are often only interested in the status of a single
* <i>foreground</i> task, typically the one associated with GUI
* element they're working with, or with the most recent command
* they've issued. The TaskMonitor's PropertyChangeListener is
* notified each time a property of the {@link #setForegroundTask
* foregroundTask} changes. Additionally the TaskMonitor fires
* synthetic PropertyChangeEvents for properties named "pending",
* "started", and "done" when the corresponding Task {@code state}
* property changes occur.
*
* <p>
* TaskMonitor manages a queue of new Tasks. The
* foregroundTask is automatically set to the first new Task, and when
* that Task finishes, the next Task in the queue, and so on.
* Applications can set the foregroundTask explicitly, to better
* reflect what the user is doing. For example, a tabbed browsing GUI
* that launched one Task per tab might set the foreground Task each
* time the user selected a tab. To prevent the foregroundTask
* property from (ever) being reset automatically, one must set {@link
* #setAutoUpdateForegroundTask autoUpdateForegroundTask} to false.
*
* <p>
* This class is not thread-safe. All of its methods must be called
* on the event dispatching thread (EDT) and all of its listeners will
* run on the EDT.
*
*
* @author Hans Muller (Hans.Muller@Sun.COM)
* @see ApplicationContext#getTaskServices
* @see TaskService#getTasks
* @see TaskService#execute
*/
public class TaskMonitor extends SwingBean {
public static final String FOREGROUND_TASK_PROP = "foregroundTask";
private final PropertyChangeListener applicationPCL;
private final PropertyChangeListener taskServicePCL;
private final PropertyChangeListener taskPCL;
private final LinkedList<Task<?, ?>> taskQueue;
private boolean autoUpdateForegroundTask = true;
private Task<?, ?> foregroundTask = null;
/**
* Construct a TaskMonitor.
* @param context
*/
public TaskMonitor(ApplicationContext context) {
applicationPCL = new ApplicationPCL();
taskServicePCL = new TaskServicePCL();
taskPCL = new TaskPCL();
taskQueue = new LinkedList<Task<?, ?>>();
context.addPropertyChangeListener(applicationPCL);
for (TaskService taskService : context.getTaskServices()) {
taskService.addPropertyChangeListener(taskServicePCL);
}
}
/**
* The TaskMonitor's PropertyChangeListeners are fired each time
* any property of the the {@code foregroundTask} changes. By
* default this property is set to the first Task to be executed
* and then, when that Task finishes, reset to the next most
* recently executed Task. If the {@code
* autoUpdateForegroundTask} is false, then the foregroundTask
* property is not reset automatically.
*
* @param foregroundTask the task whose properties are reflected by this class
* @see #setAutoUpdateForegroundTask
* @see #getForegroundTask
*/
public void setForegroundTask(Task<?, ?> foregroundTask) {
final Task<?, ?> oldTask = this.foregroundTask;
if (oldTask != null) {
oldTask.removePropertyChangeListener(taskPCL);
}
this.foregroundTask = foregroundTask;
Task<?, ?> newTask = this.foregroundTask;
if (newTask != null) {
newTask.addPropertyChangeListener(taskPCL);
}
firePropertyChange(FOREGROUND_TASK_PROP, oldTask, newTask);
}
/**
* Indicates the {@code Task} whose status the ApplicationContext's GUI wants
* to be displayed, typically in the main window's status bar.
*
*
* @return the value of the foregroundTask property.
* @see #setForegroundTask
*/
public Task<?, ?> getForegroundTask() {
return foregroundTask;
}
/**
* True if the {@code foregroundTask} property should be automatically
* reset to the oldest Task in the queue when it finishes running.
* <p>
* This property is true by default.
*
* @return true if the foregroundTask should be set automatically.
* @see #setAutoUpdateForegroundTask
* @see #setForegroundTask
*/
public boolean getAutoUpdateForegroundTask() {
return autoUpdateForegroundTask;
}
/**
* True if the {@code foregroundTask} property should be automatically
* reset to the oldest Task in the queue when it finishes running. An
* application that wants explicit control over the Task being monitored
* can set this property to false.
* <p>
* This property is true by default.
*
* @param autoUpdateForegroundTask true if the foregroundTask should be set automatically
* @see #getAutoUpdateForegroundTask
*/
public void setAutoUpdateForegroundTask(boolean autoUpdateForegroundTask) {
boolean oldValue = this.autoUpdateForegroundTask;
this.autoUpdateForegroundTask = autoUpdateForegroundTask;
firePropertyChange("autoUpdateForegroundTask", oldValue, this.autoUpdateForegroundTask);
}
private List<Task<?, ?>> copyTaskQueue() {
synchronized (taskQueue) {
if (taskQueue.isEmpty()) {
return Collections.emptyList();
} else {
return new ArrayList<Task<?, ?>>(taskQueue);
}
}
}
/**
* All of the Application Tasks whose {@code state} is not {@code DONE}.
* <p>
* Each time the list of Tasks changes, a PropertyChangeEvent for the
* property named "tasks" is fired. Applications that want to monitor all
* background Tasks should monitor the tasks property.
*
* @return a list of all Tasks that aren't {@code DONE}
*/
public List<Task<?, ?>> getTasks() {
return copyTaskQueue();
}
/* Called on the EDT, each time a TaskService's list of tasks changes,
* i.e. each time a new Task is executed and each time a Task's
* state changes to DONE.
*/
private void updateTasks(List<Task<?, ?>> oldTasks, List<Task<?, ?>> newTasks) {
boolean tasksChanged = false; // has the "tasks" property changed?
List<Task<?, ?>> oldTaskQueue = copyTaskQueue();
// Remove each oldTask that's not in the newTasks list from taskQueue
for (Task<?, ?> oldTask : oldTasks) {
if (!(newTasks.contains(oldTask))) {
if (taskQueue.remove(oldTask)) {
tasksChanged = true;
}
}
}
// Add each newTask that's not in the oldTasks list to the taskQueue
for (Task<?, ?> newTask : newTasks) {
if (!(taskQueue.contains(newTask))) {
taskQueue.addLast(newTask);
tasksChanged = true;
}
}
// Remove any tasks that are DONE for the sake of tasksChanged
for (Iterator<Task<?, ?>> tasks = taskQueue.iterator(); tasks.hasNext();) {
Task<?, ?> task = tasks.next();
if (task.isDone()) {
tasks.remove();
tasksChanged = true;
}
}
// Maybe fire the "tasks" PCLs
if (tasksChanged) {
List<Task<?, ?>> newTaskQueue = copyTaskQueue();
firePropertyChange(TaskService.TASKS_PROPERTY, oldTaskQueue, newTaskQueue);
}
if (autoUpdateForegroundTask && (getForegroundTask() == null)) {
setForegroundTask(taskQueue.isEmpty() ? null : taskQueue.getLast());
}
}
/* Each time an ApplicationContext TaskService is added or removed, we
* remove our taskServicePCL from the old ones, add it to the new
* ones. In a typical application, this will happen infrequently
* and the number of TaskServices will be small, often just one.
* This listener runs on the EDT.
*/
private class ApplicationPCL implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (ApplicationContext.TASK_SERVICES_PROPERTY.equals(propertyName)) {
@SuppressWarnings("unchecked")
List<TaskService> oldList = (List<TaskService>) e.getOldValue();
@SuppressWarnings("unchecked")
List<TaskService> newList = (List<TaskService>) e.getNewValue();
for (TaskService oldTaskService : oldList) {
oldTaskService.removePropertyChangeListener(taskServicePCL);
}
for (TaskService newTaskService : newList) {
newTaskService.addPropertyChangeListener(taskServicePCL);
}
}
}
}
/* Each time a TaskService's list of Tasks (the "tasks" property) changes,
* update the taskQueue (the "tasks" property) and possibly the
* foregroundTask property. See updateTasks().
* This listener runs on the EDT.
*/
private class TaskServicePCL implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (TaskService.TASKS_PROPERTY.equals(propertyName)) {
@SuppressWarnings("unchecked")
List<Task<?, ?>> oldList = (List<Task<?, ?>>) e.getOldValue();
@SuppressWarnings("unchecked")
List<Task<?, ?>> newList = (List<Task<?, ?>>) e.getNewValue();
updateTasks(oldList, newList);
}
}
}
/* Each time a property of the foregroundTask that's also a
* TaskMonitor property changes, update the TaskMonitor's state
* and fire a TaskMonitor ProprtyChangeEvent.
* This listener runs on the EDT.
*/
private class TaskPCL implements PropertyChangeListener {
private void fireStateChange(Task<?, ?> task, String propertyName) {
firePropertyChange(new PropertyChangeEvent(task, propertyName, false, true));
}
@Override
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
Task<?, ?> task = (Task<?, ?>) (e.getSource());
if ((task != null) && (task == getForegroundTask())) {
firePropertyChange(e);
// utility of synonyms ??
if (Task.STATE_PROP.equals(propertyName)) {
fireStateChange(task, task.getState().name().toLowerCase());
}
if (Task.PROP_COMPLETED.equals(propertyName)) {
if (autoUpdateForegroundTask) {
setForegroundTask(taskQueue.isEmpty() ? null : taskQueue.getLast());
}
}
}
}
}
}