Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package javajs.async

File AsyncSwingWorker.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

38
97
27
1
431
209
55
0.57
3.59
27
2.04

Classes

Class Line # Actions
AsyncSwingWorker 75 97 55
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    package javajs.async;
2   
3    import java.awt.Component;
4   
5    import javax.swing.ProgressMonitor;
6    import javax.swing.SwingUtilities;
7    import javax.swing.SwingWorker;
8   
9    import javajs.async.SwingJSUtils.StateHelper;
10    import javajs.async.SwingJSUtils.StateMachine;
11   
12    /**
13    * v. 2020.06.03
14    *
15    * Executes synchronous or asynchronous tasks using a SwingWorker in Java or
16    * JavaScript, equivalently.
17    *
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
21    * single thread.
22    *
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.
27    *
28    * Three methods must be supplied by the subclass:
29    *
30    * void initAsync()
31    *
32    * int doInBackgroundAsync(int progress)
33    *
34    * void doneAsync()
35    *
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
41    * ProgressMonitor.
42    *
43    * If it is desired to run the AsyncSwingWorker synchronously, call the
44    * executeSynchronously() method rather than execute(). Never call
45    * SwingWorker.run().
46    *
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.
51    *
52    * If doInBackgroundAsync has tasks that are time intensive, the thing to do is to
53    *
54    * (a) pause this worker by setting the value of progress for the NEXT step:
55    *
56    * setProgressAsync(n);
57    *
58    * (b) pause the timer so that when doInBackgroundAsync returns, the timer is not fired:
59    *
60    * setPaused(true);
61    *
62    * (c) start your process as new Thread, which bypasses the AWT EventQueue:
63    *
64    * new Thread(Runnable).start();
65    *
66    * (d) have your thread, when it is done, return control to this worker:
67    *
68    * setPaused(false);
69    *
70    * This final call restarts the worker with the currently specified progress value.
71    *
72    * @author hansonr
73    *
74    */
 
75    public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
76   
77   
78    // PropertyChangeEvent getPropertyName()
79   
80    private static final String PROPERTY_STATE = "state";
81    private static final String PROPERTY_PAUSE = "pause";
82   
83    // PropertyChangeEvent getNewValue()
84   
85    public static final String STARTED_ASYNC = "STARTED_ASYNC";
86    public static final String STARTED_SYNC = "STARTED_SYNC";
87   
88    public static final String DONE_ASYNC = "DONE_ASYNC";
89    public static final String CANCELED_ASYNC = "CANCELED_ASYNC";
90   
91    public static final String PAUSED = "PAUSED";
92    public static final String RESUMED = "RESUMED";
93   
94    protected int progressAsync;
95   
96    /**
97    * Override to provide initial tasks.
98    */
99    abstract public void initAsync();
100   
101    /**
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.
105    *
106    * @param progress
107    * @return new progress
108    */
109    abstract public int doInBackgroundAsync(int progress);
110   
111    /**
112    * Do something when the task is finished or canceled.
113    *
114    */
115    abstract public void doneAsync();
116   
117    protected ProgressMonitor progressMonitor;
118   
119    protected int delayMillis;
120    protected String note;
121    protected int min;
122    protected int max;
123    protected int progressPercent;
124   
125    protected boolean isAsync;
126    private Exception exception;
127   
128    /**
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.
133    *
134    * @param owner optional owner for the ProgressMonitor, typically a JFrame
135    * or JDialog.
136    *
137    * @param title A non-null title indicates we want to use a
138    * ProgressMonitor with that title line.
139    *
140    * @param delayMillis A positive number indicating the delay we want before
141    * executions, during which progress will be reported.
142    *
143    * @param min The first progress value. No range limit.
144    *
145    * @param max The last progress value. No range limit; may be greater
146    * than min.
147    *
148    */
 
