Java/Threads/Swing Thread

Материал из Java эксперт
Версия от 18:01, 31 мая 2010; (обсуждение)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

An abstract class to perform lengthy GUI-interacting tasks in a dedicated thread.

 
/* 
 * $Id: SwingWorker.java,v 1.4 2006/10/19 21:03:49 evanx Exp $
 * 
 * Copyright � 2005 Sun Microsystems, Inc. All rights
 * reserved. Use is subject to license terms.
 */

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.SwingUtilities;
/**
 * An abstract class to perform lengthy GUI-interacting tasks in a
 * dedicated thread.
 * 
 * <p>
 * When writing a multi-threaded application using Swing, there are
 * two constraints to keep in mind:
 * (refer to 
 *  for more details):
 * <ul>
 *   <li> Time-consuming tasks should not be run on the <i>Event
 *        Dispatch Thread</i>. Otherwise the application becomes unresponsive.
 *   </li>
 *   <li> Swing components should be accessed  on the <i>Event
 *        Dispatch Thread</i> only.
 *   </li>
 * </ul>
 *
 * <p>
 *
 * <p>
 * These constraints mean that a GUI application with time intensive
 * computing needs at least two threads:  1) a thread to perform the lengthy
 * task and 2) the <i>Event Dispatch Thread</i> (EDT) for all GUI-related
 * activities.  This involves inter-thread communication which can be
 * tricky to implement.
 *
 * <p>
 * {@code SwingWorker} is designed for situations where you need to have a long 
 * running task run in a background thread and provide updates to the UI 
 * either when done, or while processing. 
 * Subclasses of {@code SwingWorker} must implement 
 * the {@see #doInBackground} method to perform the background computation.
 *
 *
 * <p>
 * <b>Workflow</b>
 * <p>
 * There are three threads involved in the life cycle of a 
 * {@code SwingWorker} :
 * <ul>
 * <li>
 * <p>
 * <i>Current</i> thread: The {@link #execute} method is
 * called on this thread. It schedules {@code SwingWorker} for the execution on a
 * <i>worker</i>
 * thread and returns immediately. One can wait for the {@code SwingWorker} to
 * complete using the {@link #get get} methods.
 * <li>
 * <p>
 * <i>Worker</i> thread: The {@link #doInBackground} 
 * method is called on this thread.
 * This is where all background activities should happen. To notify
 * {@code PropertyChangeListeners} about bound properties changes use the
 * {@link #firePropertyChange firePropertyChange} and
 * {@link #getPropertyChangeSupport} methods. By default there are two bound
 * properties available: {@code state} and {@code progress}.
 * <li>
 * <p>
 * <i>Event Dispatch Thread</i>:  All Swing related activities occur
 * on this thread. {@code SwingWorker} invokes the
 * {@link #process process} and {@link #done} methods and notifies
 * any {@code PropertyChangeListeners} on this thread.
 * </ul>
 * 
 * <p>
 * Often, the <i>Current</i> thread is the <i>Event Dispatch
 * Thread</i>. 
 *
 *
 * <p>
 * Before the {@code doInBackground} method is invoked on a <i>worker</i> thread,
 * {@code SwingWorker} notifies any {@code PropertyChangeListeners} about the
 * {@code state} property change to {@code StateValue.STARTED}.  After the
 * {@code doInBackground} method is finished the {@code done} method is
 * executed.  Then {@code SwingWorker} notifies any {@code PropertyChangeListeners}
 * about the {@code state} property change to {@code StateValue.DONE}.
 *
 * <p>
 * {@code SwingWorker} is only designed to be executed once.  Executing a
 * {@code SwingWorker} more than once will not result in invoking the
 * {@code doInBackground} method twice.
 *
 * <p>
 * <b>Sample Usage</b>
 * <p>
 * The following example illustrates the simplest use case.  Some 
 * processing is done in the background and when done you update a Swing 
 * component.
 *
 * <p>
 * Say we want to find the "Meaning of Life" and display the result in
 * a {@code JLabel}.
 * 
 * <pre>
 *   final JLabel label;
 *   class MeaningOfLifeFinder extends SwingWorker&lt;String, Object&gt; {
 *       {@code @Override}
 *       public String doInBackground() {
 *           return findTheMeaningOfLife();
 *       }
 *
 *       {@code @Override}
 *       protected void done() {
 *           try { 
 *               label.setText(get());
 *           } catch (Exception ignore) {
 *           }
 *       }
 *   }
 * 
 *   (new MeaningOfLifeFinder()).execute();
 * </pre>
 * 
 * <p>
 * The next example is useful in situations where you wish to process data 
 * as it is ready on the <i>Event Dispatch Thread</i>.
 *
 * <p>
 * Now we want to find the first N prime numbers and display the results in a
 * {@code JTextArea}.  While this is computing, we want to update our
 * progress in a {@code JProgressBar}.  Finally, we also want to print 
 * the prime numbers to {@code System.out}.
 * <pre>
 * class PrimeNumbersTask extends 
 *         SwingWorker&lt;List&lt;Integer&gt;, Integer&gt; {
 *     PrimeNumbersTask(JTextArea textArea, int numbersToFind) { 
 *         //initialize 
 *     }
 *
 *     {@code @Override}
 *     public List&lt;Integer&gt; doInBackground() {
 *         while (! enough &amp;&amp; ! isCancelled()) {
 *                 number = nextPrimeNumber();
 *                 publish(number);
 *                 setProgress(100 * numbers.size() / numbersToFind);
 *             }
 *         }
 *         return numbers;
 *     }
 *
 *     {@code @Override}
 *     protected void process(Integer... chunks) {
 *         for (int number : chunks) {
 *             textArea.append(number + &quot;\n&quot;);
 *         }
 *     }
 * }
 *
 * JTextArea textArea = new JTextArea();
 * final JProgressBar progressBar = new JProgressBar(0, 100);
 * PrimeNumbersTask task = new PrimeNumbersTask(textArea, N);
 * task.addPropertyChangeListener(
 *     new PropertyChangeListener() {
 *         public  void propertyChange(PropertyChangeEvent evt) {
 *             if (&quot;progress&quot;.equals(evt.getPropertyName())) {
 *                 progressBar.setValue((Integer)evt.getNewValue());
 *             }
 *         }
 *     });
 *
 * task.execute();
 * System.out.println(task.get()); //prints all prime numbers we have got
 * </pre>
 * 
 * <p>
 * Because {@code SwingWorker} implements {@code Runnable}, a
 * {@code SwingWorker} can be submitted to an
 * {@link java.util.concurrent.Executor} for execution.
 *  
 * @author Igor Kushnirskiy
 * @version $Revision: 1.4 $ $Date: 2006/10/19 21:03:49 $
 * 
 * @param <T> the result type returned by this {@code SwingWorker"s}
 *        {@code doInBackground} and {@code get} methods
 * @param <V> the type used for carrying out intermediate results by this
 *        {@code SwingWorker"s} {@code publish} and {@code process} methods
 * 
 */
public abstract class SwingWorker<T, V> implements Future<T>, Runnable {
    /**
     * number of worker threads.
     */
    private static final int MAX_WORKER_THREADS = 10;
    /**
     * current progress.
     */
    private volatile int progress;
    /**
     * current state.
     */
    private volatile StateValue state;
    /**
     * everything is run inside this FutureTask. Also it is used as
     * a delegatee for the Future API.
     */
    private final FutureTask<T> future;
    /**
     * all propertyChangeSupport goes through this.
     */
    private final SwingPropertyChangeSupport propertyChangeSupport;
    /**
     * handler for {@code process} mehtod.
     */
    private AccumulativeRunnable<V> doProcess;
    /**
     * handler for progress property change notifications.
     */
    private AccumulativeRunnable<Integer> doNotifyProgressChange;
    
    private static ExecutorService executorService = null;
    /**
     * Values for the {@code state} bound property.
     */
    public enum StateValue {
        /**
         * Initial {@code SwingWorker} state.
         */
        PENDING,
        /**
         * {@code SwingWorker} is {@code STARTED} 
         * before invoking {@code doInBackground}.
         */
        STARTED,
        /**
         * {@code SwingWorker} is {@code DONE}
         * after {@code doInBackground} method
         * is finished.
         */
        DONE
    };
    /**
     * Constructs this {@code SwingWorker}.
     */
    public SwingWorker() {
        Callable<T> callable = 
                new Callable<T>() {
                    public T call() throws Exception {
                        setState(StateValue.STARTED);
                        return doInBackground();
                    }
                };
        future = new FutureTask<T>(callable) {
                       @Override
                       protected void done() {
                           doneEDT();
                           setState(StateValue.DONE);
                       }
                   };
       state = StateValue.PENDING;
       propertyChangeSupport = new SwingPropertyChangeSupport(this, true);
       doProcess = null;
       doNotifyProgressChange = null;
    }
    
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * <p>
     * Note that this method is executed only once.
     * 
     * <p>
     * Note: this method is executed in a background thread.
     *  
     *
     * @return the computed result
     * @throws Exception if unable to compute a result
     * 
     */
    protected abstract T doInBackground() throws Exception ;
    
    /**
     * Sets this {@code Future} to the result of computation unless
     * it has been cancelled.
     */
    public final void run() {
        future.run();
    }
    
