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; } } } }