149  0 toggle public AsyncSwingWorker(Component owner, String title, int delayMillis, int min, int max) {
150  0 if (title != null && delayMillis > 0) {
151  0 progressMonitor = new ProgressMonitor(owner, title, "", Math.min(min, max), Math.max(min, max));
152  0 progressMonitor.setProgress(Math.min(min, max)); // displays monitor
153    }
154  0 this.delayMillis = Math.max(0, delayMillis);
155  0 this.isAsync = (delayMillis > 0);
156   
157  0 this.min = min;
158  0 this.max = max;
159    }
160   
 
161  0 toggle public void executeAsync() {
162  0 firePropertyChange(PROPERTY_STATE, null, STARTED_ASYNC);
163  0 super.execute();
164    }
165   
 
166  0 toggle public void executeSynchronously() {
167  0 firePropertyChange(PROPERTY_STATE, null, STARTED_SYNC);
168  0 isAsync = false;
169  0 delayMillis = 0;
170  0 try {
171  0 doInBackground();
172    } catch (Exception e) {
173  0 exception = e;
174  0 e.printStackTrace();
175  0 cancelAsync();
176    }
177    }
178   
 
179  0 toggle public Exception getException() {
180  0 return exception;
181    }
182   
 
183  0 toggle public int getMinimum() {
184  0 return min;
185    }
186   
 
187  0 toggle public void setMinimum(int min) {
188  0 this.min = min;
189  0 if (progressMonitor != null) {
190  0 progressMonitor.setMinimum(min);
191    }
192    }
193   
 
194  0 toggle public int getMaximum() {
195  0 return max;
196    }
197   
 
198  0 toggle public void setMaximum(int max) {
199  0 if (progressMonitor != null) {
200  0 progressMonitor.setMaximum(max);
201    }
202  0 this.max = max;
203    }
204   
 
205  0 toggle public int getProgressPercent() {
206  0 return progressPercent;
207    }
208   
 
209  0 toggle public void setNote(String note) {
210  0 this.note = note;
211  0 if (progressMonitor != null) {
212  0 progressMonitor.setNote(note);
213    }
214    }
215   
216    /**
217    * Cancel the asynchronous process.
218    *
219    */
 
220  0 toggle public void cancelAsync() {
221  0 helper.interrupt();
222    }
223   
224    /**
225    * Check to see if the asynchronous process has been canceled.
226    *
227    * @return true if StateHelper is not alive anymore
228    *
229    */
 
230  0 toggle public boolean isCanceledAsync() {
231  0 return !helper.isAlive();
232    }
233   
234    /**
235    * Check to see if the asynchronous process is completely done.
236    *
237    * @return true only if the StateMachine is at STATE_DONE
238    *
239    */
 
240  0 toggle public boolean isDoneAsync() {
241  0 return helper.getState() == STATE_DONE;
242    }
243   
244    /**
245    * Override to set a more informed note for the ProcessMonitor.
246    *
247    * @param progress
248    * @return
249    */
 
250  0 toggle public String getNote(int progress) {
251  0 return String.format("Completed %d%%.\n", progress);
252    }
253   
254    /**
255    * Retrieve the last note delivered by the ProcessMonitor.
256    *
257    * @return
258    */
 
259  0 toggle public String getNote() {
260  0 return note;
261    }
262   
 
263  0 toggle public int getProgressAsync() {
264  0 return progressAsync;
265    }
266   
267    /**
268    * Set the [min,max] progress safely.
269    *
270    * SwingWorker only allows progress between 0 and 100. This method safely
271    * translates [min,max] to [0,100].
272    *
273    * @param n
274    */
 
275  0 toggle public void setProgressAsync(int n) {
276  0 n = (max > min ? Math.max(min, Math.min(n, max)) : Math.max(max, Math.min(n, min)));
277  0 progressAsync = n;
278  0 n = (n - min) * 100 / (max - min);
279  0 n = (n < 0 ? 0 : n > 100 ? 100 : n);
280  0 progressPercent = n;
281    }
282   
283    ///// the StateMachine /////
284   
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;
289   
290    private StateHelper helper;
291   
 