    /**
     * Sends data chunks to the {@link #process} method. This method is to be
     * used from inside the {@code doInBackground} method to deliver 
     * intermediate results
     * for processing on the <i>Event Dispatch Thread</i> inside the
     * {@code process} method.
     * 
     * <p>
     * Because the {@code process} method is invoked asynchronously on
     * the <i>Event Dispatch Thread</i>
     * multiple invocations to the {@code publish} method
     * might occur before the {@code process} method is executed. For
     * performance purposes all these invocations are coalesced into one
     * invocation with concatenated arguments.
     * 
     * <p>
     * For example:
     * 
     * <pre>
     * publish(&quot;1&quot;);
     * publish(&quot;2&quot;, &quot;3&quot;);
     * publish(&quot;4&quot;, &quot;5&quot;, &quot;6&quot;);
     * </pre>
     * 
     * might result in:
     * 
     * <pre>
     * process(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;)
     * </pre>
     *
     * <p>
     * <b>Sample Usage</b>. This code snippet loads some tabular data and
     * updates {@code DefaultTableModel} with it. Note that it safe to mutate
     * the tableModel from inside the {@code process} method because it is 
     * invoked on the <i>Event Dispatch Thread</i>.
     * 
     * <pre>
     * class TableSwingWorker extends 
     *         SwingWorker&lt;DefaultTableModel, Object[]&gt; {
     *     private final DefaultTableModel tableModel;
     * 
     *     public TableSwingWorker(DefaultTableModel tableModel) {
     *         this.tableModel = tableModel;
     *     }
     * 
     *     {@code @Override}
     *     protected DefaultTableModel doInBackground() throws Exception {
     *         for (Object[] row = loadData(); 
     *                  ! isCancelled() &amp;&amp; row != null; 
     *                  row = loadData()) {
     *             publish((Object[]) row);
     *         }
     *         return tableModel;
     *     }
     * 
     *     {@code @Override}
     *     protected void process(Object[]... chunks) {
     *         for (Object[] row : chunks) {
     *             tableModel.addRow(row);
     *         }
     *     }
     * }
     * </pre>
     * 
     * @param chunks intermediate results to process
     * 
     * @see #process
     * 
     */
    protected final void publish(V... chunks) {
        synchronized (this) {
            if (doProcess == null) {
                doProcess = new AccumulativeRunnable<V>() {
                    @Override
                    public void run(V... args) {
                        process(args);
                    }
                };
            }
        }
        doProcess.add(chunks);
    }
    /**
     * Receives data chunks from the {@code publish} method asynchronously on the
     * <i>Event Dispatch Thread</i>.
     * 
     * <p>
     * Please refer to the {@link #publish} method for more details.
     * 
     * @param chunks intermediate results to process
     * 
     * @see #publish
     * 
     */
    protected void process(V... chunks) {
    }
    /**
     * Executed on the <i>Event Dispatch Thread</i> after the {@code doInBackground}
     * method is finished. The default
     * implementation does nothing. Subclasses may override this method to
     * perform completion actions on the <i>Event Dispatch Thread</i>. Note
     * that you can query status inside the implementation of this method to
     * determine the result of this task or whether this task has been cancelled.
     * 
     * @see #doInBackground
     * @see #isCancelled()
     * @see #get
     */
    protected void done() {
    }
    /**
     * Sets the {@code progress} bound property.
     * The value should be from 0 to 100.
     *
     * <p>
     * Because {@code PropertyChangeListener}s are notified asynchronously on
     * the <i>Event Dispatch Thread</i> multiple invocations to the
     * {@code setProgress} method might occur before any
     * {@code PropertyChangeListeners} are invoked. For performance purposes
     * all these invocations are coalesced into one invocation with the last
     * invocation argument only.
     * 
     * <p>
     * For example, the following invokations:
     * 
     * <pre>
     * setProgress(1);
     * setProgress(2);
     * setProgress(3);
     * </pre>
     * 
     * might result in a single {@code PropertyChangeListener} notification with
     * the value {@code 3}.
     * 
     * @param progress the progress value to set
     * @throws IllegalArgumentException is value not from 0 to 100
     */
    protected final void setProgress(int progress) {
        if (progress < 0 || progress > 100) {
            throw new IllegalArgumentException("the value should be from 0 to 100");
        }
        int oldProgress = this.progress;
        this.progress = progress;
        synchronized (this) {
            if (doNotifyProgressChange == null) {
                doNotifyProgressChange = 
                    new AccumulativeRunnable<Integer>() {
                        @Override
                        public void run(Integer... args) {
                            firePropertyChange("progress", 
                               args[0], 
                               args[args.length - 1]);
                        }
                    };
            }
        }
        doNotifyProgressChange.add(oldProgress, progress);
    }
    /**
     * Returns the {@code progress} bound property.
     * 
     * @return the progress bound property.
     */
    public final int getProgress() {
        return progress;
    }
    /**
     * Schedules this {@code SwingWorker} for execution on a <i>worker</i>
     * thread. There are a number of <i>worker</i> threads available. In the
     * event all <i>worker</i> threads are busy handling other
     * {@code SwingWorkers} this {@code SwingWorker} is placed in a waiting
     * queue.
     *
     * <p>
     * Note:
     * {@code SwingWorker} is only designed to be executed once.  Executing a
     * {@code SwingWorker} more than once will not result in invoking the
     * {@code doInBackground} method twice.
     */
    public final void execute() {
        getWorkersExecutorService().execute(this);
    }
    // Future methods START
    /**
     * {@inheritDoc}
     */
    public final boolean cancel(boolean mayInterruptIfRunning) {
        return future.cancel(mayInterruptIfRunning);
    }
    /**
     * {@inheritDoc}
     */
    public final boolean isCancelled() {
        return future.isCancelled();
    }
    /**
     * {@inheritDoc}
     */
    public final boolean isDone() {
        return future.isDone();
    }
    /**
     * {@inheritDoc}
     * <p>
     * Note: calling {@code get} on the <i>Event Dispatch Thread</i> blocks
     * <i>all</i> events, including repaints, from being processed until this
     * {@code SwingWorker} is complete.
     * 
     * <p>
     * When you want the {@code SwingWorker} to block on the <i>Event
     * Dispatch Thread</i> we recommend that you use a <i>modal dialog</i>.
     *
     * <p>
     * For example:
     * 
     * <pre>
     * class SwingWorkerCompletionWaiter extends PropertyChangeListener {
     *     private JDialog dialog;
     * 
     *     public SwingWorkerCompletionWaiter(JDialog dialog) {
     *         this.dialog = dialog;
     *     }
     * 
     *     public void propertyChange(PropertyChangeEvent event) {
     *         if (&quot;state&quot;.equals(event.getPropertyName())
     *                 &amp;&amp; SwingWorker.StateValue.DONE == event.getNewValue()) {
     *             dialog.setVisible(false);
     *             dialog.dispose();
     *         }
     *     }
     * }
     * JDialog dialog = new JDialog(owner, true);
     * swingWorker.addPropertyChangeListener(
     *     new SwingWorkerCompletionWaiter(dialog));
     * swingWorker.execute();
     * //the dialog will be visible until the SwingWorker is done
     * dialog.setVisible(true); 
     * </pre>
     */
    public final T get() throws InterruptedException, ExecutionException {
        return future.get();
    }
    /**
     * {@inheritDoc}
     * <p>
     * Please refer to {@link #get} for more details.
     */
    public final T get(long timeout, TimeUnit unit) throws InterruptedException,
            ExecutionException, TimeoutException {
        return future.get(timeout, unit);
    }
    // Future methods END
    // PropertyChangeSupports methods START
    /**
     * Adds a {@code PropertyChangeListener} to the listener list. The listener
     * is registered for all properties. The same listener object may be added
     * more than once, and will be called as many times as it is added. If
     * {@code listener} is {@code null}, no exception is thrown and no action is taken.
     * 
     * <p>
     * Note: This is merely a convenience wrapper. All work is delegated to
     * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}.
     * 
     * @param listener the {@code PropertyChangeListener} to be added
     */
    public final void addPropertyChangeListener(PropertyChangeListener listener) {
        getPropertyChangeSupport().addPropertyChangeListener(listener);
    }
    /**
     * Removes a {@code PropertyChangeListener} from the listener list. This
     * removes a {@code PropertyChangeListener} that was registered for all
     * properties. If {@code listener} was added more than once to the same
     * event source, it will be notified one less time after being removed. If
     * {@code listener} is {@code null}, or was never added, no exception is
     * thrown and no action is taken.
     * 
     * <p>
     * Note: This is merely a convenience wrapper. All work is delegated to
     * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}.
     * 
     * @param listener the {@code PropertyChangeListener} to be removed
     */
    public final void removePropertyChangeListener(PropertyChangeListener listener) {
        getPropertyChangeSupport().removePropertyChangeListener(listener);
    }
    /**
     * Reports a bound property update to any registered listeners. No event is
     * fired if {@code old} and {@code new} are equal and non-null.
     * 
     * <p>
     * This {@code SwingWorker} will be the source for 
     * any generated events.
     *
     * <p>
     * When called off the <i>Event Dispatch Thread</i>
     * {@code PropertyChangeListeners} are notified asynchronously on
     * the <i>Event Dispatch Thread</i>.
     * <p>
     * Note: This is merely a convenience wrapper. All work is delegated to
     * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}.
     * 
     * 
     * @param propertyName the programmatic name of the property that was
     *        changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(String propertyName, Object oldValue,
            Object newValue) {
        getPropertyChangeSupport().firePropertyChange(propertyName,
            oldValue, newValue);
    }
    /**
     * Returns the {@code PropertyChangeSupport} for this {@code SwingWorker}. 
     * This method is used when flexible access to bound properties support is
     * needed.
     * <p>
     * This {@code SwingWorker} will be the source for 
     * any generated events.
     * 
     * <p>
     * Note: The returned {@code PropertyChangeSupport} notifies any
     * {@code PropertyChangeListener}s asynchronously on the <i>Event Dispatch
     * Thread</i> in the event that {@code firePropertyChange} or
     * {@code fireIndexedPropertyChange} are called off the <i>Event Dispatch
     * Thread</i>.
     * 
     * @return {@code PropertyChangeSupport} for this {@code SwingWorker}
     */
    public final PropertyChangeSupport getPropertyChangeSupport() {
        return propertyChangeSupport;
    }
    // PropertyChangeSupports methods END
    /**
     * Returns the {@code SwingWorker} state bound property.
     * 
     * @return the current state
     */
    public final StateValue getState() {
        /*
         * DONE is a special case
         * to keep getState and isDone is sync
         */
        if (isDone()) {
            return StateValue.DONE;
        } else {
            return state;
        }
    }
    
    /**
     * Sets this {@code SwingWorker} state bound property.
     * @param the state state to set
     */
    private void setState(StateValue state) {
        StateValue old = this.state;
        this.state = state;
        firePropertyChange("state", old, state);
    }
    /**
     * Invokes {@code done} on the EDT.
     */
    private void doneEDT() {
        Runnable doDone = 
            new Runnable() {
                public void run() {
                    done();
                }
            };
        if (SwingUtilities.isEventDispatchThread()) {
            doDone.run();
        } else {
            SwingUtilities.invokeLater(doDone);
        }
    }

    /**
     * returns workersExecutorService.
     *
     * returns the service stored in the appContext or creates it if
     * necessary. If the last one it triggers autoShutdown thread to
     * get started.
     * 
     * @return ExecutorService for the {@code SwingWorkers}
     * @see #startAutoShutdownThread
     */
    private static synchronized ExecutorService getWorkersExecutorService() {
        if (executorService == null) {
            //this creates non-daemon threads. 
            ThreadFactory threadFactory = 
                new ThreadFactory() {
                    final ThreadFactory defaultFactory = 
                        Executors.defaultThreadFactory();
                    public Thread newThread(final Runnable r) {
                        Thread thread = 
                            defaultFactory.newThread(r);
                        thread.setName("SwingWorker-" 
                            + thread.getName());
                        return thread;
                    }
                };
            /*
             * We want a to have no more than MAX_WORKER_THREADS
             * running threads.
             *
             * We want a worker thread to wait no longer than 1 second
             * for new tasks before terminating.
             */
            executorService = new ThreadPoolExecutor(0, MAX_WORKER_THREADS,
                                         1L, TimeUnit.SECONDS,
                                         new LinkedBlockingQueue<Runnable>(),
                                         threadFactory) {
                    private final ReentrantLock pauseLock = new ReentrantLock();
                    private final Condition unpaused = pauseLock.newCondition();
                    private boolean isPaused = false;
                    private final ReentrantLock executeLock = new ReentrantLock();
                    
                    @Override
                    public void execute(Runnable command) {
                        /*
                         * ThreadPoolExecutor first tries to run task
                         * in a corePool. If all threads are busy it
                         * tries to add task to the waiting queue. If it
                         * fails it run task in maximumPool.
                         *
                         * We want corePool to be 0 and
                         * maximumPool to be MAX_WORKER_THREADS
                         * We need to change the order of the execution.
                         * First try corePool then try maximumPool
                         * pool and only then store to the waiting
                         * queue. We can not do that because we would
                         * need access to the private methods.
                         *
                         * Instead we enlarge corePool to
                         * MAX_WORKER_THREADS before the execution and
                         * shrink it back to 0 after. 
                         * It does pretty much what we need.
                         *
                         * While we changing the corePoolSize we need
                         * to stop running worker threads from accepting new
                         * tasks.
                         */
                        
                        //we need atomicity for the execute method.
                        executeLock.lock();
                        try {
                            pauseLock.lock();
                            try {
                                isPaused = true;
                            } finally {
                                pauseLock.unlock();
                            }
                            
                            setCorePoolSize(MAX_WORKER_THREADS);
                            super.execute(command);
                            setCorePoolSize(0);
                            
                            pauseLock.lock();
                            try {
                                isPaused = false;
                                unpaused.signalAll();
                            } finally {
                                pauseLock.unlock();
                            }
                        } finally {
                            executeLock.unlock();
                        }
                    }
                    @Override 
                    protected void afterExecute(Runnable r, Throwable t) { 
                        super.afterExecute(r, t);
                        pauseLock.lock();
                        try {
                            while(isPaused) {
                                unpaused.await();
                            }
                        } catch(InterruptedException ignore) {
                            
                        } finally {
                            pauseLock.unlock();
                        }
                    }
                };
        }
        return executorService; 
    }
}
/* 
 * $Id: SwingPropertyChangeSupport.java,v 1.4 2006/10/19 21:03:49 evanx Exp $
 * 
 * Copyright � 2005 Sun Microsystems, Inc. All rights
 * reserved. Use is subject to license terms.
 */

/**
 * This subclass of {@code java.beans.PropertyChangeSupport} is almost
 * identical in functionality. The only difference is if constructed with 
 * {@code SwingPropertyChangeSupport(sourceBean, true)} it ensures
 * listeners are only ever notified on the <i>Event Dispatch Thread</i>.
 *
 * @author Igor Kushnirskiy
 * @version $Revision: 1.4 $ $Date: 2006/10/19 21:03:49 $
 */
 final class SwingPropertyChangeSupport extends PropertyChangeSupport {
    /**
     * Constructs a SwingPropertyChangeSupport object.
     *
     * @param sourceBean  The bean to be given as the source for any
     *        events.
     * @throws NullPointerException if {@code sourceBean} is 
     *         {@code null}
     */
    public SwingPropertyChangeSupport(Object sourceBean) {
        this(sourceBean, false);
    }
    /**
     * Constructs a SwingPropertyChangeSupport object.
     * 
     * @param sourceBean the bean to be given as the source for any events
     * @param notifyOnEDT whether to notify listeners on the <i>Event
     *        Dispatch Thread</i> only
     *
     * @throws NullPointerException if {@code sourceBean} is 
     *         {@code null}
     * @since 1.6
     */
    public SwingPropertyChangeSupport(Object sourceBean, boolean notifyOnEDT) {
        super(sourceBean);
        this.notifyOnEDT = notifyOnEDT;
    }
    /**
     * {@inheritDoc}
     *
     * <p>
     * If {@see #isNotifyOnEDT} is {@code true} and called off the
     * <i>Event Dispatch Thread</i> this implementation uses 
     * {@code SwingUtilities.invokeLater} to send out the notification
     * on the <i>Event Dispatch Thread</i>. This ensures  listeners
     * are only ever notified on the <i>Event Dispatch Thread</i>.
     *
     * @throws NullPointerException if {@code evt} is 
     *         {@code null}
     * @since 1.6
     */
    public void firePropertyChange(final PropertyChangeEvent evt) {
        if (evt == null) {
            throw new NullPointerException();
        }
        if (! isNotifyOnEDT()
            || SwingUtilities.isEventDispatchThread()) {
            super.firePropertyChange(evt);
        } else {
            SwingUtilities.invokeLater(
                new Runnable() {
                    public void run() {
                        firePropertyChange(evt);
                    }
                });
        }
    }
    /**
     * Returns {@code notifyOnEDT} property.
     * 
     * @return {@code notifyOnEDT} property
     * @see #SwingPropertyChangeSupport(Object sourceBean, boolean notifyOnEDT)
     * @since 1.6
     */
    public final boolean isNotifyOnEDT() {
        return notifyOnEDT;
    }
    // Serialization version ID
    static final long serialVersionUID = 7162625831330845068L;
    /**
     * whether to notify listeners on EDT
     * 
     * @serial 
     * @since 1.6
     */ 
    private final boolean notifyOnEDT;
}
 /* 
  * $Id: AccumulativeRunnable.java,v 1.4 2006/10/19 21:03:49 evanx Exp $
  * 
  * Copyright � 2005 Sun Microsystems, Inc. All rights
  * reserved. Use is subject to license terms.
  */

 /**
  * An abstract class to be used in the cases where we need {@code Runnable}
  * to perform  some actions on an appendable set of data.
  * The set of data might be appended after the {@code Runnable} is
  * sent for the execution. Usually such {@code Runnables} are sent to
  * the EDT.
  *
  * <p>
  * Usage example:
  * 
  * <p>
  * Say we want to implement JLabel.setText(String text) which sends
  * {@code text} string to the JLabel.setTextImpl(String text) on the EDT. 
  * In the event JLabel.setText is called rapidly many times off the EDT
  * we will get many updates on the EDT but only the last one is important.
  * (Every next updates overrides the previous one.)
  * We might want to implement this {@code setText} in a way that only
  * the last update is delivered.
  * <p>
  * Here is how one can do this using {@code AccumulativeRunnable}:
  * <pre>
  * AccumulativeRunnable<String> doSetTextImpl = 
  * new  AccumulativeRunnable<String>() {
  *     @Override 
  *     protected void run(String... args) {
  *         //set to the last string being passed
  *         setTextImpl(args[args.size - 1]);
  *     }
  * }
  * void setText(String text) {
  *     //add text and send for the execution if needed.
  *     doSetTextImpl.add(text);
  * }
  * </pre>
  *
  * <p>
  * Say we want want to implement addDirtyRegion(Rectangle rect)
  * which sends this region to the 
  * handleDirtyRegions(List<Rect> regiouns) on the EDT.
  * addDirtyRegions better be accumulated before handling on the EDT.
  * 
  * <p>
  * Here is how it can be implemented using AccumulativeRunnable:
  * <pre>
  * AccumulativeRunnable<Rectangle> doHandleDirtyRegions = 
  *     new AccumulativeRunnable<Rectangle>() {
  *         @Override 
  *         protected void run(Rectangle... args) {
  *             handleDirtyRegions(Arrays.asList(args));
  *         }
  *     };
  *  void addDirtyRegion(Rectangle rect) {
  *      doHandleDirtyRegions.add(rect);
  *  }
  * </pre>
  *
  * @author Igor Kushnirskiy
  * @version $Revision: 1.4 $ $Date: 2006/10/19 21:03:49 $
  *
  * @param <T> the type this {@code Runnable} accumulates
  * 
  */
 abstract class AccumulativeRunnable<T> implements Runnable {
     private List<T> arguments = null;
     private Class<?> componentType = null;
     
     /**
      * Equivalent to {@code Runnable.run} method with the
      * accumulated arguments to process.
      *
      * @param args accumulated argumets to process.
      */
     protected abstract void run(T... args);
     
     /**
      * {@inheritDoc}
      *
      * <p>
      * This implementation calls {@code run(T... args)} mehtod
      * with the list of accumulated arguments.
      */
     public final void run() {
         run(flush());
     }
     
     /**
      * appends arguments and sends this {@cod Runnable} for the
      * execution if needed.
      * <p>
      * This implementation uses {@see #submit} to send this 
      * {@code Runnable} for execution. 
      * @param args the arguments to accumulate
      */
     public final synchronized void add(T... args) {
         if (componentType == null) {
             componentType = (Class<T>) args.getClass().getComponentType();
         }
         boolean isSubmitted = true;
         if (arguments == null) {
             isSubmitted = false;
             arguments = new ArrayList<T>();
         }
         Collections.addAll(arguments, args);
         if (!isSubmitted) {
             submit();
         }
     }
     /**
      * Sends this {@code Runnable} for the execution
      *
      * <p>
      * This method is to be executed only from {@code add} method.
      *
      * <p>
      * This implementation uses {@code SwingWorker.invokeLater}.
      */
     protected void submit() {
         SwingUtilities.invokeLater(this);
     }
         
     /**
      * Returns accumulated arguments and flashes the arguments storage.
      *
      * @return accumulated artuments
      */
     private final synchronized T[] flush() {
         List<T> list = arguments;
         arguments = null;
         if (componentType == null) {
             componentType = Object.class;
         }
         T[] args = (T[]) Array.newInstance(componentType,
                                            list.size()); 
         list.toArray(args);
         return args;
     }
 }





