package edu.hawaii.ics.yucheng;
import java.awt.EventQueue;
/**
* An abstract class that provides the base functionality for background worker
* threads. This class works with the AWT event queue to transfer messages to
* and from the GUI and worker thread. This class is generic and supports any
* kind of progress message or result.
*/
abstract class BackgroundWorker<TResult, TProgress> {
/**
* A delegate class used to transfer messages between the GUI thread and the
* background worker thread.
*/
final class Delegate implements Runnable {
/** The action to perform. */
final int action;
/** The error data. */
final Exception error;
/** The progress data. */
final TProgress progress;
/** The result data. */
final TResult result;
/**
* Initializes a new instance of the class.
*
* @param action The action to perform.
* @param progress The progress data (optional).
* @param result The result data (optional).
* @param error The error data (optional).
*/
public Delegate(final int action, final TProgress progress,
final TResult result, final Exception error) {
this.action = action;
this.error = error;
this.result = result;
this.progress = progress;
}
/**
* The entry point for the delegate's execution. This executes in either the
* GUI thread or the background worker thread depending on the type of
* action that should be performed.
*/
public void run() {
processDelegate(this);
}
}
/** Indicates an error action. */
private final static int ACTION_ERROR = 1;
/** Indicates a progress action. */
private final static int ACTION_PROGRESS = 2;
/** Indicates a result action. */
private final static int ACTION_RESULT = 3;
/** Indicates a start action. */
private final static int ACTION_START = 4;
/** A flag indicating the thread has been canceled. */
private boolean myCancelFlag = false;
/** The running thread. */
private Thread myThread = null;
/**
* Requests that the background worker should cancel. If the background worker
* is not running, then this method has no effect.
*/
public void cancel() {
synchronized (this) {
myCancelFlag = true;
}
}
/**
* This method returns true if the GUI thread has requested that the
* background worker thread should cancel gracefully.
*
* @return True indicates the thread should cancel and false otherwise.
*/
public boolean isCanceled() {
synchronized (this) {
return myCancelFlag;
}
}
/**
* Returns true if the background worker is running and false otherwise.
*
* @return True if the background worker is running and false otherwise.
*/
public boolean isRunning() {
synchronized (this) {
return null != myThread;
}
}
/**
* Joins the current thread with the worker thread. This method returns
* immediately if the background thread is not running.
*
* @throws InterruptedException Thrown if any thread has interrupted the
* current thread. The interrupted status of the current thread is
* cleared when this exception is thrown.
*/
public void join() throws InterruptedException {
Thread thread;
synchronized (this) {
if (null == myThread)
return;
thread = myThread;
}
thread.join();
}
/**
* Processes the delegate action. This executes in either the GUI thread or
* the background worker thread depending on the type of action that should be
* performed.
*
* @param delegate The delegate that is running.
*/
void processDelegate(final Delegate delegate) {
assert null != delegate;
// Check the type of action being performed, and branch based on the action.
switch (delegate.action) {
case ACTION_ERROR:
// Process an error message in the GUI thread.
processError(delegate.error);
break;
case ACTION_PROGRESS:
// Process a progress message in the GUI thread.
processProgress(delegate.progress);
break;
case ACTION_RESULT:
// Process a result message in the GUI thread.
processResult(delegate.result);
break;
case ACTION_START:
// Handle this action in a separate method.
tryRun();
break;
}
}
/**
* When overridden by a derived class, this method processes an error thrown
* from the background worker thread. The default implementation does nothing
* but displays the error message to standard error output.
*
* @param exception The error thrown by the background worker thread.
*/
protected void processError(final Exception exception) {
System.err.println(exception);
}
/**
* When overridden by a derived class, this method processes a progress
* message sent by the background worker thread. The default implementation
* does nothing.
*
* @param progress The progress data.
*/
protected void processProgress(final TProgress progress) {
// Do nothing for the default implementation.
}
/**
* When overridden by a derived class, this method processes a result returned
* from the background worker thread. The default implementation does nothing.
*
* @param result The result from the background worker thread.
*/
protected void processResult(final TResult result) {
// Do nothing for the default implementation.
}
/**
* This method runs from a background thread after the start() method has been
* called. The value returned by this method is later processed in the
* processResult() method. If the implementation wishes to send progress
* messages to the GUI thread, it should call the sendProgress() method, which
* will trigger a progress message to arrive in the processProgress() method.
*
* @return The result from the background worker thread.
*
* @throws Exception Thrown if any kind of exception occurs during execution.
*/
protected abstract TResult run() throws Exception;
/**
* Sends a progress message from the background worker thread to the GUI
* thread. The message arrived via the processProgress() method. This method
* does not wait for the message to arrive and returns immediately.
*
* @param progress The progress message.
*/
protected void sendProgress(final TProgress progress) {
EventQueue.invokeLater(new Delegate(ACTION_PROGRESS, progress, null, null));
}
/**
* This method starts the background worker thread. This method should not be
* called unless the thread has not already started.
*
* @throws IllegalStateException Thrown if the thread has already started.
*/
public void start() {
synchronized (this) {
if (null != myThread)
throw new IllegalStateException();
myCancelFlag = false;
myThread = new Thread(new Delegate(ACTION_START, null, null, null));
myThread.start();
}
}
/**
* Executes the action delegate.
*/
private void tryRun() {
// Run the worker thread. Catch errors while running the worker thread. If
// the thread completes successfully, send back the result. Otherwise,
// send back the error. In either case, clear the thread field so the
// class can be re-executed.
try {
EventQueue.invokeLater(new Delegate(ACTION_RESULT, null, run(), null));
} catch (final Exception e) {
EventQueue.invokeLater(new Delegate(ACTION_ERROR, null, null, e));
} finally {
synchronized (this) {
myThread = null;
}
}
}
}