/*
* $Id: AccumulativeRunnable.java,v 1.2 2006/09/28 20:20:28 idk Exp $
*
* Copyright ?2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
package org.jdesktop.swingworker;
import java.util.*;
import javax.swing.SwingUtilities;
/**
* 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(List<String> args) {
* //set to the last string being passed
* setTextImpl(args.get(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> regions) 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(List<Rectangle> args) {
* handleDirtyRegions(args);
* }
* };
* void addDirtyRegion(Rectangle rect) {
* doHandleDirtyRegions.add(rect);
* }
* </pre>
*
* @author Igor Kushnirskiy
* @version $Revision: 1.2 $ $Date: 2006/09/28 20:20:28 $
*
* @param <T> the type this {@code Runnable} accumulates
*
*/
abstract class AccumulativeRunnable<T> implements Runnable {
private List<T> arguments = null;
/**
* Equivalent to {@code Runnable.run} method with the
* accumulated arguments to process.
*
* @param args accumulated arguments to process.
*/
protected abstract void run(List<T> args);
/**
* {@inheritDoc}
*
* <p>
* This implementation calls {@code run(List<T> args)} method
* with the list of accumulated arguments.
*/
public final void run() {
run(flush());
}
/**
* appends arguments and sends this {@code 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) {
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 arguments
*/
private final synchronized List<T> flush() {
List<T> list = arguments;
arguments = null;
return list;
}
}
///////////////////////////////////////////////////////////
/*
* $Id: SwingPropertyChangeSupport.java,v 1.1 2005/06/18 21:27:14 idk Exp $
*
* Copyright ?2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
package org.jdesktop.swingworker;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeEvent;
import javax.swing.SwingUtilities;
/**
* 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.1 $ $Date: 2005/06/18 21:27:14 $
*/
public 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: SwingWorker.java,v 1.5 2007/03/01 19:55:54 idk Exp $
*
* Copyright ?2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
package org.jdesktop.swingworker;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeEvent;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.*;
import java.awt.event.*;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/**
* 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
* <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">
* How to Use Threads
* </a> 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<String, Object> {
* {@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<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
* </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.5 $ $Date: 2007/03/01 19:55:54 $
*
* @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 PropertyChangeSupport propertyChangeSupport;
/**
* handler for {@code process} method.
*/
private AccumulativeRunnable<V> doProcess;
/**
* handler for progress property change notifications.
*/
private AccumulativeRunnable<Integer> doNotifyProgressChange;
private static final AccumulativeRunnable<Runnable> 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<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 SwingWorkerPropertyChangeSupport(this);
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("1");
* publish("2", "3");
* publish("4", "5", "6");
* </pre>
*
* might result in:
*
* <pre>
* process("1", "2", "3", "4", "5", "6")
* </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<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);
* }
* }
* }
* </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(List<V> args) {
process(args);
}
@Override
protected void submit() {
doSubmit.add(this);
}
};
}
}
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(List<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 invocations:
*
* <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");
}
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<Integer>() {
@Override
public void run(List<Integer> 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 <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 ("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);
* </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 {
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}
* @see #startAutoShutdownThread
*/
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<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;
}
private static class DoSubmitAccumulativeRunnable
extends AccumulativeRunnable<Runnable> implements ActionListener {
private final static int DELAY = (int) (1000 / 30);
@Override
protected void run(List<Runnable> 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);
}
});
}
}
}
}
///////////////////////////////////////////////////////////
/*
* $Id: PrimeNumbersDemo.java,v 1.2 2006/09/28 20:20:30 idk Exp $
*
* Copyright ?2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
package org.jdesktop.swingworker;
import java.awt.BorderLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class PrimeNumbersDemo {
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Prime Numbers Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JTextArea textArea = new JTextArea();
textArea.setEditable(false);
frame.add(new JScrollPane(textArea), BorderLayout.CENTER);
PrimeNumbersTask task = new PrimeNumbersTask(textArea, 10000);
final JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
frame.add(progressBar, BorderLayout.NORTH);
frame.setSize(500, 500);
frame.setVisible(true);
task.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
return;
}
/**
* Finds first N prime numbers.
*/
static class PrimeNumbersTask extends SwingWorker<List<Integer>, Integer> {
final int numbersToFind;
//sorted list of consequent prime numbers
private final List<Integer> primeNumbers;
private final JTextArea textArea;
PrimeNumbersTask(JTextArea textArea, int numbersToFind) {
this.textArea = textArea;
this.numbersToFind = numbersToFind;
this.primeNumbers = new ArrayList<Integer>(numbersToFind);
}
@Override
public List<Integer> doInBackground() {
int number = 2;
while(primeNumbers.size() < numbersToFind
&& !isCancelled()) {
if (isPrime(number)) {
primeNumbers.add(number);
setProgress(100 * primeNumbers.size() / numbersToFind);
publish(number);
}
number++;
}
return primeNumbers;
}
@Override
protected void process(List<Integer> chunks) {
StringBuilder strBuilder = new StringBuilder();
for (int number : chunks) {
strBuilder.append(number).append('\n');
}
textArea.append(strBuilder.toString());
}
private boolean isPrime(int number) {
for (int prime : primeNumbers) {
if (number % prime == 0) {
return false;
}
}
return true;
}
}
}
///////////////////////////////////////////////////////////
/*
* $Id: SwingWorkerTest.java,v 1.3 2007/03/01 19:34:02 idk Exp $
*
* Copyright ?2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
package org.jdesktop.swingworker;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.SwingUtilities;
import junit.framework.TestCase;
import org.jdesktop.swingworker.SwingWorker.StateValue;
public class SwingWorkerTest extends TestCase {
private final static int TIME_OUT = 30;
private final static TimeUnit TIME_OUT_UNIT = TimeUnit.SECONDS;
public static void main(String[] args) {
junit.swingui.TestRunner.run(SwingWorkerTest.class);
}
// is to be run on a worker thread.
public final void testdoInBackground() throws Exception {
SwingWorker<Thread,?> test = new SwingWorker<Thread, Object>() {
@Override
protected Thread doInBackground() throws Exception {
return Thread.currentThread();
}
};
test.execute();
Thread result = test.get(TIME_OUT, TIME_OUT_UNIT);
assertNotNull(result);
assertNotSame(Thread.currentThread(), result);
}
//{@code process} gets everything from {@code publish}
//should be executed on the EDT
public final void testPublishAndProcess() throws Exception {
final Exchanger<List<Integer>> listExchanger =
new Exchanger<List<Integer>>();
final Exchanger<Boolean> boolExchanger =
new Exchanger<Boolean>();
SwingWorker<List<Integer>,Integer> test =
new SwingWorker<List<Integer>, Integer>() {
List<Integer> receivedArgs =
Collections.synchronizedList(new ArrayList<Integer>());
Boolean isOnEDT = Boolean.TRUE;
final int NUMBERS = 100;
@Override
protected List<Integer> doInBackground() throws Exception {
List<Integer> ret =
Collections.synchronizedList(
new ArrayList<Integer>(NUMBERS));
for (int i = 0; i < NUMBERS; i++) {
publish(i);
ret.add(i);
}
return ret;
}
@Override
protected void process(List<Integer> args) {
for(Integer i : args) {
receivedArgs.add(i);
}
isOnEDT = isOnEDT && SwingUtilities.isEventDispatchThread();
if (receivedArgs.size() == NUMBERS) {
try {
boolExchanger.exchange(isOnEDT);
listExchanger.exchange(receivedArgs);
} catch (InterruptedException ignore) {
ignore.printStackTrace();
}
}
}
};
test.execute();
assertTrue(boolExchanger.exchange(null, TIME_OUT, TIME_OUT_UNIT));
assertEquals(test.get(TIME_OUT, TIME_OUT_UNIT),
listExchanger.exchange(null, TIME_OUT, TIME_OUT_UNIT));
}
// done is executed on the EDT
// receives the return value from doInBackground using get()
public final void testDone() throws Exception {
final String testString = "test";
final Exchanger<Boolean> exchanger = new Exchanger<Boolean>();
SwingWorker<?,?> test = new SwingWorker<String, Object>() {
@Override
protected String doInBackground() throws Exception {
return testString;
}
@Override
protected void done() {
try {
exchanger.exchange(
testString == get()
&& SwingUtilities.isEventDispatchThread());
} catch (Exception ignore) {
}
}
};
test.execute();
assertTrue(exchanger.exchange(null, TIME_OUT, TIME_OUT_UNIT));
}
//PropertyChangeListener should be notified on the EDT only
public final void testPropertyChange() throws Exception {
final Exchanger<Boolean> boolExchanger =
new Exchanger<Boolean>();
final SwingWorker<?,?> test =
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
firePropertyChange("test", null, "test");
return null;
}
};
test.addPropertyChangeListener(
new PropertyChangeListener() {
boolean isOnEDT = true;
public void propertyChange(PropertyChangeEvent evt) {
isOnEDT &= SwingUtilities.isEventDispatchThread();
if ("state".equals(evt.getPropertyName())
&& StateValue.DONE == evt.getNewValue()) {
try {
boolExchanger.exchange(isOnEDT);
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
}
});
test.execute();
assertTrue(boolExchanger.exchange(null, TIME_OUT, TIME_OUT_UNIT));
}
//the sequence should be
//StateValue.STARTED, done, StateValue.DONE
public final void testWorkFlow() throws Exception {
final List<Object> goldenSequence =
Arrays.asList(new Object[]{StateValue.STARTED, "done",
StateValue.DONE});
final List<Object> sequence =
Collections.synchronizedList(new ArrayList<Object>());
final Exchanger<List<Object>> listExchanger = new Exchanger<List<Object>>();
final SwingWorker<?,?> test =
new SwingWorker<Object,Object>() {
@Override
protected Object doInBackground() throws Exception {
return null;
}
@Override
protected void done() {
sequence.add("done");
}
};
test.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName())) {
sequence.add(evt.getNewValue());
if (StateValue.DONE == evt.getNewValue()) {
try {
listExchanger.exchange(sequence);
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
}
}
});
test.execute();
assertEquals(goldenSequence,
listExchanger.exchange(null, TIME_OUT, TIME_OUT_UNIT));
}
/*
* regression test for 6493680
* [SwingWorker notifications might be out of order.]
*/
public final void test6493680() throws Exception {
final AtomicInteger lastProgressValue = new AtomicInteger(-1);
final Exchanger<Boolean> exchanger = new Exchanger<Boolean>();
class Test {
private final AtomicInteger lastProgressValue =
new AtomicInteger(-1);
private final Exchanger<Boolean> exchanger =
new Exchanger<Boolean>();
boolean test() throws Exception {
TestSwingWorker swingWorker = new TestSwingWorker();
swingWorker.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress" == evt.getPropertyName()) {
lastProgressValue.set((Integer) evt.getNewValue());
}
}
});
swingWorker.execute();
return exchanger.exchange(true);
}
class TestSwingWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() throws Exception {
for (int i = 0; i <= 100; i++) {
Thread.sleep(1);
setProgress(i);
}
return null;
}
@Override
protected void done() {
boolean isPassed = (lastProgressValue.get() == 100);
try {
exchanger.exchange(isPassed);
} catch (Exception ingore) {
}
}
}
}
/*
* because timing is involved in this bug we will run the test
* NUMBER_OF_TRIES times.
* the tes`t passes if it does not fail once.
*/
final int NUMBER_OF_TRIES = 50;
for (int i = 0; i < NUMBER_OF_TRIES; i++) {
assertTrue((new Test()).test());
}
}
}
///////////////////////////////////////////////////////////
|