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;
15 * Executes synchronous or asynchronous tasks using a SwingWorker in Java or
16 * JavaScript, equivalently.
18 * Unlike a standard SwingWorker, AsyncSwingWorker may itself be asynchronous.
19 * For example, it might load a file asynchronously, or carry out a background
20 * process in JavaScript much like one might be done in Java, but with only a
23 * Whereas a standard SwingWorker would execute done() long before the
24 * asynchronous task completed, this class will wait until progress has been
25 * asynchronously set greater or equal to its max value or the task is canceled
26 * before executing that method.
28 * Three methods must be supplied by the subclass:
32 * int doInBackgroundAsync(int progress)
36 * Both initAsync() and doneAsync() are technically optional - they may be
37 * empty. doInBackgroundAsync(), however, is the key method where, like
38 * SwingWorker's doInBackground, the main work is done. The supplied progress
39 * parameter reminds the subclass of where it is at, and the return value allows
40 * the subclass to update the progress field in both the SwingWorker and the
43 * If it is desired to run the AsyncSwingWorker synchronously, call the
44 * executeSynchronously() method rather than execute(). Never call
47 * Note that doInBackgroundAsync runs on the Java AWT event queue. This means
48 * that, unlike a true SwingWorker, it will run in event-queue sequence, after
49 * anything that that method itself adds to the queue. This is what SwingWorker itself
50 * does with its done() signal.
52 * If doInBackgroundAsync has tasks that are time intensive, the thing to do is to
54 * (a) pause this worker by setting the value of progress for the NEXT step:
56 * setProgressAsync(n);
58 * (b) pause the timer so that when doInBackgroundAsync returns, the timer is not fired:
62 * (c) start your process as new Thread, which bypasses the AWT EventQueue:
64 * new Thread(Runnable).start();
66 * (d) have your thread, when it is done, return control to this worker:
70 * This final call restarts the worker with the currently specified progress value.
75 public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
78 // PropertyChangeEvent getPropertyName()
80 private static final String PROPERTY_STATE = "state";
81 private static final String PROPERTY_PAUSE = "pause";
83 // PropertyChangeEvent getNewValue()
85 public static final String STARTED_ASYNC = "STARTED_ASYNC";
86 public static final String STARTED_SYNC = "STARTED_SYNC";
88 public static final String DONE_ASYNC = "DONE_ASYNC";
89 public static final String CANCELED_ASYNC = "CANCELED_ASYNC";
91 public static final String PAUSED = "PAUSED";
92 public static final String RESUMED = "RESUMED";
94 protected int progressAsync;
97 * Override to provide initial tasks.
99 abstract public void initAsync();
102 * Given the last progress, do some portion of the task that the SwingWorker
103 * would do in the background, and return the new progress. returning max or
104 * above will complete the task.
107 * @return new progress
109 abstract public int doInBackgroundAsync(int progress);
112 * Do something when the task is finished or canceled.
115 abstract public void doneAsync();
117 protected ProgressMonitor progressMonitor;
119 protected int delayMillis;
120 protected String note;
123 protected int progressPercent;
125 protected boolean isAsync;
126 private Exception exception;
129 * Construct an asynchronous SwingWorker task that optionally will display a
130 * ProgressMonitor. Progress also can be monitored by adding a
131 * PropertyChangeListener to the AsyncSwingWorker and looking for the "progress"
132 * event, just the same as for a standard SwingWorker.
134 * @param owner optional owner for the ProgressMonitor, typically a JFrame
137 * @param title A non-null title indicates we want to use a
138 * ProgressMonitor with that title line.
140 * @param delayMillis A positive number indicating the delay we want before
141 * executions, during which progress will be reported.
143 * @param min The first progress value. No range limit.
145 * @param max The last progress value. No range limit; may be greater
149 public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) {
150 if (title != null && delayMillis > 0) {
151 progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max));
152 progressMonitor.setProgress(Math.min(min, max)); // displays monitor
154 this.delayMillis = Math.max(0, delayMillis);
155 this.isAsync = (delayMillis > 0);
161 public void executeAsync() {
162 firePropertyChange(PROPERTY_STATE, null, STARTED_ASYNC);
166 public void executeSynchronously() {
167 firePropertyChange(PROPERTY_STATE, null, STARTED_SYNC);
172 } catch (Exception e) {
179 public Exception getException() {
183 public int getMinimum() {
187 public void setMinimum(int min) {
189 if (progressMonitor != null) {
190 progressMonitor.setMinimum(min);
194 public int getMaximum() {
198 public void setMaximum(int max) {
199 if (progressMonitor != null) {
200 progressMonitor.setMaximum(max);
205 public int getProgressPercent() {
206 return progressPercent;
209 public void setNote(String note) {
211 if (progressMonitor != null) {
212 progressMonitor.setNote(note);
217 * Cancel the asynchronous process.
220 public void cancelAsync() {
225 * Check to see if the asynchronous process has been canceled.
227 * @return true if StateHelper is not alive anymore
230 public boolean isCanceledAsync() {
231 return !helper.isAlive();
235 * Check to see if the asynchronous process is completely done.
237 * @return true only if the StateMachine is at STATE_DONE
240 public boolean isDoneAsync() {
241 return helper.getState() == STATE_DONE;
245 * Override to set a more informed note for the ProcessMonitor.
250 public String getNote(int progress) {
251 return String.format("Completed %d%%.\n", progress);
255 * Retrieve the last note delivered by the ProcessMonitor.
259 public String getNote() {
263 public int getProgressAsync() {
264 return progressAsync;
268 * Set the [min,max] progress safely.
270 * SwingWorker only allows progress between 0 and 100. This method safely
271 * translates [min,max] to [0,100].
275 public void setProgressAsync(int n) {
276 n = (max > min ? Math.max(min, Math.min(n, max)) : Math.max(max, Math.min(n, min)));
278 n = (n - min) * 100 / (max - min);
279 n = (n < 0 ? 0 : n > 100 ? 100 : n);
283 ///// the StateMachine /////
285 private final static int STATE_INIT = 0;
286 private final static int STATE_LOOP = 1;
287 private final static int STATE_WAIT = 2;
288 private final static int STATE_DONE = 99;
290 private StateHelper helper;
292 protected StateHelper getHelper() {
296 private boolean isPaused;
298 protected void setPaused(boolean tf) {
300 firePropertyChange(PROPERTY_PAUSE, null, (tf ? PAUSED : RESUMED));
305 protected boolean isPaused() {
310 * The StateMachine's main loop.
312 * Note that a return from this method will exit doInBackground, trigger the
313 * isDone() state on the underying worker, and scheduling its done() for
314 * execution on the AWTEventQueue.
316 * Since this happens essentially immediately, it is unlikely that
317 * SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task
318 * itself won't be cancelable in Java or in JavaScript, since its
319 * doInBackground() method is officially complete, and isDone() is true well
320 * before we are "really" done. FutureTask will not set isCancelled() true once
323 * We are using an asynchronous task specifically because we want to have the
324 * opportunity for the ProgressMonitor to report in JavaScript. We will have to
325 * cancel our task and report progress explicitly using our own methods.
329 public boolean stateLoop() {
330 while (helper.isAlive() && !isPaused) {
331 switch (helper.getState()) {
333 setProgressAsync(min);
335 helper.setState(STATE_WAIT);
338 if (checkCanceled()) {
339 helper.setState(STATE_DONE);
340 firePropertyChange(PROPERTY_STATE, null, CANCELED_ASYNC);
342 int ret = doInBackgroundAsync(progressAsync);
343 if (!helper.isAlive() || isPaused) {
347 setProgressAsync(progressAsync);
348 setNote(getNote(progressAsync));
349 setProgress(progressPercent);
350 if (progressMonitor != null) {
351 progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync);
353 helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT);
357 // meaning "sleep" and then "loop"
358 helper.setState(STATE_LOOP);
359 helper.sleep(delayMillis);
363 stopProgressMonitor();
364 // Put the doneAsync() method on the AWTEventQueue
365 // just as for SwingWorker.done().
367 SwingUtilities.invokeLater(doneRunnable);
375 if (!helper.isAlive()) {
376 stopProgressMonitor();
381 private void stopProgressMonitor() {
382 if (progressMonitor != null) {
383 progressMonitor.close();
384 progressMonitor = null;
388 private Runnable doneRunnable = new Runnable() {
392 firePropertyChange(PROPERTY_STATE, null, DONE_ASYNC);
397 private boolean checkCanceled() {
398 if (isMonitorCanceled() || isCancelled()) {
405 //// final SwingWorker methods not to be used by subclasses ////
407 private boolean isMonitorCanceled() {
408 return (progressMonitor != null && progressMonitor.isCanceled());
412 * see SwingWorker, made final here.
416 final protected Void doInBackground() throws Exception {
417 helper = new StateHelper(this);
418 setProgressAsync(min);
419 helper.next(STATE_INIT);
424 * see SwingWorker, made final here. Nothing to do.
428 final public void done() {