/Users/lyon/j4p/src/classUtils/pack/util/CommandRunner.java
|
1 package classUtils.pack.util;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.Toolkit;
7 import java.awt.event.WindowAdapter;
8 import java.awt.event.WindowEvent;
9 import java.io.ByteArrayOutputStream;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.PrintStream;
13 import java.lang.reflect.Method;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.Set;
17
18 import javax.swing.JFrame;
19 import javax.swing.JLabel;
20 import javax.swing.JOptionPane;
21
22 import classUtils.pack.util.util.JPanelOutputStream;
23 import classUtils.pack.util.util.JPanelPrintStream;
24 import classUtils.pack.util.util.TimeInterval;
25
26 /**
27 * A class to run a command in a separate thread,
28 * monitor its progress via a monitor window and being notified of its completion,
29 * exit status, and elasped time.
30 *
31 * @author cris
32 * @version 1.0
33 */
34 public class CommandRunner extends Thread {
35
36 /**
37 * Classes implementing this interface may receive {@link CommandRunner CommandRunner} events.
38 */
39 public static interface Listener {
40 public void starting();
41 public void failed(Exception e);
42 public void started();
43 public void terminated(int exitValue, long elapsed);
44 }
45
46 /**
47 * An helper class which implements all the methods in the {@link CommandRunner.Listener CommandRunner.Listener}
48 * interface in an empty way.
49 */
50 public static class Adapter implements Listener {
51 public void starting() {}
52 public void failed(Exception e) {}
53 public void started() {}
54 public void terminated(int exitValue, long elapsed) {}
55 }
56
57 public static class StdOutAdapter implements Listener {
58 public void starting() { System.out.println("Starting"); }
59 public void failed(Exception e) { e.printStackTrace(); }
60 public void started() { System.out.println("Started"); }
61 public void terminated(int exitValue, long elapsed) { System.out.println("Terminated with exit value "+exitValue+" in "+elapsed+"ms"); }
62 }
63
64 private String command;
65 private Class cls;
66 private String[] args;
67 private Component parent;
68 private boolean openWindowOnStartup=true;
69 private boolean outputOnSystemOut=false;
70 private boolean closeWindowOnTermination=false;
71 private boolean captureOutput=false;
72 private boolean outputElapsedTime=true;
73 private String lastOutput;
74 private JFrame jf;
75
76 private Set listeners = new HashSet();
77
78 /**
79 * Create a command runner with given parent window, running a system command with
80 * the given arguments. If used as a thread, the command runner has the given daemon
81 * status.
82 * @param parent the parent window for the CommandRunner monitor
83 * @param command the executable to run
84 * @param args the arguments to pass to the executable
85 * @param daemon the daemon status of this {@link java.lang.Thread Thread}
86 */
87 public CommandRunner(
88 Component parent,
89 String command,
90 String[] args,
91 boolean daemon) {
92 setDaemon(daemon);
93 this.parent = parent;
94 this.command = command;
95 this.args = unquote(args);
96 }
97
98 /**
99 * Create a command runner with given parent window, running a class' main() method with
100 * the given arguments. If used as a thread, the command runner has the given daemon
101 * status.
102 * @param parent the parent window for the CommandRunner monitor
103 * @param cls the class whose main method is to be run
104 * @param args the arguments to pass to the executable
105 * @param daemon the daemon status of this {@link java.lang.Thread Thread}
106 */
107 public CommandRunner(
108 Component parent,
109 Class cls,
110 String[] args,
111 boolean daemon) {
112 setDaemon(daemon);
113 this.parent = parent;
114 this.cls = cls;
115 this.args = unquote(args);
116 }
117
118 /**
119 * Create a command runner with no parent window, running a class' main() method with
120 * the given arguments. If used as a thread, the command runner has the given daemon
121 * status.
122 * @param cls the class whose main method is to be run
123 * @param args the arguments to pass to the executable
124 * @param daemon the daemon status of this {@link java.lang.Thread Thread}
125 */
126 public CommandRunner(Class cls, String[] args, boolean daemon) {
127 this(null, cls, args, daemon);
128 }
129
130 /**
131 * Create a command runner with the given parent window, running a class' main() method with
132 * the given arguments. If used as a thread, the command runner has daemon status (it terminates if
133 * it is the only one thread running).
134 * @param parent the parent window for the CommandRunner monitor
135 * @param cls the class whose main method is to be run
136 * @param args the arguments to pass to the executable
137 */
138 public CommandRunner(Component parent, Class cls, String[] args) {
139 this(parent, cls, args, true);
140 }
141
142 /**
143 * Create a command runner with no parent window, running a class' main() method with
144 * the given arguments. If used as a thread, the command runner has daemon status (it terminates if
145 * it is the only one thread running).
146 * @param cls the class whose main method is to be run
147 * @param args the arguments to pass to the executable
148 */
149 public CommandRunner(Class cls, String[] args) {
150 this(cls, args, true);
151 }
152
153 /**
154 * Create a command runner with no parent window, running a system command with
155 * the given arguments. If used as a thread, the command runner has the given daemon
156 * status.
157 * @param command the executable to run
158 * @param args the arguments to pass to the executable
159 * @param daemon the daemon status of this {@link java.lang.Thread Thread}
160 */
161 public CommandRunner(String command, String[] args, boolean daemon) {
162 this(null, command, args, daemon);
163 }
164
165 /**
166 * Create a command runner with given parent window, running a system command with
167 * the given arguments. If used as a thread, the command runner has daemon status (it terminates if
168 * it is the only one thread running).
169 * @param parent the parent window for the CommandRunner monitor
170 * @param command the executable to run
171 * @param args the arguments to pass to the executable
172 */
173 public CommandRunner(Component parent, String command, String[] args) {
174 this(parent, command, args, true);
175 }
176
177 /**
178 * Create a command runner with no parent window, running a system command with
179 * the given arguments. If used as a thread, the command runner has daemon status (it terminates if
180 * it is the only one thread running).
181 * @param parent the parent window for the CommandRunner monitor
182 * @param command the executable to run
183 * @param args the arguments to pass to the executable
184 */
185 public CommandRunner(String command, String[] args) {
186 this(command, args, true);
187 }
188
189 /**
190 * Add a listener to the listener set. On {@link #run() run()}, each listener is notifyed
191 * of events concerning the run via its {@link CommandRunner.Listener CommandRunner.Listener}
192 * interface, in arbitrary order.
193 * @param listener the {@link CommandRunner.Listener CommandRunner.Listener} object to add.
194 */
195 public void addListener(CommandRunner.Listener listener) {
196 listeners.add(listener);
197 }
198
199 /**
200 * Remove a listener to the listener set. If the given listener is not in the listeners set,
201 * this method does nothing.
202 * @param listener the {@link CommandRunner.Listener CommandRunner.Listener} object to remove.
203 */
204 public void removeListener(CommandRunner.Listener listener) {
205 listeners.remove(listener);
206 }
207
208 private String [] unquote(String [] s) {
209 for(int i=0;i<s.length;i++)
210 s[i]=unquote(s[i]);
211 return s;
212 }
213
214 private String unquote(String s) {
215 if (s==null) return null;
216 if (s.length()<2) return s;
217 if ((s.charAt(0)=='\'' && s.charAt(s.length()-1)=='\'') ||
218 (s.charAt(0)=='\"' && s.charAt(s.length()-1)=='\"') )
219 return s.substring(1,s.length()-1);
220 return s;
221 }
222
223 private static final int STARTING=0;
224 private static final int FAILED=1;
225 private static final int STARTED=2;
226 private static final int TERMINATED=3;
227
228 private void notify(int eventCode) {
229 notify(eventCode, null);
230 }
231
232 private void notify(int eventCode, Object arg) {
233 for(Iterator i = listeners.iterator(); i.hasNext(); ) {
234 Listener listener = (Listener)i.next();
235 switch(eventCode) {
236 case STARTING:
237 listener.starting();
238 break;
239 case FAILED:
240 listener.failed((Exception)arg);
241 break;
242 case STARTED:
243 listener.started();
244 break;
245 case TERMINATED:
246 Object [] v = (Object[])arg;
247 listener.terminated(((Integer)v[0]).intValue(), ((Long)v[1]).longValue());
248 break;
249 }
250 }
251 }
252
253 /**
254 * Run the command. Depending on the various flag, command output goes to a window
255 * or standard output, or both. Each listener registered via {@link #addListener(classUtils.pack.util.CommandRunner.Listener)
256 * addListener} is notifyed of events concerning the run via its {@link CommandRunner.Listener CommandRunner.Listener}
257 * interface, in arbitrary order.
258 */
259 public synchronized void run() {
260 // Open a window
261 String s;
262 if (command!=null) s = command + " " + argsList(args);
263 else s=cls + " " + argsList(args);
264 closeWindow();
265 JPanelPrintStream jpos = new JPanelPrintStream();
266 JLabel label=new JLabel(" Running "+s);
267 if (openWindowOnStartup) {
268 jf = new JFrame("Running \"" + command + "\"");
269 jf.getContentPane().setLayout(new BorderLayout());
270 jf.getContentPane().add(label, BorderLayout.NORTH);
271 jf.getContentPane().add(jpos.getPanel(), BorderLayout.CENTER);
272 jf.pack();
273 jf.setSize(600,400);
274 if (parent == null) {
275 Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
276 jf.setLocation(
277 (screenDim.width - jf.getSize().width) / 2,
278 (screenDim.height - jf.getSize().height) / 2);
279 } else {
280 jf.setLocation(
281 parent.getLocation().x + 30,
282 parent.getLocation().y + 30);
283 }
284 jf.setVisible(true);
285 }
286
287 if (command!=null) executeCommand(s, jpos, label);
288 else executeClass(s, jpos, label);
289
290
291 if (closeWindowOnTermination) closeWindow();
292 }
293
294 private void executeClass(String classString, JPanelPrintStream jpos, JLabel label) {
295 PrintStream stdout=System.out;
296 try {
297 Method ms = cls.getMethod("main",new Class[] { String[].class } );
298
299 if (openWindowOnStartup)
300 System.setOut(jpos);
301 notify(STARTING);
302 long startTime=System.currentTimeMillis(),elapsed=0;
303 ms.invoke(null, new Object[] { args });
304 elapsed=System.currentTimeMillis()-startTime;
305 notify(STARTED);
306 Object [] arg= new Object[2];
307 arg[0]=new Integer(0);
308 arg[1]=new Long(elapsed);
309 if (openWindowOnStartup) {
310 System.setOut(stdout);
311 jpos.flush();
312 }
313 notify(TERMINATED, arg);
314
315 if (outputElapsedTime) {
316 String ti = new TimeInterval(elapsed).toString();
317 if (this.openWindowOnStartup) {
318 jf.setTitle("Run \""+cls+"\" in "+ti);
319 label.setText(" "+cls+" - terminated");
320 jpos.flush();
321 }
322 if (this.outputOnSystemOut) System.out.println("Elapsed "+ti);
323 } else {
324 if (this.openWindowOnStartup) {
325 jf.setTitle(" \""+cls+"\" terminated");
326 label.setText(" "+cls+" - terminated");
327 jpos.flush();
328 }
329 }
330
331
332 } catch (Exception e) {
333 notify(FAILED, e);
334 if (this.openWindowOnStartup) {
335 ByteArrayOutputStream bs = new ByteArrayOutputStream();
336 PrintStream ps = new PrintStream(bs);
337 ps.println("Exception occurred");
338 ps.println();
339 e.printStackTrace(ps);
340 jpos.println(bs.toString());
341 jpos.flush();
342 JOptionPane.showMessageDialog(jf, bs.toString(), "Exception Occurred", JOptionPane.ERROR_MESSAGE);
343 jf.setTitle(" \""+classString+"\" failed");
344 label.setText(" "+classString+" - failed");
345 }
346 if (this.outputOnSystemOut) e.printStackTrace();
347 }
348 System.setOut(stdout);
349 }
350
351 private void executeCommand(String cmdString, JPanelPrintStream jpos, JLabel label) {
352
353 String [] cmdLine = new String[args.length+1];
354 cmdLine[0]=command;
355 System.arraycopy(args, 0, cmdLine, 1, args.length);
356
357 for(int i=0;i<cmdLine.length;i++) {
358 System.out.print(cmdLine[i]);
359 System.out.print(" ");
360 }
361 System.out.println();
362
363
364 try {
365
366 notify(STARTING);
367 long startTime=System.currentTimeMillis(),elapsed=0;
368
369 Process p = Runtime.getRuntime().exec(cmdLine);
370 notify(STARTED);
371 InputStream is = p.getInputStream();
372 int c;
373 boolean cont;
374 do {
375 cont=false;
376 while((c=is.read())!=-1) {
377 if (openWindowOnStartup) jpos.write(c);
378 if (outputOnSystemOut) System.out.write(c);
379 }
380 elapsed=System.currentTimeMillis()-startTime;
381 try {
382 int v = p.exitValue();
383 jpos.flush();
384 Object [] arg= new Object[2];
385 arg[0]=new Integer(v);
386 arg[1]=new Long(elapsed);
387 notify(TERMINATED, arg);
388 p.destroy();
389 if (captureOutput) lastOutput=jpos.getContents();
390 } catch(IllegalThreadStateException e) {
391 cont=true;
392 }
393 } while(cont);
394 if (outputElapsedTime) {
395 String ti = new TimeInterval(elapsed).toString();
396 if (this.openWindowOnStartup) {
397 jf.setTitle("Run \""+command+"\" in "+ti);
398 label.setText(" "+cmdString+" - terminated");
399 }
400 if (this.outputOnSystemOut) System.out.println("Elapsed "+ti);
401 } else {
402 if (this.openWindowOnStartup) {
403 jf.setTitle(" \""+cmdString+"\" terminated");
404 label.setText(" "+cmdString+" - terminated");
405 }
406 }
407 } catch (IOException e) {
408 notify(FAILED, e);
409 if (this.openWindowOnStartup) {
410 ByteArrayOutputStream bs = new ByteArrayOutputStream();
411 PrintStream ps = new PrintStream(bs);
412 ps.println("Exception occurred");
413 ps.println();
414 e.printStackTrace(ps);
415 JOptionPane.showMessageDialog(jf, bs.toString(), "Exception Occurred", JOptionPane.ERROR_MESSAGE);
416 jf.setTitle(" \""+command+"\" failed");
417 label.setText(" "+cmdString+" - failed");
418 }
419 if (this.outputOnSystemOut) e.printStackTrace();
420 }
421 }
422
423 private void closeWindow() {
424 if (jf==null) return;
425 jf.setVisible(false);
426 jf.dispose();
427 jf=null;
428 lastOutput=null;
429 }
430
431 private String argsList(String[] args) {
432 StringBuffer sb = new StringBuffer();
433 for (int i = 0; i < args.length; i++) {
434 sb.append(args[i]);
435 if (i < args.length - 1)
436 sb.append(" ");
437 }
438 return sb.toString();
439 }
440
441 /**
442 * Returns the closeWindowOnTermination.
443 * @return boolean
444 */
445 public boolean isCloseWindowOnTermination() {
446 return closeWindowOnTermination;
447 }
448
449 /**
450 * Returns the openWindowOnStartup.
451 * @return boolean
452 */
453 public boolean isOpenWindowOnStartup() {
454 return openWindowOnStartup;
455 }
456
457 /**
458 * Returns the outputOnSystemOut.
459 * @return boolean
460 */
461 public boolean isOutputOnSystemOut() {
462 return outputOnSystemOut;
463 }
464
465 /**
466 * Sets the closeWindowOnTermination.
467 * @param closeWindowOnTermination The closeWindowOnTermination to set
468 */
469 public synchronized void setCloseWindowOnTermination(boolean closeWindowOnTermination) {
470 this.closeWindowOnTermination = closeWindowOnTermination;
471 }
472
473 /**
474 * Sets the openWindowOnStartup.
475 * @param openWindowOnStartup The openWindowOnStartup to set
476 */
477 public synchronized void setOpenWindowOnStartup(boolean openWindowOnStartup) {
478 this.openWindowOnStartup = openWindowOnStartup;
479 }
480
481 /**
482 * Set the output-on-system-out property. If <b>true</b>, the runner will (also)
483 * send the execution output to standard output.
484 * @param outputOnSystemOut The outputOnSystemOut to set
485 */
486 public synchronized void setOutputOnSystemOut(boolean outputOnSystemOut) {
487 this.outputOnSystemOut = outputOnSystemOut;
488 }
489
490 /**
491 * Return <b>true</b> if the runner is set to capture and store the output of the program run.
492 * @see #getLastOutput()
493 * @return boolean
494 */
495 public boolean isCaptureOutput() {
496 return captureOutput;
497 }
498
499 /**
500 * Returns the output of the last execution, or <b>null</b> if either no execution has
501 * occurred yet, or {@link #isCaptureOutput() isCaptureOutput()} is <b>false</b>.
502 * @return String
503 */
504 public String getLastOutput() {
505 return lastOutput;
506 }
507
508 /**
509 * Set the capture-output property. If <b>true</b> the output of the last run of the command
510 * is stored in memory, and can be retrieved with {@link #getLastOutput() getLastOutput()}.
511 * @param captureOutput The value to set.
512 */
513 public void setCaptureOutput(boolean captureOutput) {
514 this.captureOutput = captureOutput;
515 }
516
517 /**
518 * Returns the outputElapsedTime.
519 * @return boolean
520 */
521 public boolean isOutputElapsedTime() {
522 return outputElapsedTime;
523 }
524
525 /**
526 * Sets the outputElapsedTime.
527 * @param outputElapsedTime The outputElapsedTime to set
528 */
529 public void setOutputElapsedTime(boolean outputElapsedTime) {
530 this.outputElapsedTime = outputElapsedTime;
531 }
532
533
534 /*
535 * Test method
536 */
537 public static void main(String[] args) {
538 new Thread() {
539 public void run() {
540 CommandRunner cmd =
541 new CommandRunner(ListMapIterator.class, new String[] { "-X" }, false);
542 //new CommandRunner("java", new String[0], false);
543 //cmd.setOpenWindowOnStartup(false);
544 //cmd.setOutputOnSystemOut(true);
545 //cmd.addListener(new CommandRunner.StdOutAdapter());
546 cmd.start();
547 while(true) {
548 System.out.println("busy");
549 Thread.yield();
550 }
551 }
552 }.start();
553 }
554
555
556 }
557