/* * 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()); } } } } } }