Animation: Swing and thread

 
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class SwingWithThread extends JComponent {
  private Image[] frameList;
  private long msPerFrame;
  private volatile int currFrame;
  private Thread internalThread;
  private volatile boolean noStopRequested;
  public SwingWithThread(int width, int height, long msPerCycle, int framesPerSec,
      Color fgColor) {
    setPreferredSize(new Dimension(width, height));
    int framesPerCycle = (int) ((framesPerSec * msPerCycle) / 1000);
    msPerFrame = 1000L / framesPerSec;
    frameList = buildImages(width, height, fgColor, framesPerCycle);
    currFrame = 0;
    noStopRequested = true;
    Runnable r = new Runnable() {
      public void run() {
        try {
          runWork();
        } catch (Exception x) {
          // in case ANY exception slips through
          x.printStackTrace();
        }
      }
    };
    internalThread = new Thread(r);
    internalThread.start();
  }
  private Image[] buildImages(int width, int height, Color color, int count) {
    BufferedImage[] im = new BufferedImage[count];
    for (int i = 0; i < count; i++) {
      im[i] = new BufferedImage(width, height,
          BufferedImage.TYPE_INT_ARGB);
      double xShape = 0.0;
      double yShape = ((double) (i * height)) / (double) count;
      double wShape = width;
      double hShape = 2.0 * (height - yShape);
      Rectangle2D shape = new Rectangle2D.Double(xShape, yShape, wShape,
          hShape);
      Graphics2D g2 = im[i].createGraphics();
      g2.setColor(color);
      g2.fill(shape);
      g2.dispose();
    }
    return im;
  }
  private void runWork() {
    while (noStopRequested) {
      currFrame = (currFrame + 1) % frameList.length;
      repaint();
      try {
        Thread.sleep(msPerFrame);
      } catch (InterruptedException x) {
        Thread.currentThread().interrupt();
      }
    }
  }
  public void stopRequest() {
    noStopRequested = false;
    internalThread.interrupt();
  }
  public boolean isAlive() {
    return internalThread.isAlive();
  }
  public void paint(Graphics g) {
    g.drawImage(frameList[currFrame], 0, 0, this);
  }
  public static void main(String[] args) {
    SwingWithThread redSquish = new SwingWithThread(250, 200, 2500L, 10, Color.red);
    JFrame f = new JFrame();
    f.setLayout(new FlowLayout());
    f.add(redSquish);
    f.setSize(450, 250);
    f.setVisible(true);
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }
}





Counter: Swing and thread

 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class SecondCounterDemo extends JPanel {
  private SecondCounterRunnable sc = new SecondCounterRunnable();
  private JButton startB = new JButton("Start");
  private JButton stopB = new JButton("Stop");
  public SecondCounterDemo() {
    stopB.setEnabled(false); // begin with this disabled
    startB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        startB.setEnabled(false);
        Thread counterThread = new Thread(sc, "Counter");
        counterThread.start();
        stopB.setEnabled(true);
        stopB.requestFocus();
      }
    });
    stopB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        stopB.setEnabled(false);
        sc.stopClock();
        startB.setEnabled(true);
        startB.requestFocus();
      }
    });
    JPanel innerButtonP = new JPanel();
    innerButtonP.setLayout(new GridLayout(0, 1, 0, 3));
    innerButtonP.add(startB);
    innerButtonP.add(stopB);
    JPanel buttonP = new JPanel();
    buttonP.setLayout(new BorderLayout());
    buttonP.add(innerButtonP, BorderLayout.NORTH);
    this.setLayout(new BorderLayout(10, 10));
    this.setBorder(new EmptyBorder(20, 20, 20, 20));
    this.add(buttonP, BorderLayout.WEST);
    this.add(sc, BorderLayout.CENTER);
  }
  public static void main(String[] args) {
    SecondCounterDemo scm = new SecondCounterDemo();
    JFrame f = new JFrame();
    f.setContentPane(scm);
    f.setSize(320, 200);
    f.setVisible(true);
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }
  class SecondCounterRunnable extends JComponent implements Runnable {
    private volatile boolean keepRunning;
    private Font paintFont = new Font("SansSerif", Font.BOLD, 14);
    private volatile String timeMsg = "never started";
    private volatile int arcLen = 0;
    public SecondCounterRunnable() {
    }
    public void run() {
      runClock();
    }
    public void runClock() {
      DecimalFormat fmt = new DecimalFormat("0.000");
      long normalSleepTime = 100;
      int counter = 0;
      keepRunning = true;
      while (keepRunning) {
        try {
          Thread.sleep(normalSleepTime);
        } catch (InterruptedException x) {
          // ignore
        }
        counter++;
        double counterSecs = counter / 10.0;
        timeMsg = fmt.format(counterSecs);
        arcLen = (((int) counterSecs) % 60) * 360 / 60;
        repaint();
      }
    }
    public void stopClock() {
      keepRunning = false;
    }
    public void paint(Graphics g) {
      g.setColor(Color.black);
      g.setFont(paintFont);
      g.drawString(timeMsg, 0, 15);
      g.fillOval(0, 20, 100, 100);
      g.setColor(Color.white);
      g.fillOval(3, 23, 94, 94);
      g.setColor(Color.red);
      g.fillArc(2, 22, 96, 96, 90, -arcLen);
    }
  }
}





Eliminating race Conditions using Swing Components

 
// : c14:InvokeLaterFrame.java
// Eliminating race Conditions using Swing Components.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class InvokeLaterFrame extends JFrame {
  private JTextField statusField = new JTextField("Initial Value");
  public InvokeLaterFrame() {
    Container cp = getContentPane();
    cp.add(statusField, BorderLayout.NORTH);
    addWindowListener(new WindowAdapter() {
      public void windowOpened(WindowEvent e) {
        try { // Simulate initialization overhead
          Thread.sleep(2000);
        } catch (InterruptedException ex) {
          throw new RuntimeException(ex);
        }
        statusField.setText("Initialization complete");
      }
    });
  }
  public static void main(String[] args) {
    final InvokeLaterFrame ilf = new InvokeLaterFrame();
    run(ilf, 150, 60);
    // Use invokeAndWait() to synchronize output to prompt:
    // SwingUtilities.invokeAndWait(new Runnable() {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        ilf.statusField.setText("Application ready");
      }
    });
    System.out.println("Done");
  }
  public static void run(JFrame frame, int width, int height) {
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(width, height);
    frame.setVisible(true);
  }
} ///:~





GUI clock

 
/* From http://java.sun.ru/docs/books/tutorial/index.html */
/*
 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 *
 * -Redistribution in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */
import java.applet.Applet;
import java.awt.Graphics;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
public class GUIClock extends Applet implements Sleeper {
  private AlarmClock clock;
  public void init() {
    clock = new AlarmClock();
  }
  public void start() {
    clock.letMeSleepFor(this, ONE_SECOND);
  }
  public void paint(Graphics g) {
    Calendar cal = Calendar.getInstance();
    Date date = cal.getTime();
    DateFormat dateFormatter = DateFormat.getTimeInstance();
    g.drawString(dateFormatter.format(date), 5, 10);
  }
  public void wakeUp() {
    repaint();
    clock.letMeSleepFor(this, ONE_SECOND);
  }
}
interface Sleeper {
  public void wakeUp();
  public long ONE_SECOND = 1000; // in milliseconds
}
class AlarmClock {
  private static final int MAX_CAPACITY = 10;
  private static final int UNUSED = -1;
  private static final int NOROOM = -1;
  private Sleeper[] sleepers = new Sleeper[MAX_CAPACITY];
  private long[] sleepFor = new long[MAX_CAPACITY];
  public AlarmClock() {
    for (int i = 0; i < MAX_CAPACITY; i++)
      sleepFor[i] = UNUSED;
  }
  public synchronized boolean letMeSleepFor(Sleeper s, long time) {
    int index = findNextSlot();
    if (index == NOROOM) {
      return false;
    } else {
      sleepers[index] = s;
      sleepFor[index] = time;
      new AlarmThread(index).start();
      return true;
    }
  }
  private synchronized int findNextSlot() {
    for (int i = 0; i < MAX_CAPACITY; i++) {
      if (sleepFor[i] == UNUSED)
        return i;
    }
    return NOROOM;
  }
  private synchronized void wakeUpSleeper(int sleeperIndex) {
    sleepers[sleeperIndex].wakeUp();
    sleepers[sleeperIndex] = null;
    sleepFor[sleeperIndex] = UNUSED;
  }
  private class AlarmThread extends Thread {
    int mySleeper;
    AlarmThread(int sleeperIndex) {
      super();
      mySleeper = sleeperIndex;
    }
    public void run() {
      try {
        sleep(sleepFor[mySleeper]);
      } catch (InterruptedException e) {
      }
      wakeUpSleeper(mySleeper);
    }
  }
}





InvokeExample: Swing and thread

 
/*
Java Swing, 2nd Edition
By Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole
ISBN: 0-596-00408-7
Publisher: O"Reilly 
*/
// InvokeExample.java
//This class demonstrates several examples of how to handle long-running
//tasks (such as querying a remote resource). Some of the examples are
//good, some are not!
//
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeExample {
  private static JButton good = new JButton("Good");
  private static JButton bad = new JButton("Bad");
  private static JButton bad2 = new JButton("Bad2");
  private static JLabel resultLabel = new JLabel("Ready", JLabel.CENTER);
  public static void main(String[] args) {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    // Layout . . .
    JPanel p = new JPanel();
    p.setOpaque(true);
    p.setLayout(new FlowLayout());
    p.add(good);
    p.add(bad);
    p.add(bad2);
    Container c = f.getContentPane();
    c.setLayout(new BorderLayout());
    c.add(p, BorderLayout.CENTER);
    c.add(resultLabel, BorderLayout.SOUTH);
    // Listeners
    good.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ev) {
        resultLabel.setText("Working . . .");
        setEnabled(false);
        // We"re going to do something that takes a long time, so we
        // spin off a thread and update the display when we"re done.
        Thread worker = new Thread() {
          public void run() {
            // Something that takes a long time . . . in real life,
            // this
            // might be a DB query, remote method invocation, etc.
            try {
              Thread.sleep(5000);
            } catch (InterruptedException ex) {
            }
            // Report the result using invokeLater().
            SwingUtilities.invokeLater(new Runnable() {
              public void run() {
                resultLabel.setText("Ready");
                setEnabled(true);
              }
            });
          }
        };
        worker.start(); // So we don"t hold up the dispatch thread.
      }
    });
    bad.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ev) {
        resultLabel.setText("Working . . .");
        setEnabled(false);
        // We"re going to do the same thing, but not in a separate
        // thread.
        try {
          Thread.sleep(5000); // Dispatch thread is starving!
        } catch (InterruptedException ex) {
        }
        // Report the result.
        resultLabel.setText("Ready");
        setEnabled(true);
      }
    });
    bad2.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ev) {
        resultLabel.setText("Working . . . ");
        setEnabled(false);
        // The wrong way to use invokeLater(). The runnable() shouldn"t
        // starve the dispatch thread.
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            try {
              Thread.sleep(5000); // Dispatch thread is starving!
            } catch (InterruptedException ex) {
            }
            resultLabel.setText("Ready");
            setEnabled(true);
          }
        });
      }
    });
    f.setSize(300, 100);
    f.setVisible(true);
  }
  // Allows us to turn the buttons on or off while we work.
  static void setEnabled(boolean b) {
    good.setEnabled(b);
    bad.setEnabled(b);
    bad2.setEnabled(b);
  }
}





