3 import java.awt.Component;
5 import javax.swing.ProgressMonitor;
6 import javax.swing.SwingUtilities;
7 import javax.swing.SwingWorker;
9 import javajs.async.SwingJSUtils.StateHelper;
10 import javajs.async.SwingJSUtils.StateMachine;
13 * Executes synchronous or asynchronous tasks using a SwingWorker in Java or JavaScript,
16 * Unlike a standard SwingWorker, AsyncSwingWorker may itself be asynchronous.
17 * For example, it might load a file asynchronously, or carry out a background
18 * process in JavaScript much like one might be done in Java, but with only a
21 * Whereas a standard SwingWorker would execute done() long before the
22 * asynchronous task completed, this class will wait until progress has been
23 * asynchronously set greater or equal to its max value or the task is canceled
24 * before executing that method.
26 * Three methods must be supplied by the subclass:
30 * int doInBackgroundAsync(int progress)
34 * Both initAsync() and doneAsync() are technically optional - they may be
35 * empty. doInBackgroundAsync(), however, is the key method where, like
36 * SwingWorker's doInBackground, the main work is done. The supplied progress
37 * parameter reminds the subclass of where it is at, and the return value allows
38 * the subclass to update the progress field in both the SwingWorker and the
41 * If it is desired to run the AsyncSwingWorker synchonously, call the
42 * executeSynchronously() method rather than execute(). Never call SwingWorker.run().
48 public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
51 public static final String DONE_ASYNC = "DONE_ASYNC";
52 public static final String CANCELED_ASYNC = "CANCELED_ASYNC";
54 protected int progressAsync;
57 * Override to provide initial tasks.
59 abstract public void initAsync();
62 * Given the last progress, do some portion of the task that the SwingWorker would do in the background, and return the new progress.
63 * returning max or above will complete the task.
66 * @return new progress
68 abstract public int doInBackgroundAsync(int progress);
71 * Do something when the task is finished or canceled.
74 abstract public void doneAsync();
77 protected ProgressMonitor progressMonitor;
78 protected int delayMillis;
79 protected String note;
82 protected int progressPercent;
84 protected boolean isAsync;
85 private Exception exception;
88 * Construct an asynchronous SwingWorker task that optionally will display a
89 * ProgressMonitor. Progress also can be monitored by adding a PropertyChangeListener
90 * to the AsyncSwingWorker and looking for the "progress" event, just the same as for a
91 * standard SwingWorker.
93 * @param owner optional owner for the ProgressMonitor, typically a JFrame or JDialog.
95 * @param title A non-null title indicates we want to use a ProgressMonitor with that title line.
97 * @param delayMillis A positive number indicating the delay we want before executions, during which progress will be reported.
99 * @param min The first progress value. No range limit.
101 * @param max The last progress value. No range limit; may be greater than min.
104 public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) {
105 if (title != null && delayMillis > 0) {
106 progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max));
107 progressMonitor.setProgress(Math.min(min, max)); // displays monitor
109 this.delayMillis = Math.max(0, delayMillis);
110 this.isAsync = (delayMillis > 0);
116 public void executeAsync() {
120 public void executeSynchronously() {
125 } catch (Exception e) {
132 public Exception getException() {
136 public int getMinimum() {
140 public void setMinimum(int min) {
142 if (progressMonitor != null)
143 progressMonitor.setMinimum(min);
146 public int getMaximum() {
150 public void setMaximum(int max) {
151 if (progressMonitor != null)
152 progressMonitor.setMaximum(max);
157 public int getProgressPercent() {
158 return progressPercent;
161 public void setNote(String note) {
163 if (progressMonitor != null)
164 progressMonitor.setNote(note);
170 * Cancel the asynchronous process.
173 public void cancelAsync() {
178 * Check to see if the asynchronous process has been canceled.
180 * @return true if StateHelper is not alive anymore
183 public boolean isCanceledAsync() {
184 return !helper.isAlive();
188 * Check to see if the asynchronous process is completely done.
190 * @return true only if the StateMachine is at STATE_DONE
193 public boolean isDoneAsync() {
194 return helper.getState() == STATE_DONE;
198 * Override to set a more informed note for the ProcessMonitor.
203 public String getNote(int progress) {
204 return String.format("Completed %d%%.\n", progress);
208 * Retrieve the last note delivered by the ProcessMonitor.
212 public String getNote() {
216 public int getProgressAsync() {
217 return progressAsync;
221 * Set the [min,max] progress safely.
223 * SwingWorker only allows progress between 0 and 100.
224 * This method safely translates [min,max] to [0,100].
228 public void setProgressAsync(int n) {
229 n = (max > min ? Math.max(min, Math.min(n, max))
230 : Math.max(max, Math.min(n, min)));
232 n = (int) ((n - min) * 100 / (max - min));
233 n = (n < 0 ? 0 : n > 100 ? 100 : n);
238 ///// the StateMachine /////
241 private final static int STATE_INIT = 0;
242 private final static int STATE_LOOP = 1;
243 private final static int STATE_WAIT = 2;
244 private final static int STATE_DONE = 99;
246 private StateHelper helper;
249 * The StateMachine's main loop.
251 * Note that a return from this method will exit doInBackground, trigger the
252 * isDone() state on the underying worker, and scheduling its done() for
253 * execution on the AWTEventQueue.
255 * Since this happens essentially immediately, it is unlikely that
256 * SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task
257 * itself won't be cancelable in Java or in JavaScript, since its
258 * doInBackground() method is officially complete, and isDone() is true well
259 * before we are "really" done. FutureTask will not set isCancelled() true once
262 * We are using an asynchronous task specifically because we want to have the
263 * opportunity for the ProgressMonitor to report in JavaScript. We will have to
264 * cancel our task and report progress explicitly using our own methods.
268 public boolean stateLoop() {
269 while (helper.isAlive()) {
270 switch (helper.getState()) {
272 setProgressAsync(min);
274 helper.setState(STATE_WAIT);
277 if (checkCanceled()) {
278 helper.setState(STATE_DONE);
279 firePropertyChange("state", null, CANCELED_ASYNC);
282 progressAsync = doInBackgroundAsync(progressAsync);
283 setProgressAsync(progressAsync);
284 setNote(getNote(progressAsync));
285 setProgress(progressPercent);
286 if (progressMonitor != null)
287 progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync);
288 helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT);
292 helper.setState(STATE_LOOP);
293 helper.sleep(delayMillis);
297 if (progressMonitor != null)
298 progressMonitor.close();
299 // Put the doneAsync() method on the AWTEventQueue
300 // just as for SwingWorker.done().
303 SwingUtilities.invokeLater(doneRunnable);
314 private Runnable doneRunnable = new Runnable() {
318 firePropertyChange("state", null, DONE_ASYNC);
324 private boolean checkCanceled() {
325 if (isMonitorCanceled() || isCancelled()) {
332 //// final SwingWorker methods not to be used by subclasses ////
334 private boolean isMonitorCanceled() {
335 return (progressMonitor != null && progressMonitor.isCanceled());
339 * see SwingWorker, made final here.
343 final protected Void doInBackground() throws Exception {
344 helper = new StateHelper(this);
345 setProgressAsync(min);
346 helper.next(STATE_INIT);
351 * see SwingWorker, made final here. Nothing to do.
355 final public void done() {