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