Is Event Dispatcher Thread

 
import java.awt.Color;
import java.awt.ruponent;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class IsEDTExample extends JPanel {
  private boolean keepRunning;
  private static int RED = 0;
  private static int BLUE = 1;
  private static int GREEN = 2;
  private static int VARIABLE = 3;
  private static int SIZE = 3;
  private int threadShade;
  private ColorTableModel tableModel= new ColorTableModel();
  private Thread colorShadeThread;
  public IsEDTExample() {
    JTable table = new JTable(tableModel);
    table.setRowHeight(100);
    table.setDefaultRenderer(Object.class, new ColorRenderer());
    add(table);
    add(new JLabel("Thread Color Shade:"));
    ButtonGroup group = new ButtonGroup();
    JRadioButton redOption = new JRadioButton("Red");
    group.add(redOption);
    redOption.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        threadShade = RED;
      }
    });
    JRadioButton blueOption = new JRadioButton("Blue");
    group.add(blueOption);
    blueOption.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        threadShade = BLUE;
      }
    });
    JRadioButton greenOption = new JRadioButton("Green");
    group.add(greenOption);
    greenOption.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        threadShade = GREEN;
      }
    });
    redOption.setSelected(true);
    this.threadShade = RED;
    add(redOption);
    add(greenOption);
    add(blueOption);
    add(new JButton(new RandomColorAction()));
    this.keepRunning = true;
    this.colorShadeThread = new Thread(new RandomColorShadeRunnable());
    this.colorShadeThread.start();
  }
  private class RandomColorAction extends AbstractAction {
    public RandomColorAction() {
      super("Create Random Color");
    }
    public void actionPerformed(ActionEvent e) {
      IsEDTExample.this.tableModel.generateRandomColor(VARIABLE);
    }
  }
  private class ColorTableModel extends AbstractTableModel {
    private Color[][] colors = new Color[3][3];
    public ColorTableModel() {
      for (int i = 0; i < SIZE; i++) {
        for (int x = 0; x < SIZE; x++) {
          colors[i][x] = Color.white;
        }
      }
    }
    public int getRowCount() {
      return SIZE;
    }
    public int getColumnCount() {
      return SIZE;
    }
    public Object getValueAt(int rowIndex, int columnIndex) {
      return colors[rowIndex][columnIndex];
    }
    public void generateRandomColor(int type) {
      Random random = new Random(System.currentTimeMillis());
      final int row = random.nextInt(SIZE);
      final int column = random.nextInt(SIZE);
      final Color color;
      if (type == RED) {
        color = new Color(random.nextInt(256), 0, 0);
      } else if (type == BLUE) {
        color = new Color(0, 0, random.nextInt(256));
      } else if (type == GREEN) {
        color = new Color(0, random.nextInt(256), 0);
      } else {
        color = new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));
      }
      if (SwingUtilities.isEventDispatchThread()) {
        colors[row][column] = color;
        fireTableCellUpdated(row, column);
      } else {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            colors[row][column] = color;
            fireTableCellUpdated(row, column);
          }
        });
      }
    }
  }
  private class ColorRenderer implements TableCellRenderer {
    private JLabel label;
    public ColorRenderer() {
      label = new JLabel();
      label.setOpaque(true);
      label.setPreferredSize(new Dimension(100, 100));
    }
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
        boolean hasFocus, int row, int column) {
      label.setBackground((Color) value);
      return label;
    }
  }
  private class RandomColorShadeRunnable implements Runnable {
    public void run() {
      while (keepRunning) {
        tableModel.generateRandomColor(threadShade);
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
        }
      }
    }
  }
  public static void main(String[] a) {
    JFrame f = new JFrame("Is Event Dispatch Thread Example");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.add(new IsEDTExample());
    f.pack();
    f.setVisible(true);
  }
}





Race Conditions using Swing Components

 
// : c14:EventThreadFrame.java
// Race Conditions using Swing Components.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
public class EventThreadFrame extends JFrame {
  private JTextField statusField = new JTextField("Initial Value");
  public EventThreadFrame() {
    Container cp = getContentPane();
    cp.add(statusField, BorderLayout.NORTH);
    addWindowListener(new WindowAdapter() {
      public void windowOpened(WindowEvent e) {
        try { // Simulate initialization overhead
          Thread.sleep(2000);
        } catch (InterruptedException ex) {
          throw new RuntimeException(ex);
        }
        statusField.setText("Initialization complete");
      }
    });
  }
  public static void main(String[] args) {
    EventThreadFrame etf = new EventThreadFrame();
    run(etf, 150, 60);
    etf.statusField.setText("Application ready");
    System.out.println("Done");
  }
  public static void run(JFrame frame, int width, int height) {
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(width, height);
    frame.setVisible(true);
  }
} ///:~





Swing and Thread: cancel a lengthy operation

 
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class BalanceLookupCantCancel extends JPanel {
  private JButton searchB = new JButton("Search");
  private JButton cancelB = new JButton("Cancel Search");
  private JLabel balanceL;
  public BalanceLookupCantCancel() {
    buildGUI();
    hookupEvents();
  }
  private void buildGUI() {
    cancelB.setEnabled(false);
    JPanel innerButtonP = new JPanel();
    innerButtonP.setLayout(new GridLayout(1, -1, 5, 5));
    innerButtonP.add(searchB);
    innerButtonP.add(cancelB);
    JPanel buttonP = new JPanel();
    buttonP.setLayout(new FlowLayout(FlowLayout.CENTER));
    buttonP.add(innerButtonP);
    JLabel balancePrefixL = new JLabel("Account Balance:");
    balanceL = new JLabel("BALANCE UNKNOWN");
    JPanel balanceP = new JPanel(new FlowLayout(FlowLayout.CENTER));
    balanceP.add(balancePrefixL);
    balanceP.add(balanceL);
    JPanel northP = new JPanel();
    northP.add(buttonP);
    northP.add(balanceP);
    setLayout(new BorderLayout());
    add(northP, BorderLayout.NORTH);
  }
  private void hookupEvents() {
    searchB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        search();
      }
    });
    cancelB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cancelSearch();
      }
    });
  }
  private void search() {
    // better be called by event thread!
    searchB.setEnabled(false);
    cancelB.setEnabled(true);
    balanceL.setText("SEARCHING ...");
    String bal = lookupBalance();
    setBalance(bal);
  }
  private String lookupBalance() {
    try {
      // Simulate a lengthy search that takes 5 seconds
      // to communicate over the network.
      Thread.sleep(5000);
      // result "retrieved", return it
      return "1,234.56";
    } catch (InterruptedException x) {
      return "SEARCH CANCELLED";
    }
  }
  private void setBalance(String newBalance) {
    // better be called by event thread!
    balanceL.setText(newBalance);
    cancelB.setEnabled(false);
    searchB.setEnabled(true);
  }
  private void cancelSearch() {
    System.out.println("in cancelSearch()");
    // Here"s where the code to cancel would go if this
    // could ever be called!
  }
  public static void main(String[] args) {
    BalanceLookupCantCancel bl = new BalanceLookupCantCancel();
    JFrame f = new JFrame("Can"t Cancel");
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    f.setContentPane(bl);
    f.setSize(400, 150);
    f.setVisible(true);
  }
}





Swing and Thread for length operation

 
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingLengthyOperation extends JPanel {
  private JButton searchBn = new JButton("Search");
  private JButton cancelBn = new JButton("Cancel Search");
  private JLabel balanceL = new JLabel();
  private volatile Thread lookupThread;
  public SwingLengthyOperation() {
    cancelBn.setEnabled(false);
    JPanel innerButtonP = new JPanel();
    innerButtonP.setLayout(new GridLayout(1, -1, 5, 5));
    innerButtonP.add(searchBn);
    innerButtonP.add(cancelBn);
    JPanel buttonP = new JPanel();
    buttonP.setLayout(new FlowLayout(FlowLayout.CENTER));
    buttonP.add(innerButtonP);
    JLabel balancePrefixL = new JLabel("Account Balance:");
    
    JPanel balanceP = new JPanel();
    balanceP.setLayout(new FlowLayout(FlowLayout.CENTER));
    balanceP.add(balancePrefixL);
    balanceP.add(balanceL);
    JPanel northP = new JPanel();
    northP.setLayout(new GridLayout(-1, 1, 5, 5));
    northP.add(buttonP);
    northP.add(balanceP);
    setLayout(new BorderLayout());
    add(northP, BorderLayout.NORTH);
    searchBn.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        search();
      }
    });
    cancelBn.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cancelSearch();
      }
    });
  }
  private void search() {
    ensureEventThread();
    searchBn.setEnabled(false);
    cancelBn.setEnabled(true);
    balanceL.setText("SEARCHING ...");
    lookupAsync();
  }
  private void lookupAsync() {
    Runnable lookupRun = new Runnable() {
      public void run() {
        String bal = lookupBalance();
        setBalanceSafely(bal);
      }
    };
    lookupThread = new Thread(lookupRun, "lookupThread");
    lookupThread.start();
  }
  private String lookupBalance() {
    try {
      Thread.sleep(5000);
      return "1,234.56";
    } catch (InterruptedException x) {
      return "SEARCH CANCELLED";
    }
  }
  private void setBalanceSafely(String newBal) {
    final String newBalance = newBal;
    Runnable r = new Runnable() {
      public void run() {
        try {
          setBalance(newBalance);
        } catch (Exception x) {
          x.printStackTrace();
        }
      }
    };
    SwingUtilities.invokeLater(r);
  }
  private void setBalance(String newBalance) {
    ensureEventThread();
    balanceL.setText(newBalance);
    cancelBn.setEnabled(false);
    searchBn.setEnabled(true);
  }
  private void cancelSearch() {
    ensureEventThread();
    cancelBn.setEnabled(false);
    if (lookupThread != null) {
      lookupThread.interrupt();
    }
  }
  private void ensureEventThread() {
    if (SwingUtilities.isEventDispatchThread()) {
      return;
    }
    throw new RuntimeException("only the event "
        + "thread should invoke this method");
  }
  public static void main(String[] args) {
    JFrame f = new JFrame();
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    f.setContentPane(new SwingLengthyOperation());
    f.setSize(400, 150);
    f.setVisible(true);
  }
}





Swing and Threading

 
 
 
/*
 * Copyright (c) 2007, Romain Guy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
/**
 * @author Romain Guy
 */
public class SwingThreading extends JFrame implements ActionListener {
  private JLabel counter;
  private int tickCounter = 0;
  private static SwingThreading edt;
  public SwingThreading() {
    super("Swing Threading");
    JButton freezer = new JButton("Increment");
    freezer.addActionListener(this);
    counter = new JLabel("0");
    add(freezer, BorderLayout.CENTER);
    add(counter, BorderLayout.SOUTH);
    pack();
    setLocationRelativeTo(null);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }
  public void actionPerformed(ActionEvent e) {
    incrementLabel();
  }
  private void incrementLabel() {
    tickCounter++;
    Runnable code = new Runnable() {
      public void run() {
        counter.setText(String.valueOf(tickCounter));
      }
    };
    if (SwingUtilities.isEventDispatchThread()) {
      code.run();
    } else {
      SwingUtilities.invokeLater(code);
    }
  }
  public static void main(String... args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        edt = new SwingThreading();
        edt.setVisible(true);
        new Thread(new Runnable() {
          public void run() {
            while (true) {
              try {
                Thread.sleep(300);
              } catch (InterruptedException e) {
              }
              edt.incrementLabel();
            }
          }
        }).start();
      }
    });
  }
}





Swing and thread: invoke and wait

 
import java.awt.FlowLayout;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeAndWaitDemo extends Object {
  private static void print(String msg) {
    String name = Thread.currentThread().getName();
    System.out.println(name + ": " + msg);
  }
  public static void main(String[] args) {
    final JLabel label = new JLabel("--------");
    JPanel panel = new JPanel(new FlowLayout());
    panel.add(label);
    JFrame f = new JFrame("InvokeAndWaitDemo");
    f.setContentPane(panel);
    f.setSize(300, 100);
    f.setVisible(true);
    try {
      print("sleeping for 3 seconds");
      Thread.sleep(3000);
      print("creating code block for event thread");
      Runnable setTextRun = new Runnable() {
        public void run() {
          print("about to do setText()");
          label.setText("New text!");
        }
      };
      print("about to invokeAndWait()");
      SwingUtilities.invokeAndWait(setTextRun);
      print("back from invokeAndWait()");
    } catch (InterruptedException ix) {
      print("interrupted while waiting on invokeAndWait()");
    } catch (InvocationTargetException x) {
      print("exception thrown from run()");
    }
  }
}





Swing and Thread: repaint

 
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class DigitalTimer extends JLabel {
  private volatile String timeText;
  private Thread internalThread;
  private volatile boolean noStopRequested;
  public DigitalTimer() {
    setBorder(BorderFactory.createLineBorder(Color.black));
    setHorizontalAlignment(SwingConstants.RIGHT);
    setFont(new Font("SansSerif", Font.BOLD, 16));
    setText("00000.0"); // use to size component
    setMinimumSize(getPreferredSize());
    setPreferredSize(getPreferredSize());
    setSize(getPreferredSize());
    timeText = "0.0";
    setText(timeText);
    noStopRequested = true;
    Runnable r = new Runnable() {
      public void run() {
        try {
          runWork();
        } catch (Exception x) {
          x.printStackTrace();
        }
      }
    };
    internalThread = new Thread(r, "DigitalTimer");
    internalThread.start();
  }
  private void runWork() {
    long startTime = System.currentTimeMillis();
    int tenths = 0;
    long normalSleepTime = 100;
    long nextSleepTime = 100;
    DecimalFormat fmt = new DecimalFormat("0.0");
    Runnable updateText = new Runnable() {
      public void run() {
        setText(timeText);
      }
    };
    while (noStopRequested) {
      try {
        Thread.sleep(nextSleepTime);
        tenths++;
        long currTime = System.currentTimeMillis();
        long elapsedTime = currTime - startTime;
        nextSleepTime = normalSleepTime
            + ((tenths * 100) - elapsedTime);
        if (nextSleepTime < 0) {
          nextSleepTime = 0;
        }
        timeText = fmt.format(elapsedTime / 1000.0);
        SwingUtilities.invokeAndWait(updateText);
      } catch (InterruptedException ix) {
        // stop running
        return;
      } catch (InvocationTargetException x) {
        x.printStackTrace();
      }
    }
  }
  public void stopRequest() {
    noStopRequested = false;
    internalThread.interrupt();
  }
  public boolean isAlive() {
    return internalThread.isAlive();
  }
  public static void main(String[] args) {
    JFrame f = new JFrame();
    f.getContentPane().setLayout(new FlowLayout());
    f.getContentPane().add(new DigitalTimer());
    f.setSize(250, 100);
    f.setVisible(true);
  }
}





