/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