ProcessManager.java
001 package gate.util;
002 
003 import java.io.*;
004 import gate.util.GateRuntimeException;
005 
006 /**
007  * Class that supports running an external process and either silently
008  * consuming its standard output and error streams, or copying them to Java's
009  * stdout and stderr.
010  *
011  * This implementation reads the output and error streams in separate threads,
012  * but tries to reuse these threads from one external call to the next, unlike
013  * other approaches I've seen (which all spawn a pair of new threads for every
014  * external call).  As a result, instances of this class are <b>not thread
015  * safe</b>.  You must use a different instance of ProcessManager in each
016  * thread that you use to run external processes.
017  */
018 public class ProcessManager {
019   /**
020    * Debug flag.
021    */
022   private static final boolean DEBUG = false;
023 
024   /**
025    * StreamGobbler thread for standard output.
026    */
027   private StreamGobbler stdout;
028 
029   /**
030    * StreamGobbler thread for standard error.
031    */
032   private StreamGobbler stderr;
033 
034   /**
035    * Construct a ProcessManager object and start the gobbler threads.
036    */
037   public ProcessManager() {
038     stdout = new StreamGobbler();
039     Thread t = new Thread(stdout);
040     t.setDaemon(true);
041     t.start();
042 
043     stderr = new StreamGobbler();
044     t = new Thread(stderr);
045     t.setDaemon(true);
046     t.start();
047   }
048 
049   /**
050    * Run the given external process.  If an exception results from starting the
051    * process, or while reading the output from the process, it will be thrown.
052    * Otherwise, the exit value from the process is returned.
053    *
054    @param argv the process command line, suitable for passing to
055    <code>Runtime.exec</code>.
056    @param dumpOutput should we copy the process output and error streams to
057    * the Java output and error streams or just consume them silently?
058    */
059   public synchronized int runProcess(String[] argv, boolean dumpOutput)
060                           throws IOException {
061     return runProcess(argv, (dumpOutput ? System.out : null)(dumpOutput ? System.err : null));
062   }
063   
064   /**
065    * Run the given external process.  If an exception results from starting the
066    * process, or while reading the output from the process, it will be thrown.
067    * Otherwise, the exit value from the process is returned.
068    *
069    @param argv the process command line, suitable for passing to
070    <code>Runtime.exec</code>.
071    @param dumpOutput should we copy the process output and error streams to
072    * the Java output and error streams or just consume them silently?
073    */
074   public synchronized int runProcess(String[] argv, OutputStream out, OutputStream err)
075                           throws IOException {
076     // Start the process.  This may throw an exception
077     if(DEBUG) {
078       System.err.println("Starting process");
079     }
080     Process proc = Runtime.getRuntime().exec(argv);
081     
082     // set up the stream gobblers for stdout and stderr
083     if(DEBUG) {
084       System.err.println("Configuring gobblers");
085     }
086     stdout.setInputStream(proc.getInputStream());
087     stdout.setOutputStream(out);
088 
089     stderr.setInputStream(proc.getErrorStream());
090     stderr.setOutputStream(err);
091 
092     // start the gobblers
093     if(DEBUG) {
094       System.err.println("Waking up gobblers");
095     }
096     this.notifyAll();
097 
098     // wait for the gobblers to finish their jobs
099     while(!stderr.isDone() || !stdout.isDone()) {
100       try {
101         if(DEBUG) {
102           System.err.println("Gobblers not done, waiting...");
103         }
104         this.wait();
105       }
106       catch(InterruptedException ie) {
107         // if interrupted, try waiting again
108       }
109     }
110 
111     // get the return code from the process
112     Integer returnCode = null;
113     while(returnCode == null) {
114       try {
115         returnCode = new Integer(proc.waitFor());
116       }
117       catch(InterruptedException ie) {
118         // if interrupted, just try waiting again
119       }
120     }
121     
122     // reset the gobblers
123     if(DEBUG) {
124       System.err.println("Gobblers done - resetting");
125     }
126     stdout.reset();
127     stderr.reset();
128     
129     // if there was an exception during running, throw that
130     Exception ex = null;
131     if(stdout.hasException()) {
132       ex = stdout.getException();
133       stderr.getException()// to reset exception cache
134     }
135     else if(stderr.hasException()) {
136       ex = stderr.getException();
137     }
138 
139     if(ex != null) {
140       if(DEBUG) {
141         System.err.println("Rethrowing exception");
142       }
143       if(ex instanceof IOException) {
144         throw (IOException)ex;
145       }
146       else if(ex instanceof RuntimeException) {
147         throw (RuntimeException)ex;
148       }
149       else throw new GateRuntimeException(ex);
150     }
151     // otherwise return the exit code
152     else {
153       return returnCode.intValue();
154     }
155   }
156 
157   /**
158    * Thread body that takes a stream and either consumes it silently or echoes
159    * it to another stream.
160    */
161   private class StreamGobbler implements Runnable {
162     /**
163      * The input stream to gobble.  If this is null, the thread is idle.
164      */
165     private InputStream inputStream = null;
166 
167     /**
168      * The output stream to echo to.  If this is null the gobbler silently
169      * discards the input stream contents.
170      */
171     private OutputStream outputStream = null;;
172 
173     /**
174      * Buffer used for reading and writing.
175      */
176     private byte[] buf = new byte[4096];
177 
178     /**
179      * Are we finished?  This is set to true once the stream has been emptied.
180      */
181     private boolean done = false;
182 
183     /**
184      * If an exception is thrown during gobbling, it is stored here.
185      */
186     private Exception exception = null;
187 
188     /**
189      * Set the stream to gobble.  This should not be called while the thread is
190      * active.
191      */
192     void setInputStream(InputStream is) {
193       inputStream = is;
194     }
195 
196     /**
197      * Set the output stream to redirect output to.  A value of
198      <code>null</code> indicates that we should discard the output without
199      * echoing it.
200      */
201     void setOutputStream(OutputStream os) {
202       outputStream = os;
203     }
204 
205     /**
206      * Has an exception been thrown since {@link #getException()} was last
207      * called?
208      */
209     boolean hasException() {
210       return (exception != null);
211     }
212 
213     /**
214      * Return the last exception thrown.  This also resets the cached exception
215      * to <code>null</code>.
216      */
217     Exception getException() {
218       Exception ex = exception;
219       exception = null;
220       return ex;
221     }
222 
223     boolean isDone() {
224       return done;
225     }
226 
227     /**
228      * Reset state.
229      */
230     void reset() {
231       done = false;
232       exception = null;
233       inputStream = null;
234     }
235 
236     /**
237      * Main body of the thread.  Waits until we have been given a stream to
238      * gobble, then reads it until there is no more input available.
239      */
240     public void run() {
241       if(DEBUG) {
242         System.err.println("StreamGobbler starting");
243       }
244       // wait until we have a stream to gobble
245       synchronized(ProcessManager.this) {
246         while(inputStream == null) {
247           try {
248             if(DEBUG) {
249               System.err.println("Waiting for stream...");
250             }
251             ProcessManager.this.wait();
252           }
253           catch(InterruptedException ie) {
254           }
255         }
256       }
257 
258       while(true) {
259         // read the stream until end of file or an exception is thrown.
260         BufferedInputStream bis = new BufferedInputStream(inputStream);
261         int bytesRead = -1;
262         try {
263           if(DEBUG) {
264             System.err.println("Gobbling stream");
265           }
266           while((bytesRead = bis.read(buf)) != -1) {
267             // echo to outputStream if necessary
268             if(outputStream != null) {
269               outputStream.write(buf, 0, bytesRead);
270             }
271           }
272         }
273         catch(Exception ex) {
274           // any exception is stored to be retrieved by the ProcessManager
275           exception = ex;
276           if(DEBUG) {
277             System.err.println("Exception thrown");
278           }
279         }
280         
281         try {
282           bis.close();
283         }
284         catch(IOException ioe) {
285           // oh well, it's not the end of the world
286         }
287 
288         done = true;
289         inputStream = null;
290         outputStream = null;
291 
292         // wake up ProcessManager to say we've finished, and start waiting for
293         // the next round.
294         synchronized(ProcessManager.this) {
295           if(DEBUG) {
296             System.err.println("Waking process manager");
297           }
298           ProcessManager.this.notifyAll();
299           while(inputStream == null) {
300             try {
301               if(DEBUG) {
302                 System.err.println("Waiting for stream (2)");
303               }
304               ProcessManager.this.wait();
305             }
306             catch(InterruptedException ie) {
307             }
308           }
309         }
310       }
311     }
312   }
313 }