Swing and threads: invoke later

 
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeLaterDemo extends Object {
  private static void print(String msg) {
    String name = Thread.currentThread().getName();
    System.out.println(name + ": " + msg);
  }
  public static void main(String[] args) {
    final JLabel label = new JLabel("--------");
    JPanel panel = new JPanel(new FlowLayout());
    panel.add(label);
    JFrame f = new JFrame();
    f.setContentPane(panel);
    f.setSize(300, 100);
    f.setVisible(true);
    try {
      print("sleeping for 3 seconds");
      Thread.sleep(3000);
    } catch (InterruptedException ix) {
      print("interrupted while sleeping");
    }
    print("creating code block for event thread");
    Runnable setTextRun = new Runnable() {
      public void run() {
        try {
          Thread.sleep(500);
          print("about to do setText()");
          label.setText("New text!");
        } catch (Exception x) {
          x.printStackTrace();
        }
      }
    };
    print("call invokeLater()");
    SwingUtilities.invokeLater(setTextRun);
    print("return from invokeLater()");
  }
}





Swing and threads: scroll text

 
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ScrollText extends JComponent {
  private BufferedImage image;
  private Dimension imageSize;
  private volatile int currOffset;
  private Thread internalThread;
  private volatile boolean noStopRequested;
  public ScrollText(String text) {
    currOffset = 0;
    buildImage(text);
    setMinimumSize(imageSize);
    setPreferredSize(imageSize);
    setMaximumSize(imageSize);
    setSize(imageSize);
    noStopRequested = true;
    Runnable r = new Runnable() {
      public void run() {
        try {
          runWork();
        } catch (Exception x) {
          x.printStackTrace();
        }
      }
    };
    internalThread = new Thread(r, "ScrollText");
    internalThread.start();
  }
  private void buildImage(String text) {
    RenderingHints renderHints = new RenderingHints(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    renderHints.put(RenderingHints.KEY_RENDERING,
        RenderingHints.VALUE_RENDER_QUALITY);
    BufferedImage scratchImage = new BufferedImage(1, 1,
        BufferedImage.TYPE_INT_RGB);
    Graphics2D scratchG2 = scratchImage.createGraphics();
    scratchG2.setRenderingHints(renderHints);
    Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
    FontRenderContext frc = scratchG2.getFontRenderContext();
    TextLayout tl = new TextLayout(text, font, frc);
    Rectangle2D textBounds = tl.getBounds();
    int textWidth = (int) Math.ceil(textBounds.getWidth());
    int textHeight = (int) Math.ceil(textBounds.getHeight());
    int horizontalPad = 10;
    int verticalPad = 6;
    imageSize = new Dimension(textWidth + horizontalPad, textHeight
        + verticalPad);
    image = new BufferedImage(imageSize.width, imageSize.height,
        BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = image.createGraphics();
    g2.setRenderingHints(renderHints);
    int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
    g2.setColor(Color.white);
    g2.fillRect(0, 0, imageSize.width, imageSize.height);
    g2.setColor(Color.blue);
    tl.draw(g2, 0, baselineOffset);
    // Free-up resources right away, but keep "image" for
    // animation.
    scratchG2.dispose();
    scratchImage.flush();
    g2.dispose();
  }
  public void paint(Graphics g) {
    // Make sure to clip the edges, regardless of curr size
    g.setClip(0, 0, imageSize.width, imageSize.height);
    int localOffset = currOffset; // in case it changes
    g.drawImage(image, -localOffset, 0, this);
    g.drawImage(image, imageSize.width - localOffset, 0, this);
    // draw outline
    g.setColor(Color.black);
    g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
  }
  private void runWork() {
    while (noStopRequested) {
      try {
        Thread.sleep(100); // 10 frames per second
        // adjust the scroll position
        currOffset = (currOffset + 1) % imageSize.width;
        // signal the event thread to call paint()
        repaint();
      } catch (InterruptedException x) {
        Thread.currentThread().interrupt();
      }
    }
  }
  public void stopRequest() {
    noStopRequested = false;
    internalThread.interrupt();
  }
  public boolean isAlive() {
    return internalThread.isAlive();
  }
  public static void main(String[] args) {
    ScrollText st = new ScrollText("Java can do animation!");
    JPanel p = new JPanel(new FlowLayout());
    p.add(st);
    JFrame f = new JFrame("ScrollText Demo");
    f.setContentPane(p);
    f.setSize(400, 100);
    f.setVisible(true);
  }
}





Swing and threads: slide

 
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SlideShow extends JComponent {
  private BufferedImage[] slide;
  private Dimension slideSize;
  private volatile int currSlide;
  private Thread internalThread;
  private volatile boolean noStopRequested;
  public SlideShow() {
    currSlide = 0;
    slideSize = new Dimension(50, 50);
    buildSlides();
    setMinimumSize(slideSize);
    setPreferredSize(slideSize);
    setMaximumSize(slideSize);
    setSize(slideSize);
    noStopRequested = true;
    Runnable r = new Runnable() {
      public void run() {
        try {
          runWork();
        } catch (Exception x) {
          x.printStackTrace();
        }
      }
    };
    internalThread = new Thread(r, "SlideShow");
    internalThread.start();
  }
  private void buildSlides() {
    RenderingHints renderHints = new RenderingHints(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    renderHints.put(RenderingHints.KEY_RENDERING,
        RenderingHints.VALUE_RENDER_QUALITY);
    slide = new BufferedImage[20];
    Color rectColor = Color.BLUE;
    Color circleColor = Color.YELLOW;
    for (int i = 0; i < slide.length; i++) {
      slide[i] = new BufferedImage(slideSize.width, slideSize.height,
          BufferedImage.TYPE_INT_RGB);
      Graphics2D g2 = slide[i].createGraphics();
      g2.setRenderingHints(renderHints);
      g2.setColor(rectColor);
      g2.fillRect(0, 0, slideSize.width, slideSize.height);
      g2.setColor(circleColor);
      int diameter = 0;
      if (i < (slide.length / 2)) {
        diameter = 5 + (8 * i);
      } else {
        diameter = 5 + (8 * (slide.length - i));
      }
      int inset = (slideSize.width - diameter) / 2;
      g2.fillOval(inset, inset, diameter, diameter);
      g2.setColor(Color.black);
      g2.drawRect(0, 0, slideSize.width - 1, slideSize.height - 1);
      g2.dispose();
    }
  }
  public void paint(Graphics g) {
    g.drawImage(slide[currSlide], 0, 0, this);
  }
  private void runWork() {
    while (noStopRequested) {
      try {
        Thread.sleep(100); // 10 frames per second
        currSlide = (currSlide + 1) % slide.length;
        repaint();
      } catch (InterruptedException x) {
        Thread.currentThread().interrupt();
      }
    }
  }
  public void stopRequest() {
    noStopRequested = false;
    internalThread.interrupt();
  }
  public boolean isAlive() {
    return internalThread.isAlive();
  }
  public static void main(String[] args) {
    SlideShow ss = new SlideShow();
    JPanel p = new JPanel(new FlowLayout());
    p.add(ss);
    JFrame f = new JFrame("SlideShow");
    f.setContentPane(p);
    f.setSize(250, 150);
    f.setVisible(true);
  }
}





Swing Type Tester 10

 
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004 
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester10 extends JFrame implements CharacterSource {
  protected RandomCharacterGenerator producer;
  private AnimatedCharacterDisplayCanvas displayCanvas;
  private CharacterDisplayCanvas feedbackCanvas;
  private JButton quitButton;
  private JButton startButton;
  private JButton stopButton;
  private CharacterEventHandler handler;
  private ScoreLabel score;
  public SwingTypeTester10() {
    initComponents();
  }
  private void initComponents() {
    handler = new CharacterEventHandler();
    producer = new RandomCharacterGenerator();
    producer.setDone(true);
    producer.start();
    displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
    feedbackCanvas = new CharacterDisplayCanvas(this);
    quitButton = new JButton();
    startButton = new JButton();
    stopButton = new JButton();
    score = new ScoreLabel(producer, this);
    Container pane = getContentPane();
    JPanel p1 = new JPanel();
    p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
    p1.add(displayCanvas);
    p1.add(feedbackCanvas);
    JPanel p2 = new JPanel();
    score.setText("      ");
    score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
    p2.add(score);
    startButton.setText("Start");
    p2.add(startButton);
    stopButton.setText("Stop");
    stopButton.setEnabled(false);
    p2.add(stopButton);
    quitButton.setText("Quit");
    p2.add(quitButton);
    p1.add(p2);
    pane.add(p1, BorderLayout.NORTH);
    pack();
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent evt) {
        quit();
      }
    });
    feedbackCanvas.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent ke) {
        char c = ke.getKeyChar();
        if (c != KeyEvent.CHAR_UNDEFINED)
          newCharacter((int) c);
      }
    });
    startButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        displayCanvas.setDone(false);
        producer.setDone(false);
        score.resetScore();
        startButton.setEnabled(false);
        stopButton.setEnabled(true);
        feedbackCanvas.setEnabled(true);
        feedbackCanvas.requestFocus();
      }
    });
    stopButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        startButton.setEnabled(true);
        stopButton.setEnabled(false);
        producer.setDone(true);
        displayCanvas.setDone(true);
        feedbackCanvas.setEnabled(false);
      }
    });
    quitButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        quit();
      }
    });
  }
  private void quit() {
    System.exit(0);
  }
  public void addCharacterListener(CharacterListener cl) {
    handler.addCharacterListener(cl);
  }
  public void removeCharacterListener(CharacterListener cl) {
    handler.removeCharacterListener(cl);
  }
  public void newCharacter(int c) {
    handler.fireNewCharacter(this, c);
  }
  public void nextCharacter() {
    throw new IllegalStateException("We don"t produce on demand");
  }
  public static void main(String args[]) {
    new SwingTypeTester10().show();
  }
}
class ScoreLabel extends JLabel implements CharacterListener {
  private volatile int score = 0;
  private int char2type = -1;
  private CharacterSource generator = null, typist = null;
  private Lock scoreLock = new ReentrantLock();
  public ScoreLabel(CharacterSource generator, CharacterSource typist) {
    this.generator = generator;
    this.typist = typist;
    if (generator != null)
      generator.addCharacterListener(this);
    if (typist != null)
      typist.addCharacterListener(this);
  }
  public ScoreLabel() {
    this(null, null);
  }
  public void resetGenerator(CharacterSource newGenerator) {
    try {
      scoreLock.lock();
      if (generator != null)
        generator.removeCharacterListener(this);
      generator = newGenerator;
      if (generator != null)
        generator.addCharacterListener(this);
    } finally {
      scoreLock.unlock();
    }
  }
  public void resetTypist(CharacterSource newTypist) {
    try {
      scoreLock.lock();
      if (typist != null)
        typist.removeCharacterListener(this);
      typist = newTypist;
      if (typist != null)
        typist.addCharacterListener(this);
    } finally {
      scoreLock.unlock();
    }
  }
  public void resetScore() {
    try {
      scoreLock.lock();
      score = 0;
      char2type = -1;
      setScore();
    } finally {
      scoreLock.unlock();
    }
  }
  private void setScore() {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        setText(Integer.toString(score));
      }
    });
  }
  public void newCharacter(CharacterEvent ce) {
    scoreLock.lock();
    try {
      if (ce.source == generator) {
        if (char2type != -1) {
          score--;
          setScore();
        }
        char2type = ce.character;
      } else {
        if (char2type != ce.character) {
          score--;
        } else {
          score++;
          char2type = -1;
        }
      }
      setScore();
    } finally {
      scoreLock.unlock();
    }
  }
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
  private static char[] chars;
  private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
  static {
    chars = charArray.toCharArray();
  }
  private Random random;
  private CharacterEventHandler handler;
  private boolean done = true;
  private Lock lock = new ReentrantLock();
  private Condition cv = lock.newCondition();
  public RandomCharacterGenerator() {
    random = new Random();
    handler = new CharacterEventHandler();
  }
  public int getPauseTime(int minTime, int maxTime) {
    return (int) (minTime + ((maxTime - minTime) * random.nextDouble()));
  }
  public int getPauseTime() {
    return getPauseTime(2000, 5500);
  }
  public void addCharacterListener(CharacterListener cl) {
    handler.addCharacterListener(cl);
  }
  public void removeCharacterListener(CharacterListener cl) {
    handler.removeCharacterListener(cl);
  }
  public void nextCharacter() {
    handler.fireNewCharacter(this,
        (int) chars[random.nextInt(chars.length)]);
  }
  public void run() {
    try {
      lock.lock();
      while (true) {
        try {
          if (done) {
            cv.await();
          } else {
            nextCharacter();
            cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
          }
        } catch (InterruptedException ie) {
          return;
        }
      }
    } finally {
      lock.unlock();
    }
  }
  public void setDone(boolean b) {
    try {
      lock.lock();
      done = b;
      if (!done)
        cv.signal();
    } finally {
      lock.unlock();
    }
  }
}
interface CharacterListener {
  public void newCharacter(CharacterEvent ce);
}
interface CharacterSource {
  public void addCharacterListener(CharacterListener cl);
  public void removeCharacterListener(CharacterListener cl);
  public void nextCharacter();
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas implements
    CharacterListener, Runnable {
  private volatile boolean done = false;
  private int curX;
  private Lock lock = new ReentrantLock();
  private Condition cv = lock.newCondition();
  private Thread timer = null;
  public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
    super(cs);
  }
  public synchronized void newCharacter(CharacterEvent ce) {
    curX = 0;
    tmpChar[0] = (char) ce.character;
    repaint();
  }
  public synchronized void paintComponent(Graphics gc) {
    if (tmpChar[0] == 0)
      return;
    Dimension d = getSize();
    int charWidth = fm.charWidth(tmpChar[0]);
    gc.clearRect(0, 0, d.width, d.height);
    gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
    if (curX > d.width - charWidth)
      curX = 0;
  }
  public void run() {
    try {
      lock.lock();
      while (true) {
        try {
          if (done) {
            cv.await();
          } else {
            repaint();
            cv.await(100, TimeUnit.MILLISECONDS);
          }
        } catch (InterruptedException ie) {
          return;
        }
      }
    } finally {
      lock.unlock();
    }
  }
  public void setDone(boolean b) {
    try {
      lock.lock();
      done = b;
      if (timer == null) {
        timer = new Thread(this);
        timer.start();
      }
      if (!done)
        cv.signal();
    } finally {
      lock.unlock();
    }
  }
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
  protected FontMetrics fm;
  protected char[] tmpChar = new char[1];
  protected int fontHeight;
  public CharacterDisplayCanvas(CharacterSource cs) {
    setFont(new Font("Monospaced", Font.BOLD, 18));
    fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
    fontHeight = fm.getHeight();
    cs.addCharacterListener(this);
  }
  public void setCharacterListener(CharacterSource cs) {
    cs.addCharacterListener(this);
  }
  public Dimension preferredSize() {
    return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
  }
  public synchronized void newCharacter(CharacterEvent ce) {
    tmpChar[0] = (char) ce.character;
    repaint();
  }
  protected synchronized void paintComponent(Graphics gc) {
    Dimension d = getSize();
    gc.clearRect(0, 0, d.width, d.height);
    if (tmpChar[0] == 0)
      return;
    int charWidth = fm.charWidth((int) tmpChar[0]);
    gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
  }
}
class CharacterEvent {
  public CharacterSource source;
  public int character;
  public CharacterEvent(CharacterSource cs, int c) {
    source = cs;
    character = c;
  }
}
class CharacterEventHandler {
  private Vector listeners = new Vector();
  public void addCharacterListener(CharacterListener cl) {
    listeners.add(cl);
  }
  public void removeCharacterListener(CharacterListener cl) {
    listeners.remove(cl);
  }
  public void fireNewCharacter(CharacterSource source, int c) {
    CharacterEvent ce = new CharacterEvent(source, c);
    CharacterListener[] cl = (CharacterListener[]) listeners
        .toArray(new CharacterListener[0]);
    for (int i = 0; i < cl.length; i++)
      cl[i].newCharacter(ce);
  }
}





