BackgroundWorker.java

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;
            }
        }
    }
}
Valid HTML 4.01 Valid CSS