|
ExecRunner |
|
1 package com.mccrory.scott.base;
2
3 ////////////////////////////////////////////////////////////////////////////////
4 // Copyright (C) 2002 Scott McCrory
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 ////////////////////////////////////////////////////////////////////////////////
20
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.io.ObjectOutputStream;
24 import java.io.OutputStream;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.util.Date;
28 import java.util.StringTokenizer;
29
30 /**
31 * <P>Makes running external executables easier, optionally under a watched thread.
32 *
33 * In addition, probably the most useful feature of ExecRunner is using it to
34 * run a command-line program and obtain its stdout and stderr results in two
35 * strings. This is done with exec(String) - see that method for an example.
36 *
37 * With acknowledgements to Michael C. Daconta, author of "Java Pitfalls,
38 * Time Saving Solutions, and Workarounds to Improve Programs." and his
39 * article in JavaWorld "When Runtime.exec() Won't".</P>
40 *
41 * @author <a href="mailto:smccrory@users.sourceforge.net">Scott McCrory</a>.
42 * @version CVS $Id: ExecRunner.java,v 1.20 2002/08/04 22:04:53 smccrory Exp $
43 */
44 public class ExecRunner {
45
46 /** Win NT/2K/MEPro require cmd.exe to run programs **/
47 private static final String WINDOWS_NT_2000_COMMAND_1 = "cmd.exe";
48
49 /** Win NT/2K/MEPro require the /C to specify what to run **/
50 private static final String WINDOWS_NT_2000_COMMAND_2 = "/C";
51
52 /** Win 9X/MEHome require cmd.exe to run programs **/
53 private static final String WINDOWS_9X_ME_COMMAND_1 = "command.exe";
54
55 /** Win 9X/MEHome require the /C to specify what to run **/
56 private static final String WINDOWS_9X_ME_COMMAND_2 = "/C";
57
58 /** String to send to STDERR if program exceeds max run time **/
59 private static final String MAX_RUN_TIME_EXCEEDED_STRING =
60 "MAX_RUN_TIME_EXCEEDED";
61
62 /** String to capture STDOUT **/
63 private String out = new String();
64
65 /** String to capture STDERR **/
66 private String err = new String();
67
68 /** Default max run time (in seconds) **/
69 private int maxRunTimeSecs = 0;
70
71 /** Flag to indicate if we've exceeded max run time **/
72 private boolean maxRunTimeExceeded = false;
73
74 /** The name of this class to log context without introspection **/
75 private static final String CLASS_NAME = "ExecRunner";
76
77 /** The version of this class (filled in by CVS) **/
78 private static final String VERSION = "CVS $Revision: 1.20 $";
79
80 /**
81 * Basic ExecRunner constructor.
82 *
83 */
84 public ExecRunner() {
85 super();
86 }
87
88 /**
89 * ExecRunner constructor which also conveniently runs exec(String).
90 *
91 * @param command The program or command to run
92 * @throws ExceptionInInitializerError thrown if a problem occurs
93 */
94 public ExecRunner(String command) throws ExceptionInInitializerError {
95 this();
96 try {
97 exec(command);
98 }
99 catch (IOException ioe) {
100 throw new ExceptionInInitializerError(ioe.getMessage());
101 }
102 catch (InterruptedException inte) {
103 throw new ExceptionInInitializerError(inte.getMessage());
104 }
105
106 }
107
108 /**
109 * We override the <code>clone</code> method here to prevent cloning of our class.
110 *
111 * @throws CloneNotSupportedException To indicate cloning is not allowed
112 * @return Nothing ever really returned since we throw a CloneNotSupportedException
113 **/
114 public final Object clone() throws CloneNotSupportedException {
115
116 throw new java.lang.CloneNotSupportedException();
117
118 }
119
120 /**
121 * The <B>exec(String)</B> method runs a process inside of a watched thread.
122 * It returns the client's exit code and feeds its STDOUT and STDERR to
123 * ExecRunner's out and err strings, where you then use getOutString()
124 * and getErrString() to obtain these values. Example:
125 *
126 * <pre>
127 * // Execute the program and grab the results
128 * String out = "";
129 * String err = "";
130 * try {
131 *
132 * ExecRunner er = new ExecRunner();
133 * er.setMaxRunTimeSecs(maxRunTime);
134 * er.exec(program);
135 * if (!er.getMaxRunTimeExceeded()) {
136 * out = er.getOutString();
137 * err = er.getErrString();
138 * }
139 * else {
140 * log.error("Maximum run time exceeded!");
141 * continue;
142 * }
143 *
144 * }
145 * catch (Exception e) {
146 * log.error("Error executing " + program + ": " + e.getMessage());
147 * continue;
148 * }
149 * </pre>
150 *
151 * @return The command's return code
152 * @param command The program or command to run
153 * @throws IOException thrown if a problem occurs
154 * @throws InterruptedException thrown if a problem occurs
155 */
156 public int exec(String command) throws IOException, InterruptedException {
157
158 StringWriter swOut = new StringWriter();
159 PrintWriter pwOut = new PrintWriter(swOut, true);
160
161 StringWriter swErr = new StringWriter();
162 PrintWriter pwErr = new PrintWriter(swErr, true);
163
164 int rc = exec(command, pwOut, pwErr);
165
166 out = swOut.toString();
167 err = swErr.toString();
168
169 return rc;
170
171 }
172
173 /**
174 * Convenience method for calling exec with OutputStreams.
175 *
176 * @return The command's return code
177 * @param command The program or command to run
178 * @param stdoutStream java.io.OutputStream
179 * @param stderrStream java.io.OutputStream
180 * @throws IOException thrown if a problem occurs
181 * @throws InterruptedException thrown if a problem occurs
182 **/
183 public int exec(
184 String command,
185 OutputStream stdoutStream,
186 OutputStream stderrStream)
187 throws IOException, InterruptedException {
188
189 PrintWriter pwOut = new PrintWriter(stdoutStream, true);
190 PrintWriter pwErr = new PrintWriter(stderrStream, true);
191
192 return exec(command, pwOut, pwErr);
193
194 }
195
196 /**
197 * The <code>exec(String, PrintWriter, PrintWriter)</code> method runs
198 * a process inside of a watched thread. It returns the client's exit code
199 * and feeds its STDOUT and STDERR to the passed-in streams.
200 *
201 * @return The command's return code
202 * @param command The program or command to run
203 * @param stdoutWriter java.io.PrintWriter
204 * @param stderrWriter java.io.PrintWriter
205 * @throws IOException thrown if a problem occurs
206 * @throws InterruptedException thrown if a problem occurs
207 **/
208 public int exec(
209 String command,
210 PrintWriter stdoutWriter,
211 PrintWriter stderrWriter)
212 throws IOException, InterruptedException {
213
214 // Default exit value is non-zero to indicate a problem.
215 int exitVal = 1;
216
217 ////////////////////////////////////////////////////////////////
218 Runtime rt = Runtime.getRuntime();
219 Process proc;
220 String[] cmd = null;
221
222 // First get the start time & calculate comparison numbers
223 Date startTime = new Date();
224 long startTimeMs = startTime.getTime();
225 long maxTimeMs = startTimeMs + (maxRunTimeSecs * 1000);
226
227 ////////////////////////////////////////////////////////////////
228 // First determine the OS to build the right command string
229 String osName = System.getProperty("os.name");
230 if (osName.equals("Windows NT") || osName.equals("Windows 2000")) {
231 cmd = new String[3];
232 cmd[0] = WINDOWS_NT_2000_COMMAND_1;
233 cmd[1] = WINDOWS_NT_2000_COMMAND_2;
234 cmd[2] = command;
235 }
236 else if (
237 osName.equals("Windows 95")
238 || osName.equals("Windows 98")
239 || osName.equals("Windows ME")) {
240 cmd = new String[3];
241 cmd[0] = WINDOWS_9X_ME_COMMAND_1;
242 cmd[1] = WINDOWS_9X_ME_COMMAND_2;
243 cmd[2] = command;
244 }
245 else {
246 // Linux (and probably other *nixes) prefers to be called
247 // with each argument supplied separately, so we first
248 // Tokenize it across spaces as the boundary.
249 StringTokenizer st = new StringTokenizer(command, " ");
250 cmd = new String[st.countTokens()];
251 int token = 0;
252 while (st.hasMoreTokens()) {
253 String tokenString = st.nextToken();
254 //System.out.println(tokenString);
255 cmd[token++] = tokenString;
256 }
257 }
258
259 // Execute the command and start the two output gobblers
260 if (cmd != null && cmd.length > 0) {
261 //System.out.println("**Checkpoint** :" + cmd.length);
262 proc = rt.exec(cmd);
263 }
264 else {
265 throw new IOException("Insufficient commands!");
266 }
267
268 StreamGobbler outputGobbler =
269 new StreamGobbler(proc.getInputStream(), stdoutWriter);
270 StreamGobbler errorGobbler =
271 new StreamGobbler(proc.getErrorStream(), stderrWriter);
272 outputGobbler.start();
273 errorGobbler.start();
274
275 // Wait for the program to finish running and return the
276 // exit value obtained from the executable
277 while (true) {
278
279 try {
280 exitVal = proc.exitValue();
281 break;
282 }
283 catch (IllegalThreadStateException e) {
284
285 // If we get this exception, then the process isn't
286 // done executing and we determine if our time is up.
287 if (maxRunTimeSecs > 0) {
288
289 Date endTime = new Date();
290 long endTimeMs = endTime.getTime();
291 if (endTimeMs > maxTimeMs) {
292 // Time's up - kill the process and the gobblers and return
293 proc.destroy();
294 maxRunTimeExceeded = true;
295 stderrWriter.println(MAX_RUN_TIME_EXCEEDED_STRING);
296 outputGobbler.quit();
297 errorGobbler.quit();
298 return exitVal;
299
300 }
301 else {
302 // Time is not up yet so wait 100 ms before testing again
303 Thread.sleep(100);
304 }
305 }
306
307 }
308
309 }
310
311 ////////////////////////////////////////////////////////////////
312 // Wait for output gobblers to finish forwarding the output
313 while (outputGobbler.isAlive() || errorGobbler.isAlive()) {
314 }
315
316 ////////////////////////////////////////////////////////////////
317 // All done, flush the streams and return the exit value
318 stdoutWriter.flush();
319 stderrWriter.flush();
320 return exitVal;
321
322 }
323
324 /**
325 * Returns the error string if exec(String) was invoked.
326 *
327 * @return The error string if exec(String) was invoked.
328 */
329 public String getErrString() {
330 return err;
331 }
332
333 /**
334 * Returns whether the maximum runtime was exceeded or not.
335 *
336 * @return boolean indicating whether the maximum runtime was exceeded or not.
337 */
338 public boolean getMaxRunTimeExceeded() {
339 return maxRunTimeExceeded;
340 }
341
342 /**
343 * Returns the maximum run time in seconds for this object.
344 *
345 * @return the maximum run time in seconds for this object.
346 */
347 public int getMaxRunTimeSecs() {
348 return maxRunTimeSecs;
349 }
350
351 /**
352 * Returns the output string if exec(String) was invoked.
353 *
354 * @return The output string if exec(String) was invoked.
355 */
356 public String getOutString() {
357 return out;
358 }
359
360 /**
361 * This is for unit testing of the class.
362 *
363 * @param args an array of command-line arguments
364 * @throws IOException thrown if a problem occurs
365 **/
366 public static void main(String[] args) throws IOException {
367
368 try {
369
370 ExecRunner er = new ExecRunner();
371
372 /////////////////////////////////////////////////////////////////////
373 // Linux: Test the exec operation with just STDOUT and STDERR
374 //System.out.println("Testing ExecRunner with STDOUT and STDERR...");
375 //er.exec("ls -l", System.out, System.err);
376 //System.out.println("Complete");
377
378 /////////////////////////////////////////////////////////////////////
379 // Windows: Test the exec operation with just STDOUT and STDERR
380 System.out.println("Testing ExecRunner with StringWriter...");
381
382 er = new ExecRunner();
383 er.setMaxRunTimeSecs(1);
384 er.exec("dir /s c:\\");
385 //er.exec("ls -l");
386
387 System.out.println("<STDOUT>\n" + er.getOutString() + "</STDOUT>");
388 System.out.println("<STDERR>\n" + er.getErrString() + "</STDERR>");
389 System.out.println("Testing Done");
390
391 /////////////////////////////////////////////////////////////////////
392 // Exit nicely
393 System.exit(0);
394
395 }
396 catch (Exception e) {
397
398 e.printStackTrace();
399 System.exit(1);
400
401 }
402 }
403
404 /**
405 * We override the <code>readObject</code> method here to prevent
406 * deserialization of our class for security reasons.
407 *
408 * @param in java.io.ObjectInputStream
409 * @throws IOException thrown if a problem occurs
410 **/
411 private final void readObject(ObjectInputStream in) throws IOException {
412
413 throw new IOException("Object cannot be deserialized");
414
415 }
416
417 /**
418 * Sets the maximum run time in seconds.
419 * If you do not want to limit the executable's run time, simply pass in
420 * a 0 (which is also the default).
421 *
422 * @param max Maximim number of seconds to let program run
423 */
424 public void setMaxRunTimeSecs(int max) {
425
426 maxRunTimeSecs = max;
427
428 }
429
430 /**
431 * We override the <code>writeObject</code> method here to prevent
432 * serialization of our class for security reasons.
433 *
434 * @param out java.io.ObjectOutputStream
435 * @throws IOException thrown if a problem occurs
436 **/
437 private final void writeObject(ObjectOutputStream out) throws IOException {
438
439 throw new IOException("Object cannot be serialized");
440
441 }
442
443 }
|
ExecRunner |
|