292  0 toggle protected StateHelper getHelper() {
293  0 return helper;
294    }
295   
296    private boolean isPaused;
297   
 
298  0 toggle protected void setPaused(boolean tf) {
299  0 isPaused = tf;
300  0 firePropertyChange(PROPERTY_PAUSE, null, (tf ? PAUSED : RESUMED));
301  0 if (!tf)
302  0 stateLoop();
303    }
304   
 
305  0 toggle protected boolean isPaused() {
306  0 return isPaused;
307    }
308   
309    /**
310    * The StateMachine's main loop.
311    *
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.
315    *
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
321    * the task has run.
322    *
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.
326    *
327    */
 
328  0 toggle @Override
329    public boolean stateLoop() {
330  0 while (helper.isAlive() && !isPaused) {
331  0 switch (helper.getState()) {
332  0 case STATE_INIT:
333  0 setProgressAsync(min);
334  0 initAsync();
335  0 helper.setState(STATE_WAIT);
336  0 continue;
337  0 case STATE_LOOP:
338  0 if (checkCanceled()) {
339  0 helper.setState(STATE_DONE);
340  0 firePropertyChange(PROPERTY_STATE, null, CANCELED_ASYNC);
341    } else {
342  0 int ret = doInBackgroundAsync(progressAsync);
343  0 if (!helper.isAlive() || isPaused) {
344  0 continue;
345    }
346  0 progressAsync = ret;
347  0 setProgressAsync(progressAsync);
348  0 setNote(getNote(progressAsync));
349  0 setProgress(progressPercent);
350  0 if (progressMonitor != null) {
351  0 progressMonitor.setProgress(max > min ? progressAsync : max + min - progressAsync);
352    }
353  0 helper.setState(progressAsync == max ? STATE_DONE : STATE_WAIT);
354    }
355  0 continue;
356  0 case STATE_WAIT:
357    // meaning "sleep" and then "loop"
358  0 helper.setState(STATE_LOOP);
359  0 helper.sleep(delayMillis);
360  0 return true;
361  0 default:
362  0 case STATE_DONE:
363  0 stopProgressMonitor();
364    // Put the doneAsync() method on the AWTEventQueue
365    // just as for SwingWorker.done().
366  0 if (isAsync) {
367  0 SwingUtilities.invokeLater(doneRunnable);
368    } else {
369  0 doneRunnable.run();
370    }
371   
372  0 return false;
373    }
374    }
375  0 if (!helper.isAlive()) {
376  0 stopProgressMonitor();
377    }
378  0 return false;
379    }
380   
 
381  0 toggle private void stopProgressMonitor() {
382  0 if (progressMonitor != null) {
383  0 progressMonitor.close();
384  0 progressMonitor = null;
385    }
386    }
387   
388    private Runnable doneRunnable = new Runnable() {
 
389  0 toggle @Override
390    public void run() {
391  0 doneAsync();
392  0 firePropertyChange(PROPERTY_STATE, null, DONE_ASYNC);
393    }
394   
395    };
396   
 
397  0 toggle private boolean checkCanceled() {
398  0 if (isMonitorCanceled() || isCancelled()) {
399  0 helper.interrupt();
400  0 return true;
401    }
402  0 return false;
403    }
404   
405    //// final SwingWorker methods not to be used by subclasses ////
406   
 
407  0 toggle private boolean isMonitorCanceled() {
408  0 return (progressMonitor != null && progressMonitor.isCanceled());
409    }
410   
411    /**
412    * see SwingWorker, made final here.
413    *
414    */
 
415  0 toggle @Override
416    final protected Void doInBackground() throws Exception {
417  0 helper = new StateHelper(this);
418  0 setProgressAsync(min);
419  0 helper.next(STATE_INIT);
420  0 return null;
421    }
422   
423    /**
424    * see SwingWorker, made final here. Nothing to do.
425    *
426    */
 
427  0 toggle @Override
428    final public void done() {
429    }
430   
431    }