Skip to content

Commit f9a28b3

Browse files
committed
AsyncSwingWorker and ProgressBar with tests in test/components
1 parent 090d66e commit f9a28b3

File tree

16 files changed

+2348
-465
lines changed

16 files changed

+2348
-465
lines changed
-2.04 MB
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20200429091705
1+
20200430164525
-2.04 MB
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20200429091705
1+
20200430164525
12.3 KB
Binary file not shown.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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 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 to 100 or the task is cancelled before executing that
24+
* 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 init() and doneAsync() are technically optional - they may be empty.
35+
* doInBackground(), however, is the key method where, like SwingWorker's
36+
* doInBackground, the main work is done. The supplied progress parameter
37+
* reminds the subclass of where it is at, and the return value allows the
38+
* subclass to update the progress field in both the SwingWorker and the
39+
* ProgressMonitor.
40+
*
41+
*
42+
* @author hansonr
43+
*
44+
*/
45+
public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
46+
47+
48+
protected int progress = 0;
49+
50+
/**
51+
* Override to provide initial tasks.
52+
*/
53+
abstract public void initAsync();
54+
55+
/**
56+
* Given the last progress, do some portion of the task that the SwingWorker would do in the background, and return the new progress.
57+
* returning 100 will complete the task.
58+
*
59+
* @param progress
60+
* @return new progress
61+
*/
62+
abstract public int doInBackgroundAsync(int progress);
63+
64+
/**
65+
* Do something when the task is finished or cancelled.
66+
*
67+
*/
68+
abstract public void doneAsync();
69+
70+
71+
private ProgressMonitor progressMonitor;
72+
private int delayMillis;
73+
private String note;
74+
75+
/**
76+
* Construct an asynchronous SwingWorker task that optionally will display a
77+
* ProgressMonitor. Progress also can be monitored by adding a PropertyChangeListener
78+
* to the AsyncSwingWorker and looking for the "progress" event, just the same as for a
79+
* standard SwingWorker.
80+
*
81+
* @param owner optional owner for the ProgressMonitor, typically a JFrame or JDialog.
82+
*
83+
* @param title A non-null title indicates we want to use a ProgressMonitor with that title line.
84+
*
85+
* @param delayMillis A positive number indicating the delay we want before executions, during which progress will be reported.
86+
*/
87+
public AsyncSwingWorker(Component owner, String title, int delayMillis) {
88+
if (title != null) {
89+
progressMonitor = new ProgressMonitor(owner, title, "", 0, 100);
90+
progressMonitor.setProgress(0); // displays monitor
91+
}
92+
this.delayMillis = Math.max(1, delayMillis);
93+
}
94+
95+
/**
96+
* Cancel the asynchronous process.
97+
*
98+
*/
99+
public void cancelAsync() {
100+
helper.interrupt();
101+
}
102+
103+
/**
104+
* Check to see if the asynchronous process has been cancelled.
105+
*
106+
* @return true if StateHelper is not alive anymore
107+
*
108+
*/
109+
public boolean isCancelledAsync() {
110+
return !helper.isAlive();
111+
}
112+
113+
/**
114+
* Check to see if the asynchronous process is completely done.
115+
*
116+
* @return true only if the StateMachine is at STATE_DONE
117+
*
118+
*/
119+
public boolean isDoneAsync() {
120+
return helper.getState() == STATE_DONE;
121+
}
122+
123+
/**
124+
* Override to set a more informed note for the ProcessMonitor.
125+
*
126+
* @param progress
127+
* @return
128+
*/
129+
public String setNote(int progress) {
130+
return String.format("Completed %d%%.\n", progress);
131+
}
132+
133+
/**
134+
* Retrieve the last note delivered by the ProcessMonitor.
135+
*
136+
* @return
137+
*/
138+
public String getNote() {
139+
return note;
140+
}
141+
142+
public int getProgressAsync() {
143+
return progress;
144+
}
145+
146+
147+
148+
///// the StateMachine /////
149+
150+
151+
private final static int STATE_INIT = 0;
152+
private final static int STATE_LOOP = 1;
153+
private final static int STATE_WAIT = 2;
154+
private final static int STATE_DONE = 99;
155+
156+
private StateHelper helper;
157+
158+
/**
159+
* The StateMachine's main loop.
160+
*
161+
* Note that a return from this method will exit doInBackground, trigger the
162+
* isDone() state on the underying worker, and scheduling its done() for
163+
* execution on the AWTEventQueue.
164+
*
165+
* Since this happens essentially immediately, it is unlikely that
166+
* SwingWorker.isCancelled() will ever be true. Thus, the SwingWorker task
167+
* itself won't be cancelable in Java or in JavaScript, since its
168+
* doInBackground() method is officially complete, and isDone() is true well
169+
* before we are "really" done. FutureTask will not set isCancelled() true once
170+
* the task has run.
171+
*
172+
* We are using an asynchronous task specifically because we want to have the
173+
* opportunity for the ProgressMonitor to report in JavaScript. We will have to
174+
* cancel our task and report progress explicitly using our own methods.
175+
*
176+
*/
177+
@Override
178+
public boolean stateLoop() {
179+
while (helper.isAlive()) {
180+
switch (helper.getState()) {
181+
case STATE_INIT:
182+
initAsync();
183+
helper.setState(STATE_WAIT);
184+
continue;
185+
case STATE_LOOP:
186+
progress = doInBackgroundAsync(progress);
187+
progress = Math.min(progress, 100);
188+
note = setNote(progress);
189+
if (progressMonitor != null) {
190+
progressMonitor.setNote(note);
191+
progressMonitor.setProgress(progress);
192+
}
193+
if (progress >= 100 || isCancelled()) {
194+
helper.setState(STATE_DONE);
195+
} else {
196+
helper.setState(STATE_WAIT);
197+
}
198+
setProgress(progress);
199+
continue;
200+
case STATE_WAIT:
201+
helper.setState(STATE_LOOP);
202+
helper.sleep(delayMillis);
203+
return true;
204+
case STATE_DONE:
205+
// Put the reallyDone() method on the AWTEventQueue
206+
// just as the done() method was.
207+
SwingUtilities.invokeLater(new Runnable() {
208+
209+
@Override
210+
public void run() {
211+
doneAsync();
212+
}
213+
214+
});
215+
return false;
216+
}
217+
}
218+
return false;
219+
}
220+
221+
//// final SwingWorker methods not to be used by subclasses ////
222+
223+
/**
224+
* see SwingWorker, made final here.
225+
*
226+
*/
227+
@Override
228+
final protected Void doInBackground() throws Exception {
229+
helper = new StateHelper(AsyncSwingWorker.this);
230+
setProgress(0);
231+
helper.next(STATE_INIT);
232+
return null;
233+
}
234+
235+
/**
236+
* see SwingWorker, made final here. Nothing to do.
237+
*
238+
*/
239+
@Override
240+
final public void done() {
241+
}
242+
243+
244+
}

0 commit comments

Comments
 (0)