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 }
|