All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jidesoft.utils.SwingWorker Maven / Gradle / Ivy

There is a newer version: 3.6.18
Show newest version
/* 
 * $Id: SwingWorker.java,v 1.5 2007/03/01 19:55:54 idk Exp $
 * 
 * Copyright (c) 1995, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.jidesoft.utils;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

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

*

* When writing a multi-threaded application using Swing, there are two constraints to keep in mind: (refer to How to Use Threads for more details): *

  • Time-consuming tasks should not be run on the Event Dispatch Thread. Otherwise the application * becomes unresponsive.
  • Swing components should be accessed on the Event Dispatch Thread only.
  • *
*

*

*

*

* 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 Event Dispatch Thread (EDT) for all GUI-related activities. This * involves inter-thread communication which can be tricky to implement. *

*

* {@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. *

*

*

* Workflow *

* There are three threads involved in the life cycle of a {@code SwingWorker} :

  • *

    * Current thread: The {@link #execute} method is called on this thread. It schedules {@code SwingWorker} for the * execution on a worker thread and returns immediately. One can wait for the {@code SwingWorker} to complete * using the {@link #get get} methods.

  • *

    * Worker 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}.

  • *

    * Event Dispatch Thread: 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. *

*

*

* Often, the Current thread is the Event Dispatch Thread. *

*

*

* Before the {@code doInBackground} method is invoked on a worker 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}. *

*

* {@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. *

*

* Sample Usage *

* The following example illustrates the simplest use case. Some processing is done in the background and when done you * update a Swing component. *

*

* Say we want to find the "Meaning of Life" and display the result in a {@code JLabel}. *

*

 *   final JLabel label;
 *   class MeaningOfLifeFinder extends SwingWorker<String, Object> {
 *       {@code @Override}
 *       public String doInBackground() {
 *           return findTheMeaningOfLife();
 *       }
 *
 *       {@code @Override}
 *       protected void done() {
 *           try {
 *               label.setText(get());
 *           } catch (Exception ignore) {
 *           }
 *       }
 *   }
 *
 *   (new MeaningOfLifeFinder()).execute();
 * 
*

*

* The next example is useful in situations where you wish to process data as it is ready on the Event Dispatch * Thread. *

*

* 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}. *

 * class PrimeNumbersTask extends
 *         SwingWorker<List<Integer>, Integer> {
 *     PrimeNumbersTask(JTextArea textArea, int numbersToFind) {
 *         //initialize
 *     }
 *
 *     {@code @Override}
 *     public List<Integer> doInBackground() {
 *         while (! enough && ! isCancelled()) {
 *                 number = nextPrimeNumber();
 *                 publish(number);
 *                 setProgress(100 * numbers.size() / numbersToFind);
 *             }
 *         }
 *         return numbers;
 *     }
 *
 *     {@code @Override}
 *     protected void process(List<Integer> chunks) {
 *         for (int number : chunks) {
 *             textArea.append(number + "\n");
 *         }
 *     }
 * }
 *
 * 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 ("progress".equals(evt.getPropertyName())) {
 *                 progressBar.setValue((Integer)evt.getNewValue());
 *             }
 *         }
 *     });
 *
 * task.execute();
 * System.out.println(task.get()); //prints all prime numbers we have got
 * 
*

*

* Because {@code SwingWorker} implements {@code Runnable}, a {@code SwingWorker} can be submitted to an {@link * java.util.concurrent.Executor} for execution. * * @param the result type returned by this {@code SwingWorker's} {@code doInBackground} and {@code get} methods * @param the type used for carrying out intermediate results by this {@code SwingWorker's} {@code publish} and * {@code process} methods * @author Igor Kushnirskiy * @version $Revision: 1.5 $ $Date: 2007/03/01 19:55:54 $ */ public abstract class SwingWorker implements Future, 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 delegate for the Future API. */ private final FutureTask future; /** * all propertyChangeSupport goes through this. */ private final PropertyChangeSupport propertyChangeSupport; /** * handler for {@code process} method. */ private AccumulativeRunnable doProcess; /** * handler for progress property change notifications. */ private AccumulativeRunnable doNotifyProgressChange; private static final AccumulativeRunnable doSubmit = new DoSubmitAccumulativeRunnable(); 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 callable = new Callable() { public T call() throws Exception { setState(StateValue.STARTED); return doInBackground(); } }; future = new FutureTask(callable) { @Override protected void done() { doneEDT(); setState(StateValue.DONE); } }; state = StateValue.PENDING; propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this); doProcess = null; doNotifyProgressChange = null; } /** * Computes a result, or throws an exception if unable to do so. *

*

* Note that this method is executed only once. *

*

* 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 Event Dispatch Thread inside * the {@code process} method. *

*

* Because the {@code process} method is invoked asynchronously on the Event Dispatch Thread 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. *

*

* For example: *

*

     * publish("1");
     * publish("2", "3");
     * publish("4", "5", "6");
     * 
*

* might result in: *

*

     * process("1", "2", "3", "4", "5", "6")
     * 
*

*

* Sample Usage. 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 * Event Dispatch Thread. *

*

     * class TableSwingWorker extends
     *         SwingWorker<DefaultTableModel, Object[]> {
     *     private final DefaultTableModel tableModel;
     *
     *     public TableSwingWorker(DefaultTableModel tableModel) {
     *         this.tableModel = tableModel;
     *     }
     *
     *     {@code @Override}
     *     protected DefaultTableModel doInBackground() throws Exception {
     *         for (Object[] row = loadData();
     *                  ! isCancelled() && row != null;
     *                  row = loadData()) {
     *             publish((Object[]) row);
     *         }
     *         return tableModel;
     *     }
     *
     *     {@code @Override}
     *     protected void process(List<Object[]> chunks) {
     *         for (Object[] row : chunks) {
     *             tableModel.addRow(row);
     *         }
     *     }
     * }
     * 
* * @param chunks intermediate results to process * @see #process */ protected final void publish(V... chunks) { synchronized (this) { if (doProcess == null) { doProcess = new AccumulativeRunnable() { @Override public void run(List args) { process(args); } @Override protected void submit() { doSubmit.add(this); } }; } } doProcess.add(chunks); } /** * Receives data chunks from the {@code publish} method asynchronously on the Event Dispatch Thread. *

*

* Please refer to the {@link #publish} method for more details. * * @param chunks intermediate results to process * @see #publish */ protected void process(List chunks) { } /** * Executed on the Event Dispatch Thread after the {@code doInBackground} method is finished. The default * implementation does nothing. Subclasses may override this method to perform completion actions on the Event * Dispatch Thread. 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. *

*

* Because {@code PropertyChangeListener}s are notified asynchronously on the Event Dispatch Thread 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. *

*

* For example, the following invocations: *

*

     * setProgress(1);
     * setProgress(2);
     * setProgress(3);
     * 
*

* 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"); } if (this.progress == progress) { return; } int oldProgress = this.progress; this.progress = progress; if (!getPropertyChangeSupport().hasListeners("progress")) { return; } synchronized (this) { if (doNotifyProgressChange == null) { doNotifyProgressChange = new AccumulativeRunnable() { @Override public void run(List args) { firePropertyChange("progress", args.get(0), args.get(args.size() - 1)); } @Override protected void submit() { doSubmit.add(this); } }; } } 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 worker thread. There are a number of worker * threads available. In the event all worker threads are busy handling other {@code SwingWorkers} this * {@code SwingWorker} is placed in a waiting queue. *

*

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

* Note: calling {@code get} on the Event Dispatch Thread blocks all events, including repaints, from * being processed until this {@code SwingWorker} is complete. *

*

* When you want the {@code SwingWorker} to block on the Event Dispatch Thread we recommend that you use a * modal dialog. *

*

* For example: *

*

     * class SwingWorkerCompletionWaiter extends PropertyChangeListener {
     *     private JDialog dialog;
     *
     *     public SwingWorkerCompletionWaiter(JDialog dialog) {
     *         this.dialog = dialog;
     *     }
     *
     *     public void propertyChange(PropertyChangeEvent event) {
     *         if ("state".equals(event.getPropertyName())
     *                 && 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);
     * 
*/ public final T get() throws InterruptedException, ExecutionException { return future.get(); } /** * {@inheritDoc} *

* 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. *

*

* 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. *

*

* 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. *

*

* This {@code SwingWorker} will be the source for any generated events. *

*

* When called off the Event Dispatch Thread {@code PropertyChangeListeners} are notified asynchronously on * the Event Dispatch Thread. *

* 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. *

* This {@code SwingWorker} will be the source for any generated events. *

*

* Note: The returned {@code PropertyChangeSupport} notifies any {@code PropertyChangeListener}s asynchronously on * the Event Dispatch Thread in the event that {@code firePropertyChange} or {@code * fireIndexedPropertyChange} are called off the Event Dispatch Thread. * * @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 state the 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 { doSubmit.add(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} */ private static synchronized ExecutorService getWorkersExecutorService() { if (executorService == null) { //this creates non-daemon threads. ThreadFactory threadFactory = new ThreadFactory() { final AtomicInteger threadNumber = new AtomicInteger(1); public Thread newThread(final Runnable r) { StringBuilder name = new StringBuilder("SwingWorker-pool-"); name.append(System.identityHashCode(this)); name.append("-thread-"); name.append(threadNumber.getAndIncrement()); Thread t = new Thread(r, name.toString()); ; if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }; /* * 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, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue(), 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; } private static class DoSubmitAccumulativeRunnable extends AccumulativeRunnable implements ActionListener { private static final int DELAY = (int) (1000 / 30); @Override protected void run(List args) { for (Runnable runnable : args) { runnable.run(); } } @Override protected void submit() { Timer timer = new Timer(DELAY, this); timer.setRepeats(false); timer.start(); } public void actionPerformed(ActionEvent event) { run(); } } private class SwingWorkerPropertyChangeSupport extends PropertyChangeSupport { SwingWorkerPropertyChangeSupport(Object source) { super(source); } @Override public void firePropertyChange(final PropertyChangeEvent evt) { if (SwingUtilities.isEventDispatchThread()) { super.firePropertyChange(evt); } else { doSubmit.add( new Runnable() { public void run() { SwingWorkerPropertyChangeSupport.this .firePropertyChange(evt); } }); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy