AsyncFetchTask for SequenceFetcher
[jalview.git] / src / javajs / async / AsyncSwingWorker.java
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  * Executes synchronous or asynchronous tasks using a SwingWorker in Java or JavaScript,
14  * equivalently.
15  * 
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
19  * single thread.
20  * 
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.
25  * 
26  * Three methods must be supplied by the subclass:
27  * 
28  * void initAsync()
29  * 
30  * int doInBackgroundAsync(int progress)
31  * 
32  * void doneAsync()
33  * 
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
39  * ProgressMonitor.
40  * 
41  * If it is desired to run the AsyncSwingWorker synchonously, call the 
42  * executeSynchronously() method rather than execute(). Never call SwingWorker.run(). 
43  * 
44  * 
45  * @author hansonr
46  *
47  */
48 public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
49         
50
51         public static final String DONE_ASYNC = "DONE_ASYNC";
52         public static final String CANCELED_ASYNC = "CANCELED_ASYNC";
53
54         protected int progressAsync;
55         
56         /**
57          * Override to provide initial tasks.
58          */
59         abstract public void initAsync();
60         
61         /**
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.
64          * 
65          * @param progress
66          * @return new progress
67          */
68         abstract public int doInBackgroundAsync(int progress);
69         
70         /**
71          * Do something when the task is finished or canceled.
72          * 
73          */
74         abstract public void doneAsync();
75
76
77         protected ProgressMonitor progressMonitor;
78         protected int delayMillis;
79         protected String note;
80         protected int min;
81         protected int max;
82         protected int progressPercent;
83
84         protected boolean isAsync;
85         private Exception exception;
86         
87         /**
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.
92          * 
93          * @param owner optional owner for the ProgressMonitor, typically a JFrame or JDialog.
94          * 
95          * @param title A non-null title indicates we want to use a ProgressMonitor with that title line.
96          * 
97          * @param delayMillis A positive number indicating the delay we want before executions, during which progress will be reported. 
98          * 
99          * @param min  The first progress value. No range limit.
100          * 
101          * @param max  The last progress value. No range limit; may be greater than min.
102          * 
103          */
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
108                 }
109                 this.delayMillis = Math.max(0, delayMillis);
110                 this.isAsync = (delayMillis > 0);
111                 
112                 this.min = min;
113                 this.max = max;
114         }
115
116         public void executeAsync() {
117                 super.execute();
118         }
119         
120         public void executeSynchronously() {
121                 isAsync = false;
122                 delayMillis = 0;
123                 try {
124                         doInBackground();
125                 } catch (Exception e) {
126                         exception = e;
127                         e.printStackTrace();
128                         cancelAsync();
129                 }
130         }
131
132         public Exception getException() {
133                 return exception;
134         }
135
136         public int getMinimum() {
137                 return min;
138         }
139
140         public void setMinimum(int min) {
141                 this.min = min;
142                 if (progressMonitor != null)
143                         progressMonitor.setMinimum(min);
144         }
145
146         public int getMaximum() {
147                 return max;
148         }
149
150         public void setMaximum(int max) {
151                 if (progressMonitor != null)
152                         progressMonitor.setMaximum(max);
153                 this.max = max;
154         }
155
156
157         public int getProgressPercent() {
158                 return progressPercent;
159         }
160
161         public void setNote(String note) {
162                 this.note = note;
163                 if (progressMonitor != null)
164                         progressMonitor.setNote(note);
165         }
166
167
168         
169         /**
170          * Cancel the asynchronous process.
171          * 
172          */
173         public void cancelAsync() {
174                 helper.interrupt();
175         }
176
177         /**
178          * Check to see if the asynchronous process has been canceled. 
179          *
180          * @return true if StateHelper is not alive anymore
181          * 
182          */
183         public boolean isCanceledAsync() {
184                 return !helper.isAlive();
185         }
186         
187         /**
188          * Check to see if the asynchronous process is completely done.
189          * 
190          * @return true only if the StateMachine is at STATE_DONE
191          * 
192          */
193         public boolean isDoneAsync() {
194                 return helper.getState() == STATE_DONE;
195         }
196
197         /**
198          * Override to set a more informed note for the ProcessMonitor.
199          * 
200          * @param progress
201          * @return
202          */
203         public String getNote(int progress) {
204                 return String.format("Completed %d%%.\n", progress);
205         }
206         
207         /**
208          * Retrieve the last note delivered by the ProcessMonitor.
209          * 
210          * @return
211          */
212         public String getNote() {
213                 return note;
214         }
215
216         public int getProgressAsync() {
217                 return progressAsync;
218         }
219
220         /**
221          * Set the [min,max] progress safely.
222          * 
223          * SwingWorker only allows progress between 0 and 100. 
224          * This method safely translates [min,max] to [0,100].
225          * 
226          * @param n
227          */
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)));
231                 progressAsync = n;
232                 n = (int) ((n - min) * 100 / (max - min));
233                 n = (n < 0 ? 0 : n > 100 ? 100 : n);
234                 progressPercent = n;
235         }
236         
237         
238         ///// the StateMachine /////
239         
240         
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;
245
246         private StateHelper helper;
247         
248         /**
249          * The StateMachine's main loop.
250          * 
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.
254          *
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
260          * the task has run.
261          * 
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.
265          * 
266          */
267         @Override
268         public boolean stateLoop() {
269                 while (helper.isAlive()) {
270                         switch (helper.getState()) {
271                         case STATE_INIT:
272                                 setProgressAsync(min);
273                                 initAsync();
274                                 helper.setState(STATE_WAIT);
275                                 continue;
276                         case STATE_LOOP:
277                                 if (checkCanceled()) {
278                                         helper.setState(STATE_DONE);
279                                         firePropertyChange("state", null, CANCELED_ASYNC);
280                                         continue;
281                                 } else {
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);
289                                         continue;
290                                 }
291                         case STATE_WAIT:
292                                 helper.setState(STATE_LOOP);
293                                 helper.sleep(delayMillis);
294                                 return true;
295                         default:
296                         case STATE_DONE:
297                                 if (progressMonitor != null)
298                                         progressMonitor.close();
299                                 // Put the doneAsync() method on the AWTEventQueue
300                                 // just as for SwingWorker.done().
301                                 if (isAsync)
302                                 {
303                                         SwingUtilities.invokeLater(doneRunnable);
304                                 } else {
305                                         doneRunnable.run();
306                                 }
307
308                                 return false;
309                         }
310                 }
311                 return false;
312         }
313         
314         private Runnable doneRunnable = new Runnable() {
315                 @Override
316                 public void run() {
317                         doneAsync();
318                         firePropertyChange("state", null, DONE_ASYNC);
319                 }
320
321         };
322
323
324     private boolean checkCanceled() {
325         if (isMonitorCanceled() || isCancelled()) {
326                 helper.interrupt();
327                 return true;
328         }
329                 return false;
330         }
331
332         //// final SwingWorker methods not to be used by subclasses ////
333
334         private boolean isMonitorCanceled() {
335                 return (progressMonitor != null && progressMonitor.isCanceled());
336         }
337
338         /**
339          * see SwingWorker, made final here.
340          * 
341          */
342         @Override
343         final protected Void doInBackground() throws Exception {
344                 helper = new StateHelper(this);
345                 setProgressAsync(min);
346                 helper.next(STATE_INIT);
347                 return null;
348         }
349
350         /**
351          * see SwingWorker, made final here. Nothing to do.
352          * 
353          */
354         @Override
355         final public void done() {
356         }
357
358 }