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
14 * JavaScript, equivalently.
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
49 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
63 * would do in the background, and return the new progress. returning max or
64 * above will complete the task.
67 * @return new progress
69 abstract public int doInBackgroundAsync(int progress);
72 * Do something when the task is finished or canceled.
75 abstract public void doneAsync();
77 protected ProgressMonitor progressMonitor;
79 protected int delayMillis;
80 protected String note;
83 protected int progressPercent;
85 protected boolean isAsync;
86 private Exception exception;
89 * Construct an asynchronous SwingWorker task that optionally will display a
90 * ProgressMonitor. Progress also can be monitored by adding a
91 * PropertyChangeListener to the AsyncSwingWorker and looking for the "progress"
92 * event, just the same as for a standard SwingWorker.
94 * @param owner optional owner for the ProgressMonitor, typically a JFrame
97 * @param title A non-null title indicates we want to use a
98 * ProgressMonitor with that title line.
100 * @param delayMillis A positive number indicating the delay we want before
101 * executions, during which progress will be reported.
103 * @param min The first progress value. No range limit.
105 * @param max The last progress value. No range limit; may be greater
109 public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) {
110 if (title != null && delayMillis > 0) {
111 progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max));
112 progressMonitor.setProgress(Math.min(min, max)); // displays monitor
114 this.delayMillis = Math.max(0, delayMillis);
115 this.isAsync = (delayMillis > 0);
121 public void executeAsync() {
125 public void executeSynchronously() {
130 } catch (Exception e) {
137 public Exception getException() {
141 public int getMinimum() {
145 public void setMinimum(int min) {
147 if (progressMonitor != null) {
148 progressMonitor.setMinimum(min);
152 public int getMaximum() {
156 public void setMaximum(int max) {
157 if (progressMonitor != null) {
158 progressMonitor.setMaximum(max);
163 public int getProgressPercent() {
164 return progressPercent;
167 public void setNote(String note) {
169 if (progressMonitor != null) {
170 progressMonitor.setNote(note);
175 * Cancel the asynchronous process.
178 public void cancelAsync() {
183 * Check to see if the asynchronous process has been canceled.
185 * @return true if StateHelper is not alive anymore
188 public boolean isCanceledAsync() {
189 return !helper.isAlive();
193 * Check to see if the asynchronous process is completely done.
195 * @return true only if the StateMachine is at STATE_DONE
198 public boolean isDoneAsync() {
199 return helper.getState() == STATE_DONE;
203 * Override to set a more informed note for the ProcessMonitor.
208 public String getNote(int progress) {
209 return String.format("Completed %d%%.\n", progress);
213 * Retrieve the last note delivered by the ProcessMonitor.
217 public String getNote() {
221 public int getProgressAsync() {
222 return progressAsync;
226 * Set the [min,max] progress safely.
228 * SwingWorker only allows progress between 0 and 100. This method safely
229 * translates [min,max] to [0,100].
233 public void setProgressAsync(int n) {
234 n = (max > min ? Math.max(min, Math.min(n, max)) : Math.max(max, Math.min(n, min)));
236 n = (n - min) * 100 / (max - min);
237 n = (n < 0 ? 0 : n > 100 ? 100 : n);
241 ///// the StateMachine /////
243 private final static int STATE_INIT = 0;
244 private final static int STATE_LOOP = 1;
245 private final static int STATE_WAIT = 2;
246 private final static int STATE_DONE = 99;
248 private StateHelper helper;
250 protected StateHelper getHelper() {
254 private boolean isPaused;
256 protected void setPaused(boolean tf) {
260 protected boolean isPaused() {
265 * The StateMachine's main loop.
267 * Note that a return from this method will exit doInBackground, trigger the
268 * isDone() state on the underying worker, and scheduling its done() for
269 * execution on the AWTEventQueue.
271 * Since this happens essentially immediately, it is unlikely that
272 * SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task
273 * itself won't be cancelable in Java or in JavaScript, since its
274 * doInBackground() method is officially complete, and isDone() is true well
275 * before we are "really" done. FutureTask will not set isCancelled() true once
278 * We are using an asynchronous task specifically because we want to have the
279 * opportunity for the ProgressMonitor to report in JavaScript. We will have to
280 * cancel our task and report progress explicitly using our own methods.
284 public boolean stateLoop() {
285 while (helper.isAlive() && !isPaused) {
286 switch (helper.getState()) {
288 setProgressAsync(min);
290 helper.setState(STATE_WAIT);
293 if (checkCanceled()) {
294 helper.setState(STATE_DONE);
295 firePropertyChange("state", null, CANCELED_ASYNC);
297 int ret = doInBackgroundAsync(progressAsync);
298 if (!helper.isAlive() || isPaused) {
302 setProgressAsync(progressAsync);
303 setNote(getNote(progressAsync));
304 setProgress(progressPercent);
305 if (progressMonitor != null) {
306 progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync);
308 helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT);
312 helper.setState(STATE_LOOP);
313 helper.sleep(delayMillis);
317 stopProgressMonitor();
318 // Put the doneAsync() method on the AWTEventQueue
319 // just as for SwingWorker.done().
321 SwingUtilities.invokeLater(doneRunnable);
329 if (!helper.isAlive()) {
330 stopProgressMonitor();
335 private void stopProgressMonitor() {
336 if (progressMonitor != null) {
337 progressMonitor.close();
338 progressMonitor = null;
342 private Runnable doneRunnable = new Runnable() {
346 firePropertyChange("state", null, DONE_ASYNC);
351 private boolean checkCanceled() {
352 if (isMonitorCanceled() || isCancelled()) {
359 //// final SwingWorker methods not to be used by subclasses ////
361 private boolean isMonitorCanceled() {
362 return (progressMonitor != null && progressMonitor.isCanceled());
366 * see SwingWorker, made final here.
370 final protected Void doInBackground() throws Exception {
371 helper = new StateHelper(this);
372 setProgressAsync(min);
373 helper.next(STATE_INIT);
378 * see SwingWorker, made final here. Nothing to do.
382 final public void done() {