SwingUtilities.invokeLater and swing thread

 
 
 
/*
 * Copyright (c) 2007, Romain Guy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
/**
 * @author Romain Guy
 */
public class SwingThreadingWait extends JFrame implements ActionListener {
  private JLabel counter;
  private long start = 0;
  public SwingThreadingWait() {
    super("Invoke & Wait");
    JButton freezer = new JButton("Open File");
    freezer.addActionListener(this);
    counter = new JLabel("Time elapsed: 0s");
    add(freezer, BorderLayout.CENTER);
    add(counter, BorderLayout.SOUTH);
    pack();
    setLocationRelativeTo(null);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }
  public void actionPerformed(ActionEvent e) {
    start = System.currentTimeMillis();
    new Thread(new Runnable() {
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
          }
          final int elapsed = (int) ((System.currentTimeMillis() - start) / 1000);
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              counter.setText("Time elapsed: " + elapsed + "s");
            }
          });
          if (elapsed == 4) {
            try {
              final int[] answer = new int[1];
              SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                  answer[0] = JOptionPane.showConfirmDialog(SwingThreadingWait.this,
                      "Abort long operation?", "Abort?", JOptionPane.YES_NO_OPTION);
                }
              });
              if (answer[0] == JOptionPane.YES_OPTION) {
                return;
              }
            } catch (InterruptedException e1) {
            } catch (InvocationTargetException e1) {
            }
          }
        }
      }
    }).start();
  }
  public static void main(String... args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        SwingThreadingWait edt = new SwingThreadingWait();
        edt.setVisible(true);
      }
    });
  }
}





Thread accuracy: Swing and threads

 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class SecondCounterMain extends JPanel {
  private SecondCounter sc = new SecondCounter();
  private JButton startB = new JButton("Start");
  private JButton stopB = new JButton("Stop");
  public SecondCounterMain() {
    stopB.setEnabled(false); 
    startB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        startB.setEnabled(false);
        Thread counterThread = new Thread(sc, "Counter");
        counterThread.start();
        stopB.setEnabled(true);
        stopB.requestFocus();
      }
    });
    stopB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        stopB.setEnabled(false);
        sc.stopClock();
        startB.setEnabled(true);
        startB.requestFocus();
      }
    });
    JPanel innerButtonP = new JPanel();
    innerButtonP.setLayout(new GridLayout(0, 1, 0, 3));
    innerButtonP.add(startB);
    innerButtonP.add(stopB);
    JPanel buttonP = new JPanel();
    buttonP.setLayout(new BorderLayout());
    buttonP.add(innerButtonP, BorderLayout.NORTH);
    this.setLayout(new BorderLayout(10, 10));
    this.setBorder(new EmptyBorder(20, 20, 20, 20));
    this.add(buttonP, BorderLayout.WEST);
    this.add(sc, BorderLayout.CENTER);
  }
  public static void main(String[] args) {
    SecondCounterMain scm = new SecondCounterMain();
    JFrame f = new JFrame("Second Counter");
    f.setContentPane(scm);
    f.setSize(320, 200);
    f.setVisible(true);
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }
  class SecondCounter extends JComponent implements Runnable {
    private volatile boolean keepRunning;
    private Font paintFont = new Font("SansSerif", Font.BOLD, 14);
    private volatile String timeMsg = "never started";
    private volatile int arcLen = 0;
    public SecondCounter() {
    }
    public void run() {
      runClock();
    }
    public void runClock() {
      DecimalFormat fmt = new DecimalFormat("0.000");
      long normalSleepTime = 100;
      long nextSleepTime = normalSleepTime;
      int counter = 0;
      long startTime = System.currentTimeMillis();
      keepRunning = true;
      while (keepRunning) {
        try {
          Thread.sleep(nextSleepTime);
        } catch (InterruptedException x) {
          // ignore
        }
        counter++;
        double counterSecs = counter / 10.0;
        double elapsedSecs = (System.currentTimeMillis() - startTime) / 1000.0;
        double diffSecs = counterSecs - elapsedSecs;
        nextSleepTime = normalSleepTime + ((long) (diffSecs * 1000.0));
        if (nextSleepTime < 0) {
          nextSleepTime = 0;
        }
        timeMsg = fmt.format(counterSecs) + " - "
            + fmt.format(elapsedSecs) + " = "
            + fmt.format(diffSecs);
        arcLen = (((int) counterSecs) % 60) * 360 / 60;
        repaint();
      }
    }
    public void stopClock() {
      keepRunning = false;
    }
    public void paint(Graphics g) {
      g.setColor(Color.black);
      g.setFont(paintFont);
      g.drawString(timeMsg, 0, 15);
      g.fillOval(0, 20, 100, 100); // black border
      g.setColor(Color.white);
      g.fillOval(3, 23, 94, 94); // white for unused portion
      g.setColor(Color.blue); // blue for used portion
      g.fillArc(2, 22, 96, 96, 90, -arcLen);
    }
  }
}





Thread and Swing 1

 
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004 
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester7 extends JFrame implements CharacterSource {
    protected RandomCharacterGenerator producer;
    private AnimatedCharacterDisplayCanvas displayCanvas;
    private CharacterDisplayCanvas feedbackCanvas;
    private JButton quitButton;
    private JButton startButton;
    private JButton stopButton;
    private CharacterEventHandler handler;
    private ScoreLabel score;
    public SwingTypeTester7() {
        initComponents();
    }
    private void initComponents() {
        handler = new CharacterEventHandler();
        producer = new RandomCharacterGenerator();
        producer.setDone(true);
        producer.start();
        displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
        feedbackCanvas = new CharacterDisplayCanvas(this);
        quitButton = new JButton();
        startButton = new JButton();
        stopButton = new JButton();
        score = new ScoreLabel(producer, this);
        Container pane = getContentPane();
        JPanel p1 = new JPanel();
        p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
        p1.add(displayCanvas);
        p1.add(feedbackCanvas);
        JPanel p2 = new JPanel();
        score.setText("      ");
        score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
        p2.add(score);
        startButton.setText("Start");
        p2.add(startButton);
        stopButton.setText("Stop");
        stopButton.setEnabled(false);
        p2.add(stopButton);
        quitButton.setText("Quit");
        p2.add(quitButton);
        p1.add(p2);
        pane.add(p1, BorderLayout.NORTH);
        pack();
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent evt) {
                quit();
            }
        });
        feedbackCanvas.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent ke) {
                char c = ke.getKeyChar();
                if (c != KeyEvent.CHAR_UNDEFINED)
                    newCharacter((int) c);
            }
        });
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                displayCanvas.setDone(false);
                producer.setDone(false);
                score.resetScore();
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                feedbackCanvas.setEnabled(true);
                feedbackCanvas.requestFocus();
            }
        });
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                startButton.setEnabled(true);
                stopButton.setEnabled(false);
                producer.setDone(true);
                displayCanvas.setDone(true);
                feedbackCanvas.setEnabled(false);
            }
        });
        quitButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                quit();
            }
        });
    }
    private void quit() {
        System.exit(0);
    }
    public void addCharacterListener(CharacterListener cl) {
        handler.addCharacterListener(cl);
    }
    public void removeCharacterListener(CharacterListener cl) {
        handler.removeCharacterListener(cl);
    }
    public void newCharacter(int c) {
        handler.fireNewCharacter(this, c);
    }
    public void nextCharacter() {
        throw new IllegalStateException("We don"t produce on demand");
    }
    
    public static void main(String args[]) {
        new SwingTypeTester7().show();
    }
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
             implements CharacterListener, Runnable {
    private volatile boolean done = false;
    private int curX;
    private Lock lock = new ReentrantLock();
    private Condition cv = lock.newCondition();
    private Thread timer = null;
    public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
        super(cs);
    }
    public synchronized void newCharacter(CharacterEvent ce) {
        curX = 0;
        tmpChar[0] = (char) ce.character;
        repaint();
    }
    public synchronized void paintComponent(Graphics gc) {
        if (tmpChar[0] == 0)
            return;
        Dimension d = getSize();
        int charWidth = fm.charWidth(tmpChar[0]);
        gc.clearRect(0, 0, d.width, d.height);
        gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
        if (curX > d.width - charWidth)
            curX = 0;
    }
    public void run() {
        try {
            lock.lock();
            while (true) {
                try {
                    if (done) {
                        cv.await();
                  } else {
                      repaint();
                      cv.await(100, TimeUnit.MILLISECONDS);
                  }
               } catch (InterruptedException ie) {
                   return;
               }
           }
        } finally {
            lock.unlock();
        }
    }
    public void setDone(boolean b) {
        try {
            lock.lock();
            done = b;
            if (timer == null) {
                timer = new Thread(this);
                timer.start();
           }
           if (!done) cv.signal();
        } finally {
            lock.unlock();
        }
    }
}
class ScoreLabel extends JLabel implements CharacterListener {
    
