/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.swingui;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import ca.sqlpower.swingui.event.TaskTerminationEvent;
import ca.sqlpower.swingui.event.TaskTerminationListener;
import ca.sqlpower.util.Monitorable;
public abstract class SPSwingWorker implements Runnable, Monitorable {
private static final Logger logger = Logger.getLogger(SPSwingWorker.class);
private Throwable doStuffException;
private final SwingWorkerRegistry registry;
/**
* The core application object that is responsible for this worker. The
* original purpose for this property was to communicate back to the
* SwingWorkerRegistry which application object the work is being done for.
* For example, workers in the Wabit application that execute OLAP queries
* have this value set to the OlapQuery they're executing (similarly for SQL
* queries and QueryCache).
*/
private final Object responsibleObject;
private SPSwingWorker nextProcess;
private boolean cancelled;
private Thread thread;
private final List<TaskTerminationListener> taskTerminationListeners
= new ArrayList<TaskTerminationListener>();
private boolean started;
private boolean finished;
private int progress;
private String message;
private Integer jobSize;
/**
* Creates a new worker that will register with the given registry when it
* starts and deregister when it finishes.
*
* @param registry
* The registry to notify of task start and completion.
* @param responsibleObject
* The application object that the work is being done for. For
* example, workers in the Wabit application would typically
* provide a WabitObject and workers in the Architect would
* provide a SQLObject.
* <p>
* This value can be specified as null, which means the work is
* not being done on behalf of any particular part of the
* application.
*/
public SPSwingWorker(SwingWorkerRegistry registry, Object responsibleObject) {
if (registry == null) {
throw new NullPointerException("Null worker registry is not permitted");
}
this.registry = registry;
this.responsibleObject = responsibleObject;
}
public SPSwingWorker(SwingWorkerRegistry registry) {
this(registry, null);
}
/**
* The message that will be displayed in a dialog box if
* cleanup() throws an exception. Should be changed by the
* subclass calling setCleanupExceptionMessage
*/
private String cleanupExceptionMessage = "A problem occurred."; //$NON-NLS-1$
public final void run() {
try {
setStarted(true);
setFinished(false);
registry.registerSwingWorker(this);
thread = Thread.currentThread();
try {
doStuff();
} catch (Throwable e) {
doStuffException = e;
logger.debug(e.getStackTrace());
}
// Do not move into try block above, and too long to be a finally :-)
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
try {
cleanup();
} finally {
setFinished(true);
fireTaskFinished();
}
if (nextProcess != null) {
nextProcess.setCancelled(cancelled);
new Thread(nextProcess).start();
}
} catch (Exception e) {
SPSUtils.showExceptionDialogNoReport(cleanupExceptionMessage, e);
}
}
});
} finally {
registry.removeSwingWorker(this);
thread = null;
}
}
/**
* This gets invoked at some time after doStuff() returns.
*/
public abstract void cleanup() throws Exception;
/**
* This runs on the thread you provide. If it throws an exception, you can get it
* with getDoStuffException().
*/
public abstract void doStuff() throws Exception;
public Throwable getDoStuffException() {
return doStuffException;
}
public void setDoStuffException(Throwable e) {
doStuffException = e;
}
public String getCleanupExceptionMessage() {
return cleanupExceptionMessage;
}
public void setCleanupExceptionMessage(String cleanupExceptionMessage) {
this.cleanupExceptionMessage = cleanupExceptionMessage;
}
public synchronized boolean isCancelled() {
return cancelled;
}
/**
* Cancel this and all following tasks
* @param cancelled
*/
public synchronized void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
public SPSwingWorker getNextProcess() {
return nextProcess;
}
public void setNextProcess(SPSwingWorker nextProcess) {
logger.debug("Moving to object:" + nextProcess); //$NON-NLS-1$
this.nextProcess = nextProcess;
}
/**
* Add a TaskTerminationListener that will get notified when the worker is finished
*/
public void addTaskTerminationListener(TaskTerminationListener ttl) {
taskTerminationListeners.add(ttl);
}
public void removeTaskTerminationListener(TaskTerminationListener ttl) {
taskTerminationListeners.remove(ttl);
}
private void fireTaskFinished () {
TaskTerminationEvent tte = new TaskTerminationEvent(this);
for (int i = taskTerminationListeners.size() - 1; i >= 0; i--) {
taskTerminationListeners.get(i).taskFinished(tte);
}
}
/**
* Sets cancelled to true and interrupts the thread running this SPSwingWorker if it is not null
*/
public void kill() {
if (thread != null) {
thread.interrupt();
}
setCancelled(true);
}
public final synchronized Integer getJobSize() {
return getJobSizeImpl();
}
/**
* Override this method only if something more than using getJobSize
* and setJobSize is needed. This forces the user to go through
* a synchronized method for thread safety.
*/
protected Integer getJobSizeImpl() {
return jobSize;
}
public final synchronized void setJobSize(Integer newJobSize) {
jobSize = newJobSize;
}
public final synchronized String getMessage() {
return getMessageImpl();
}
/**
* Override this method only if something more than using getMessage
* and setMessage is needed. This forces the user to go through
* a synchronized method for thread safety.
*/
protected String getMessageImpl() {
return message;
}
public final synchronized void setMessage(String newMessage) {
message = newMessage;
}
public final synchronized int getProgress() {
return getProgressImpl();
}
/**
* Override this method only if something more than using getProgress
* and setProgress is needed. This forces the user to go through
* a synchronized method for thread safety.
*/
protected int getProgressImpl() {
return progress;
}
public final synchronized void setProgress(int newProgress) {
progress = newProgress;
}
public final synchronized void increaseProgress() {
progress++;
}
public final synchronized boolean hasStarted() {
return hasStartedImpl();
}
/**
* Override this method if something more than using hasStarted
* and setStarted is needed. This forces the user to go through
* a synchronized method for thread safety.
*/
protected boolean hasStartedImpl() {
return started;
}
public final synchronized boolean isFinished() {
return isFinishedImpl();
}
/**
* Override this method only if something more than using isFinished
* and setFinished is needed. This forces the user to go through
* a synchronized method for thread safety.
*/
protected boolean isFinishedImpl() {
return finished;
}
/**
* This only needs to be called from outside the SPSwingWorker
* if the started flag needs to be set in a place other than at
* the start of the doStuff method.
*/
protected final synchronized void setStarted(boolean started) {
this.started= started;
}
/**
* This only needs to be called from outside the SPSwingWorker
* if the finished flag needs to be set in a place other than at
* the end of the cleanup method.
*/
protected final synchronized void setFinished(boolean finished) {
this.finished = finished;
}
/**
* Returns the application object this worker is working for, or null if
* that information is not available.
*
* @return An object in the application's object model, or null.
*/
public Object getResponsibleObject() {
return responsibleObject;
}
}