/* * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package javax.swing; import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeEvent; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.locks.*; import java.awt.event.*; import javax.swing.SwingUtilities; import sun.awt.AppContext; import sun.swing.AccumulativeRunnable; /** * An abstract class to perform lengthy GUI-interaction tasks in a * background thread. Several background threads can be used to execute such * tasks. However, the exact strategy of choosing a thread for any particular * {@code SwingWorker} is unspecified and should not be relied on. *
* When writing a multi-threaded application using Swing, there are * two constraints to keep in mind: * (refer to * * Concurrency in Swing * for more details): *
* 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 {@link #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.
*
* @author Igor Kushnirskiy
*
* @param
* 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:
*
*
* 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.
*
*
* Please refer to the {@link #publish} method for more details.
*
* @param chunks intermediate results to process
*
* @see #publish
*
*/
protected void process(List
* 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 invokations:
*
*
* 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:
*
*
* 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 speacial 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.
*
* @return ExecutorService for the {@code SwingWorkers}
*/
private static synchronized ExecutorService getWorkersExecutorService() {
final AppContext appContext = AppContext.getAppContext();
ExecutorService executorService =
(ExecutorService) appContext.get(SwingWorker.class);
if (executorService == null) {
//this creates 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());
thread.setDaemon(true);
return thread;
}
};
executorService =
new ThreadPoolExecutor(MAX_WORKER_THREADS, MAX_WORKER_THREADS,
10L, TimeUnit.MINUTES,
new LinkedBlockingQueue
* publish("1");
* publish("2", "3");
* publish("4", "5", "6");
*
*
* might result in:
*
*
* process("1", "2", "3", "4", "5", "6")
*
*
*
* 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
*
*/
@SafeVarargs
@SuppressWarnings("varargs") // Passing chunks to add is safe
protected final void publish(V... chunks) {
synchronized (this) {
if (doProcess == null) {
doProcess = new AccumulativeRunnable
* 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
* 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}
*