    private volatile int score = 0;
    private int char2type = -1;
    private CharacterSource generator = null, typist = null;
    private Lock scoreLock = new ReentrantLock();
    public ScoreLabel(CharacterSource generator, CharacterSource typist) {
        this.generator = generator;
        this.typist = typist;
        if (generator != null)
            generator.addCharacterListener(this);
        if (typist != null)
            typist.addCharacterListener(this);
    }
    public ScoreLabel() {
        this(null, null);
    }
    public void resetGenerator(CharacterSource newGenerator) {
        try {
            scoreLock.lock();
            if (generator != null)
                generator.removeCharacterListener(this);
            generator = newGenerator;
            if (generator != null)
                generator.addCharacterListener(this);
        } finally {
            scoreLock.unlock();
        }
    }
    public void resetTypist(CharacterSource newTypist) {
        try {
            scoreLock.lock();
            if (typist != null)
                typist.removeCharacterListener(this);
            typist = newTypist;
            if (typist != null)
                typist.addCharacterListener(this);
        } finally {
            scoreLock.unlock();
        }
    }
    public void resetScore() {
       try {
           scoreLock.lock();
           score = 0;
           char2type = -1;
           setScore();
       } finally {
           scoreLock.unlock();
       }
    }
    private void setScore() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                setText(Integer.toString(score));
            }
        });
    }
    public void newCharacter(CharacterEvent ce) {
        scoreLock.lock();
        try {
            if (ce.source == generator) {
                if (char2type != -1) {
                    score--;
                    setScore();
                }
                char2type = ce.character;
            }
            else {
                if (char2type != ce.character) {
                    score--;
                } else {
                    score++;
                    char2type = -1;
                }
            }
            setScore();
        } finally {
            scoreLock.unlock();
        }
    }
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
    private static char[] chars;
    private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
    static {
        chars = charArray.toCharArray();
    }
    private Random random;
    private CharacterEventHandler handler;
    private boolean done = true;
    private Lock lock = new ReentrantLock();
    private Condition cv = lock.newCondition();
    public RandomCharacterGenerator() {
        random = new Random();
        handler = new CharacterEventHandler();
    }
    public int getPauseTime(int minTime, int maxTime) {
        return (int) (minTime + ((maxTime-minTime)*random.nextDouble()));
    }
    public int getPauseTime() {
        return getPauseTime(2000, 5500);
    }
    public void addCharacterListener(CharacterListener cl) {
        handler.addCharacterListener(cl);
    }
    public void removeCharacterListener(CharacterListener cl) {
        handler.removeCharacterListener(cl);
    }
    public void nextCharacter() {
        handler.fireNewCharacter(this,
                                (int) chars[random.nextInt(chars.length)]);
    }
    public void run() {
        try {
            lock.lock();
            while (true) {
                try {
                    if (done) {
                        cv.await();
                    } else {
                        nextCharacter();
                        cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
                    }
                } catch (InterruptedException ie) {
                    return;
                }
            }
        } finally {
            lock.unlock();
        }
    }
    public void setDone(boolean b) {
        try {
            lock.lock();
            done = b;
            if (!done) cv.signal();
        } finally {
            lock.unlock();
        }
    }
}
interface CharacterSource {
    public void addCharacterListener(CharacterListener cl);
    public void removeCharacterListener(CharacterListener cl);
    public void nextCharacter();
}    
class CharacterEvent {
    public CharacterSource source;
    public int character;
    public CharacterEvent(CharacterSource cs, int c) {
        source = cs;
        character = c;
    }
}
 class CharacterEventHandler {
    private Vector listeners = new Vector();
    public void addCharacterListener(CharacterListener cl) {
        listeners.add(cl);
    }
    public void removeCharacterListener(CharacterListener cl) {
        listeners.remove(cl);
    }
    public void fireNewCharacter(CharacterSource source, int c) {
        CharacterEvent ce = new CharacterEvent(source, c);
        CharacterListener[] cl = (CharacterListener[] )
                                 listeners.toArray(new CharacterListener[0]);
        for (int i = 0; i < cl.length; i++)
            cl[i].newCharacter(ce);
    }
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
    protected FontMetrics fm;
    protected char[] tmpChar = new char[1];
    protected int fontHeight;
    public CharacterDisplayCanvas(CharacterSource cs) {
        setFont(new Font("Monospaced", Font.BOLD, 18));
        fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        cs.addCharacterListener(this);
    }
    public void setCharacterListener(CharacterSource cs) {
        cs.addCharacterListener(this);
    }
    public Dimension preferredSize() {
        return new Dimension(fm.getMaxAscent() + 10,
                             fm.getMaxAdvance() + 10);
    }
    public synchronized void newCharacter(CharacterEvent ce) {
        tmpChar[0] = (char) ce.character;
        repaint();
    }
    protected synchronized void paintComponent(Graphics gc) {
        Dimension d = getSize();
        gc.clearRect(0, 0, d.width, d.height);
        if (tmpChar[0] == 0)
            return;
        int charWidth = fm.charWidth((int) tmpChar[0]);
        gc.drawChars(tmpChar, 0, 1,
                     (d.width - charWidth) / 2, fontHeight);
    }
}
interface CharacterListener {
    public void newCharacter(CharacterEvent ce);
}





Thread and Swing 2

 
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004 
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester8 extends JFrame implements CharacterSource {
    protected RandomCharacterGenerator producer;
    private AnimatedCharacterDisplayCanvas displayCanvas;
    private CharacterDisplayCanvas feedbackCanvas;
    private JButton quitButton;
    private JButton startButton;
    private JButton stopButton;
    private CharacterEventHandler handler;
    private ScoreLabel score;
    public SwingTypeTester8() {
        initComponents();
    }
    private void initComponents() {
        handler = new CharacterEventHandler();
        producer = new RandomCharacterGenerator();
        producer.setDone(true);
        producer.start();
        displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
        feedbackCanvas = new CharacterDisplayCanvas(this);
        quitButton = new JButton();
        startButton = new JButton();
        stopButton = new JButton();
        score = new ScoreLabel(producer, this);
        Container pane = getContentPane();
        JPanel p1 = new JPanel();
        p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
        p1.add(displayCanvas);
        p1.add(feedbackCanvas);
        JPanel p2 = new JPanel();
        score.setText("      ");
        score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
        p2.add(score);
        startButton.setText("Start");
        p2.add(startButton);
        stopButton.setText("Stop");
        stopButton.setEnabled(false);
        p2.add(stopButton);
        quitButton.setText("Quit");
        p2.add(quitButton);
        p1.add(p2);
        pane.add(p1, BorderLayout.NORTH);
        pack();
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent evt) {
                quit();
            }
        });
        feedbackCanvas.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent ke) {
                char c = ke.getKeyChar();
                if (c != KeyEvent.CHAR_UNDEFINED)
                    newCharacter((int) c);
            }
        });
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                displayCanvas.setDone(false);
                producer.setDone(false);
                score.resetScore();
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                feedbackCanvas.setEnabled(true);
                feedbackCanvas.requestFocus();
            }
        });
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                startButton.setEnabled(true);
                stopButton.setEnabled(false);
                producer.setDone(true);
                displayCanvas.setDone(true);
                feedbackCanvas.setEnabled(false);
            }
        });
        quitButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                quit();
            }
        });
    }
    private void quit() {
        System.exit(0);
    }
    public void addCharacterListener(CharacterListener cl) {
        handler.addCharacterListener(cl);
    }
    public void removeCharacterListener(CharacterListener cl) {
        handler.removeCharacterListener(cl);
    }
    public void newCharacter(int c) {
        handler.fireNewCharacter(this, c);
    }
    public void nextCharacter() {
        throw new IllegalStateException("We don"t produce on demand");
    }
    
    public static void main(String args[]) {
        new SwingTypeTester8().show();
    }
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
    private static char[] chars;
    private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
    static {
        chars = charArray.toCharArray();
    }
    private Random random;
    private CharacterEventHandler handler;
    private boolean done = true;
    private Lock lock = new ReentrantLock();
    private Condition cv = lock.newCondition();
    public RandomCharacterGenerator() {
        random = new Random();
        handler = new CharacterEventHandler();
    }
    public int getPauseTime(int minTime, int maxTime) {
        return (int) (minTime + ((maxTime-minTime)*random.nextDouble()));
    }
    public int getPauseTime() {
        return getPauseTime(2000, 5500);
    }
    public void addCharacterListener(CharacterListener cl) {
        handler.addCharacterListener(cl);
    }
    public void removeCharacterListener(CharacterListener cl) {
        handler.removeCharacterListener(cl);
    }
    public void nextCharacter() {
        handler.fireNewCharacter(this,
                                (int) chars[random.nextInt(chars.length)]);
    }
    public void run() {
        try {
            lock.lock();
            while (true) {
                try {
                    if (done) {
                        cv.await();
                    } else {
                        nextCharacter();
                        cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
                    }
                } catch (InterruptedException ie) {
                    return;
                }
            }
        } finally {
            lock.unlock();
        }
    }
    public void setDone(boolean b) {
        try {
            lock.lock();
            done = b;
            if (!done) cv.signal();
        } finally {
            lock.unlock();
        }
    }
}
class ScoreLabel extends JLabel implements CharacterListener {
    
    private volatile int score = 0;
    private int char2type = -1;
    private CharacterSource generator = null, typist = null;
    private Lock scoreLock = new ReentrantLock();
    public ScoreLabel(CharacterSource generator, CharacterSource typist) {
        this.generator = generator;
        this.typist = typist;
        if (generator != null)
            generator.addCharacterListener(this);
        if (typist != null)
            typist.addCharacterListener(this);
    }
    public ScoreLabel() {
        this(null, null);
    }
    public void resetGenerator(CharacterSource newGenerator) {
        try {
            scoreLock.lock();
            if (generator != null)
                generator.removeCharacterListener(this);
            generator = newGenerator;
            if (generator != null)
                generator.addCharacterListener(this);
        } finally {
            scoreLock.unlock();
        }
    }
    public void resetTypist(CharacterSource newTypist) {
        try {
            scoreLock.lock();
            if (typist != null)
                typist.removeCharacterListener(this);
            typist = newTypist;
            if (typist != null)
                typist.addCharacterListener(this);
        } finally {
            scoreLock.unlock();
        }
    }
    public void resetScore() {
       try {
           scoreLock.lock();
           score = 0;
           char2type = -1;
           setScore();
       } finally {
           scoreLock.unlock();
       }
    }
    private void setScore() {
  if (SwingUtilities.isEventDispatchThread())
      setText(Integer.toString(score));
  else try {
      SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
        setText(Integer.toString(score));
    }
      });
  } catch (InterruptedException ie) {
  } catch (InvocationTargetException ite) {}
    }
    public void newCharacter(CharacterEvent ce) {
        scoreLock.lock();
        try {
            if (ce.source == generator) {
                if (char2type != -1) {
                    score--;
                    setScore();
                }
                char2type = ce.character;
            }
            else {
                if (char2type != ce.character) {
                    score--;
                } else {
                    score++;
                    char2type = -1;
                }
            }
            setScore();
        } finally {
            scoreLock.unlock();
        }
    }
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
             implements CharacterListener, Runnable {
    private volatile boolean done = false;
    private int curX;
    private Lock lock = new ReentrantLock();
    private Condition cv = lock.newCondition();
    private Thread timer = null;
    public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
        super(cs);
    }
    public synchronized void newCharacter(CharacterEvent ce) {
        curX = 0;
        tmpChar[0] = (char) ce.character;
        repaint();
    }
    public synchronized void paintComponent(Graphics gc) {
        if (tmpChar[0] == 0)
            return;
        Dimension d = getSize();
        int charWidth = fm.charWidth(tmpChar[0]);
        gc.clearRect(0, 0, d.width, d.height);
        gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
        if (curX > d.width - charWidth)
            curX = 0;
    }
    public void run() {
        try {
            lock.lock();
            while (true) {
                try {
                    if (done) {
                        cv.await();
                  } else {
                      repaint();
                      cv.await(100, TimeUnit.MILLISECONDS);
                  }
               } catch (InterruptedException ie) {
                   return;
               }
           }
        } finally {
            lock.unlock();
        }
    }
    public void setDone(boolean b) {
        try {
            lock.lock();
            done = b;
            if (timer == null) {
                timer = new Thread(this);
                timer.start();
           }
           if (!done) cv.signal();
        } finally {
            lock.unlock();
        }
    }
}
interface CharacterSource {
    public void addCharacterListener(CharacterListener cl);
    public void removeCharacterListener(CharacterListener cl);
    public void nextCharacter();
}    
class CharacterEvent {
    public CharacterSource source;
    public int character;
    public CharacterEvent(CharacterSource cs, int c) {
        source = cs;
        character = c;
    }
}
 class CharacterEventHandler {
    private Vector listeners = new Vector();
    public void addCharacterListener(CharacterListener cl) {
        listeners.add(cl);
    }
    public void removeCharacterListener(CharacterListener cl) {
        listeners.remove(cl);
    }
    public void fireNewCharacter(CharacterSource source, int c) {
        CharacterEvent ce = new CharacterEvent(source, c);
        CharacterListener[] cl = (CharacterListener[] )
                                 listeners.toArray(new CharacterListener[0]);
        for (int i = 0; i < cl.length; i++)
            cl[i].newCharacter(ce);
    }
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
    protected FontMetrics fm;
    protected char[] tmpChar = new char[1];
    protected int fontHeight;
    public CharacterDisplayCanvas(CharacterSource cs) {
        setFont(new Font("Monospaced", Font.BOLD, 18));
        fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        cs.addCharacterListener(this);
    }
    public void setCharacterListener(CharacterSource cs) {
        cs.addCharacterListener(this);
    }
    public Dimension preferredSize() {
        return new Dimension(fm.getMaxAscent() + 10,
                             fm.getMaxAdvance() + 10);
    }
    public synchronized void newCharacter(CharacterEvent ce) {
        tmpChar[0] = (char) ce.character;
        repaint();
    }
    protected synchronized void paintComponent(Graphics gc) {
        Dimension d = getSize();
        gc.clearRect(0, 0, d.width, d.height);
        if (tmpChar[0] == 0)
            return;
        int charWidth = fm.charWidth((int) tmpChar[0]);
        gc.drawChars(tmpChar, 0, 1,
                     (d.width - charWidth) / 2, fontHeight);
    }
}
interface CharacterListener {
    public void newCharacter(CharacterEvent ce);
}





Thread and Swing 3

 
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004 
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester9 extends JFrame implements CharacterSource {
    protected RandomCharacterGenerator producer;
    private AnimatedCharacterDisplayCanvas displayCanvas;
    private CharacterDisplayCanvas feedbackCanvas;
    private JButton quitButton;
    private JButton startButton;
  private JButton stopButton;
  private CharacterEventHandler handler;
  private ScoreLabel score;
  private SwingTypeTester9 parent;
  public SwingTypeTester9() {
    initComponents();
  }
  private void initComponents() {
    parent = this;
    handler = new CharacterEventHandler();
    producer = new RandomCharacterGenerator();
    producer.setDone(true);
    producer.start();
    displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
    feedbackCanvas = new CharacterDisplayCanvas(this);
    quitButton = new JButton();
    startButton = new JButton();
    stopButton = new JButton();
    score = new ScoreLabel(producer, this);
    Container pane = getContentPane();
    JPanel p1 = new JPanel();
    p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
    p1.add(displayCanvas);
    p1.add(feedbackCanvas);
    JPanel p2 = new JPanel();
    score.setText("      ");
    score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
    p2.add(score);
    startButton.setText("Start");
    p2.add(startButton);
    stopButton.setText("Stop");
    stopButton.setEnabled(false);
    p2.add(stopButton);
    quitButton.setText("Quit");
    p2.add(quitButton);
    p1.add(p2);
    pane.add(p1, BorderLayout.NORTH);
    pack();
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent evt) {
        quit();
      }
    });
    feedbackCanvas.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent ke) {
        char c = ke.getKeyChar();
        if (c != KeyEvent.CHAR_UNDEFINED)
          newCharacter((int) c);
      }
    });
    startButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        new FeedbackFrame(parent).show();
      }
    });
    stopButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        startButton.setEnabled(true);
        stopButton.setEnabled(false);
        producer.setDone(true);
        displayCanvas.setDone(true);
        feedbackCanvas.setEnabled(false);
      }
    });
    quitButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        quit();
      }
    });
  }
  void setupDone() {
    displayCanvas.setDone(false);
    producer.setDone(false);
    score.resetScore();
    startButton.setEnabled(false);
    stopButton.setEnabled(true);
    feedbackCanvas.setEnabled(true);
    feedbackCanvas.requestFocus();
  }
  void setupCancelled() {
  }
  private void quit() {
    System.exit(0);
  }
  public void addCharacterListener(CharacterListener cl) {
    handler.addCharacterListener(cl);
  }
  public void removeCharacterListener(CharacterListener cl) {
    handler.removeCharacterListener(cl);
  }
  public void newCharacter(int c) {
    handler.fireNewCharacter(this, c);
  }
  public void nextCharacter() {
    throw new IllegalStateException("We don"t produce on demand");
  }
  public static void main(String args[]) {
    new SwingTypeTester9().show();
  }
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
  private static char[] chars;
  private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
  static {
    chars = charArray.toCharArray();
  }
  private Random random;
  private CharacterEventHandler handler;
  private boolean done = true;
  private Lock lock = new ReentrantLock();
  private Condition cv = lock.newCondition();
  public RandomCharacterGenerator() {
    random = new Random();
    handler = new CharacterEventHandler();
  }
  public int getPauseTime(int minTime, int maxTime) {
    return (int) (minTime + ((maxTime - minTime) * random.nextDouble()));
  }
  public int getPauseTime() {
    return getPauseTime(2000, 5500);
  }
  public void addCharacterListener(CharacterListener cl) {
    handler.addCharacterListener(cl);
  }
  public void removeCharacterListener(CharacterListener cl) {
    handler.removeCharacterListener(cl);
  }
  public void nextCharacter() {
    handler.fireNewCharacter(this,
        (int) chars[random.nextInt(chars.length)]);
  }
  public void run() {
    try {
      lock.lock();
      while (true) {
        try {
          if (done) {
            cv.await();
          } else {
            nextCharacter();
            cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
          }
        } catch (InterruptedException ie) {
          return;
        }
      }
    } finally {
      lock.unlock();
    }
  }
  public void setDone(boolean b) {
    try {
      lock.lock();
      done = b;
      if (!done)
        cv.signal();
    } finally {
      lock.unlock();
    }
  }
}
class FeedbackFrame extends JFrame implements Runnable {
  private SwingTypeTester9 stt;
  private Thread t;
  private JLabel label;
  private int state;
  static String[] stateMessages = { "Connecting to server...",
      "Logging into server...", "Waiting for data...", "Complete" };
  public FeedbackFrame(SwingTypeTester9 stt) {
    this.stt = stt;
    setupFrame();
    t = new Thread(this);
    t.start();
    pack();
    show();
  }
  private void setupFrame() {
    label = new JLabel();
    label.setPreferredSize(new Dimension(200, 200));
    Container c = getContentPane();
    JButton stopButton = new JButton("Stop");
    stopButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        error();
      }
    });
    c.add(label, BorderLayout.NORTH);
    c.add(stopButton, BorderLayout.SOUTH);
  }
  private void setText(final String s) {
    try {
      SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
          label.setText(s);
        }
      });
    } catch (InterruptedException ie) {
      error();
    } catch (InvocationTargetException ite) {
      error();
    }
  }
  private void error() {
    t.interrupt();
    if (SwingUtilities.isEventDispatchThread())
      closeDown();
    else
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          closeDown();
        }
      });
  }
  private void closeDown() {
    stt.setupCancelled();
    hide();
    dispose();
  }
  public void run() {
    // Simulate connecting to server
    for (int i = 0; i < stateMessages.length; i++) {
      setText(stateMessages[i]);
      try {
        Thread.sleep(5 * 1000);
      } catch (InterruptedException ie) {
      }
      if (Thread.currentThread().isInterrupted())
        return;
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        stt.setupDone();
        hide();
        dispose();
      }
    });
  }
}
class ScoreLabel extends JLabel implements CharacterListener {
  private volatile int score = 0;
  private int char2type = -1;
  private CharacterSource generator = null, typist = null;
  private Lock scoreLock = new ReentrantLock();
  public ScoreLabel(CharacterSource generator, CharacterSource typist) {
    this.generator = generator;
    this.typist = typist;
    if (generator != null)
      generator.addCharacterListener(this);
    if (typist != null)
      typist.addCharacterListener(this);
  }
  public ScoreLabel() {
    this(null, null);
  }
  public void resetGenerator(CharacterSource newGenerator) {
    try {
      scoreLock.lock();
      if (generator != null)
        generator.removeCharacterListener(this);
      generator = newGenerator;
      if (generator != null)
        generator.addCharacterListener(this);
    } finally {
      scoreLock.unlock();
    }
  }
  public void resetTypist(CharacterSource newTypist) {
    try {
      scoreLock.lock();
      if (typist != null)
        typist.removeCharacterListener(this);
      typist = newTypist;
      if (typist != null)
        typist.addCharacterListener(this);
    } finally {
      scoreLock.unlock();
    }
  }
  public void resetScore() {
    try {
      scoreLock.lock();
      score = 0;
      char2type = -1;
      setScore();
    } finally {
      scoreLock.unlock();
    }
  }
  private void setScore() {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        setText(Integer.toString(score));
      }
    });
  }
  public void newCharacter(CharacterEvent ce) {
    scoreLock.lock();
    try {
      if (ce.source == generator) {
        if (char2type != -1) {
          score--;
          setScore();
        }
        char2type = ce.character;
      } else {
        if (char2type != ce.character) {
          score--;
        } else {
          score++;
          char2type = -1;
        }
      }
      setScore();
    } finally {
      scoreLock.unlock();
    }
  }
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas implements
    CharacterListener, Runnable {
  private volatile boolean done = false;
  private int curX;
  private Lock lock = new ReentrantLock();
  private Condition cv = lock.newCondition();
  private Thread timer = null;
  public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
    super(cs);
  }
  public synchronized void newCharacter(CharacterEvent ce) {
    curX = 0;
    tmpChar[0] = (char) ce.character;
    repaint();
  }
  public synchronized void paintComponent(Graphics gc) {
    if (tmpChar[0] == 0)
      return;
    Dimension d = getSize();
    int charWidth = fm.charWidth(tmpChar[0]);
    gc.clearRect(0, 0, d.width, d.height);
    gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
    if (curX > d.width - charWidth)
      curX = 0;
  }
  public void run() {
    try {
      lock.lock();
      while (true) {
        try {
          if (done) {
            cv.await();
          } else {
            repaint();
            cv.await(100, TimeUnit.MILLISECONDS);
          }
        } catch (InterruptedException ie) {
          return;
        }
      }
    } finally {
      lock.unlock();
    }
  }
  public void setDone(boolean b) {
    try {
      lock.lock();
      done = b;
      if (timer == null) {
        timer = new Thread(this);
        timer.start();
      }
      if (!done)
        cv.signal();
    } finally {
      lock.unlock();
    }
  }
}
interface CharacterSource {
  public void addCharacterListener(CharacterListener cl);
  public void removeCharacterListener(CharacterListener cl);
  public void nextCharacter();
}
class CharacterEvent {
  public CharacterSource source;
  public int character;
  public CharacterEvent(CharacterSource cs, int c) {
    source = cs;
    character = c;
  }
}
class CharacterEventHandler {
  private Vector listeners = new Vector();
  public void addCharacterListener(CharacterListener cl) {
    listeners.add(cl);
  }
  public void removeCharacterListener(CharacterListener cl) {
    listeners.remove(cl);
  }
  public void fireNewCharacter(CharacterSource source, int c) {
    CharacterEvent ce = new CharacterEvent(source, c);
    CharacterListener[] cl = (CharacterListener[]) listeners
        .toArray(new CharacterListener[0]);
    for (int i = 0; i < cl.length; i++)
      cl[i].newCharacter(ce);
  }
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
  protected FontMetrics fm;
  protected char[] tmpChar = new char[1];
  protected int fontHeight;
  public CharacterDisplayCanvas(CharacterSource cs) {
    setFont(new Font("Monospaced", Font.BOLD, 18));
    fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
    fontHeight = fm.getHeight();
    cs.addCharacterListener(this);
  }
  public void setCharacterListener(CharacterSource cs) {
    cs.addCharacterListener(this);
  }
  public Dimension preferredSize() {
    return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
  }
  public synchronized void newCharacter(CharacterEvent ce) {
    tmpChar[0] = (char) ce.character;
    repaint();
  }
  protected synchronized void paintComponent(Graphics gc) {
    Dimension d = getSize();
    gc.clearRect(0, 0, d.width, d.height);
    if (tmpChar[0] == 0)
      return;
    int charWidth = fm.charWidth((int) tmpChar[0]);
    gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
  }
}
interface CharacterListener {
  public void newCharacter(CharacterEvent ce);
}





User interface responsiveness

 
// : c13:ResponsiveUI.java
// User interface responsiveness.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
class UnresponsiveUI {
  private volatile double d = 1;
  public UnresponsiveUI() throws Exception {
    while (d > 0)
      d = d + (Math.PI + Math.E) / d;
    System.in.read(); // Never gets here
  }
}
public class ResponsiveUI extends Thread {
  private static volatile double d = 1;
  public ResponsiveUI() {
    setDaemon(true);
    start();
  }
  public void run() {
    while (true) {
      d = d + (Math.PI + Math.E) / d;
    }
  }
  public static void main(String[] args) throws Exception {
    //! new UnresponsiveUI(); // Must kill this process
    new ResponsiveUI();
    Thread.sleep(300);
    System.in.read(); // "monitor" provides input
    System.out.println(d); // Shows progress
  }
} ///:~





Using the Runnable interface

 
// : c14:ColorBoxes.java
// Using the Runnable interface.
// <applet code=ColorBoxes width=500 height=400>
// <param name=grid value="12">
// <param name=pause value="50"></applet>
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.Random;
import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JPanel;
class CBox extends JPanel implements Runnable {
  private Thread t;
  private int pause;
  private static final Color[] colors = { Color.BLACK, Color.BLUE,
      Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN,
      Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK,
      Color.RED, Color.WHITE, Color.YELLOW };
  private static Random rand = new Random();
  private static final Color newColor() {
    return colors[rand.nextInt(colors.length)];
  }
  private Color cColor = newColor();
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
  public CBox(int pause) {
    this.pause = pause;
    t = new Thread(this);
    t.start();
  }
  public void run() {
    while (true) {
      cColor = newColor();
      repaint();
      try {
        t.sleep(pause);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
  }
}
public class ColorBoxes extends JApplet {
  private boolean isApplet = true;
  private int grid = 12;
  private int pause = 50;
  public void init() {
    // Get parameters from Web page:
    if (isApplet) {
      String gsize = getParameter("grid");
      if (gsize != null)
        grid = Integer.parseInt(gsize);
      String pse = getParameter("pause");
      if (pse != null)
        pause = Integer.parseInt(pse);
    }
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(grid, grid));
    for (int i = 0; i < grid * grid; i++)
      cp.add(new CBox(pause));
  }
  public static void main(String[] args) {
    ColorBoxes applet = new ColorBoxes();
    applet.isApplet = false;
    if (args.length > 0)
      applet.grid = Integer.parseInt(args[0]);
    if (args.length > 1)
      applet.pause = Integer.parseInt(args[1]);
    run(applet, 500, 400);
  }
  public static void run(JApplet applet, int width, int height) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(applet);
    frame.setSize(width, height);
    applet.init();
    applet.start();
    frame.setVisible(true);
  }
} ///:~





Write your Beans this way so they can run in a multithreaded environment

 
// : c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.Serializable;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BangBean2 extends JPanel implements Serializable {
  private int xm, ym;
  private int cSize = 20; // Circle size
  private String text = "Bang!";
  private int fontSize = 48;
  private Color tColor = Color.RED;
  private ArrayList actionListeners = new ArrayList();
  public BangBean2() {
    addMouseListener(new ML());
    addMouseMotionListener(new MM());
  }
  public synchronized int getCircleSize() {
    return cSize;
  }
  public synchronized void setCircleSize(int newSize) {
    cSize = newSize;
  }
  public synchronized String getBangText() {
    return text;
  }
  public synchronized void setBangText(String newText) {
    text = newText;
  }
  public synchronized int getFontSize() {
    return fontSize;
  }
  public synchronized void setFontSize(int newSize) {
    fontSize = newSize;
  }
  public synchronized Color getTextColor() {
    return tColor;
  }
  public synchronized void setTextColor(Color newColor) {
    tColor = newColor;
  }
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(Color.BLACK);
    g.drawOval(xm - cSize / 2, ym - cSize / 2, cSize, cSize);
  }
  // This is a multicast listener, which is more typically
  // used than the unicast approach taken in BangBean.java:
  public synchronized void addActionListener(ActionListener l) {
    actionListeners.add(l);
  }
  public synchronized void removeActionListener(ActionListener l) {
    actionListeners.remove(l);
  }
  // Notice this isn"t synchronized:
  public void notifyListeners() {
    ActionEvent a = new ActionEvent(BangBean2.this,
        ActionEvent.ACTION_PERFORMED, null);
    ArrayList lv = null;
    // Make a shallow copy of the List in case
    // someone adds a listener while we"re
    // calling listeners:
    synchronized (this) {
      lv = (ArrayList) actionListeners.clone();
    }
    // Call all the listener methods:
    for (int i = 0; i < lv.size(); i++)
      ((ActionListener) lv.get(i)).actionPerformed(a);
  }
  class ML extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Graphics g = getGraphics();
      g.setColor(tColor);
      g.setFont(new Font("TimesRoman", Font.BOLD, fontSize));
      int width = g.getFontMetrics().stringWidth(text);
      g.drawString(text, (getSize().width - width) / 2,
          getSize().height / 2);
      g.dispose();
      notifyListeners();
    }
  }
  class MM extends MouseMotionAdapter {
    public void mouseMoved(MouseEvent e) {
      xm = e.getX();
      ym = e.getY();
      repaint();
    }
  }
  public static void main(String[] args) {
    BangBean2 bb = new BangBean2();
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("ActionEvent" + e);
      }
    });
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("BangBean2 action");
      }
    });
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("More action");
      }
    });
    run(bb, 300, 300);
  }
  public static void run(JPanel panel, int width, int height) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(panel);
    frame.setSize(width, height);
    frame.setVisible(true);
  }
} ///:~