001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of either the BSD licence or the GNU Lesser General 006 * Public Licence. These should be distributed with the code. 007 * If you do not have copies see: 008 * 009 * http://www.opensource.org/licenses/bsd-license.php 010 * http://www.gnu.org/copyleft/lesser.html 011 * 012 * Copyright for this code is held jointly by the individual 013 * authors. These should be listed in @author doc comments. 014 * 015 * For more information on the BioJava project and its aims, 016 * or to join the biojava-l mailing list, visit the home page 017 * at: 018 * 019 * http://www.biojava.org/ 020 * 021 */ 022 023package org.biojava.utils; 024 025import java.io.File; 026import java.io.IOException; 027import java.io.InputStreamReader; 028import java.io.OutputStreamWriter; 029import java.io.Reader; 030import java.io.StringReader; 031import java.io.Writer; 032import java.util.StringTokenizer; 033 034 035/** 036 * Convenience methods for running external processes. This class 037 * offers wrappers around the <code>java.lang.Process</code> API, 038 * but hides away the details of managing threads and process I/O. 039 * 040 * <h3>Example</h3> 041 * 042 * <pre> 043 * StringWriter out = new StringWriter(); 044 * ProcessTools.exec( 045 * new String[] {"/usr/bin/wc", "-w"}, 046 * "The quick brown fox jumps over the lazy dog", 047 * out, 048 * null 049 * ); 050 * int numWords = Integer.parseInt(out.toString().trim()); 051 * </pre> 052 * 053 * @author Thomas Down 054 * @author Francois Pepin 055 * @since 1.4 056 * @deprecated preferable to use org.biojava.utils.ExecRunner 057 * or the org.biojava.utils.process package. 058 */ 059 060public class ProcessTools { 061 062 /** Win NT/2K/MEPro require cmd.exe to run programs **/ 063 private static final String WINDOWS_NT_2000_COMMAND_1 = "cmd.exe"; 064 065 /** Win NT/2K/MEPro require the /C to specify what to run **/ 066 private static final String WINDOWS_NT_2000_COMMAND_2 = "/C"; 067 068 /** Win 9X/MEHome require cmd.exe to run programs **/ 069 private static final String WINDOWS_9X_ME_COMMAND_1 = "command.exe"; 070 071 /** Win 9X/MEHome require the /C to specify what to run **/ 072 private static final String WINDOWS_9X_ME_COMMAND_2 = "/C"; 073 074 // Dummy constructor since this is just a tools class 075 076 private ProcessTools() { 077 } 078 079 /** 080 * Execute the specified command and wait for it to return. 081 * 082 * @param args the command line to execute. 083 * @param input data to present to the process' standard input, or <code>null</code> if the process does not require input. 084 * @param stdout a <code>Writer</code> which will be filled with data from the process' output stream, or <code>null</code> to ignore output. 085 * @param stderr a <code>Writer</code> which will be filled with data from the process' error stream, or <code>null</code> to ignore output. 086 * @return the process' return code. 087 */ 088 089 public static int exec( 090 String[] args, 091 Reader input, 092 Writer stdout, 093 Writer stderr 094 ) 095 throws IOException 096 { 097 try { 098 return exec(args, null, null, input, stdout, stderr, 0L); 099 } catch (ProcessTimeoutException ex) { 100 throw new Error("Assertion failed: unexpected process timeout"); 101 } 102 } 103 104 /** 105 * Execute the specified command and wait for it to return, or kill 106 * it if the specified timeout expires first. 107 * 108 * @param args the command line to execute. 109 * @param envp environment variables for the child process, or <code>null</code> to inherit the current set. 110 * @param dir working directory for the child process, or <code>null</code> to inherit the current directory. 111 * @param input data to present to the process' standard input, or <code>null</code> if the process does not require input. 112 * @param stdout a <code>Writer</code> which will be filled with data from the process' output stream, or <code>null</code> to ignore output. 113 * @param stderr a <code>Writer</code> which will be filled with data from the process' error stream, or <code>null</code> to ignore output. 114 * @param timeout maximum run-time (in milliseconds) for the child process. A value of 0 indicates no limit. 115 * @return the process' return code. 116 * @throws IOException if an error occurs while starting or communicating with the process 117 * @throws ProcessTimeoutException if the child process was killed because its timeout had expired. 118 */ 119 120 public static int exec( 121 String[] args, 122 String[] envp, 123 File dir, 124 Reader input, 125 Writer stdout, 126 Writer stderr, 127 long timeout 128 ) 129 throws IOException, ProcessTimeoutException 130 { 131 Process proc = Runtime.getRuntime().exec(args, envp, dir); 132 CharPump outPump; 133 134 CharPump inPump, errPump; 135 136 if (input == null) { 137 input = new StringReader(""); 138 } 139 outPump = new PumpReaderToWriter(input, new OutputStreamWriter(proc.getOutputStream())); 140 if (stdout == null) { 141 inPump = new PumpReaderToNull(new InputStreamReader(proc.getInputStream())); 142 } else { 143 inPump = new PumpReaderToWriter(new InputStreamReader(proc.getInputStream()), stdout); 144 } 145 if (stderr == null) { 146 errPump = new PumpReaderToNull(new InputStreamReader(proc.getErrorStream())); 147 } else { 148 errPump = new PumpReaderToWriter(new InputStreamReader(proc.getErrorStream()), stderr); 149 } 150 151 TimeBomb tb = null; 152 if (timeout > 0) { 153 tb = new TimeBomb(proc, timeout); 154 tb.start(); 155 } 156 157 outPump.start(); 158 inPump.start(); 159 errPump.start(); 160 161 int rc; 162 try { 163 rc = proc.waitFor(); 164 165 if (tb != null) { 166 tb.defuse(); 167 } 168 169 outPump.join(); 170 inPump.join(); 171 errPump.join(); 172 } catch (InterruptedException iex) { 173 throw new IOException("Error waiting for process to complete"); 174 } 175 176 if (tb != null && tb.fired()) { 177 // ProcessTimeoutException trumps any IOExceptions, since odd exceptions 178 // may be generated after the process is destroyed. 179 throw new ProcessTimeoutException(rc); 180 } else { 181 checkException(outPump, "Output to child"); 182 checkException(inPump, "Input from child"); 183 checkException(errPump, "Errors from child"); 184 return rc; 185 } 186 } 187 188 189 /** 190 * Execute the specified command and wait for it to return. This is the 191 * simplified version that tries to be nice and make your life easier. If 192 * you know exactly what you want, you might want to use exec(String[],...) 193 * instead. 194 * 195 * @param command the command line to execute. 196 * @param input data to present to the process' standard input, or 197 * <code>null</code> if the process does not require input. 198 * @param stdout a <code>Writer</code> which will be filled with data 199 * from the process' output stream, or <code>null</code> to ignore output. 200 * @param stderr a <code>Writer</code> which will be filled with data 201 * from the process' error stream, or <code>null</code> to ignore output. 202 * @return the process' return code. 203 * @throws IOException if an error occurs while starting or communicating 204 * with the process 205 */ 206 public static int exec( 207 String command, 208 Reader input, 209 Writer stdout, 210 Writer stderr) 211 throws IOException 212 { 213 try { 214 return exec(command, null, null, input, stdout, stderr, 0L); 215 } catch (ProcessTimeoutException ex) { 216 throw new Error("Assertion failed: unexpected process timeout"); 217 } 218 } 219 220 /** 221 * Execute the specified command and wait for it to return. This is the 222 * simplified version that tries to be nice and make your life easier. If 223 * you know exactly what you want, you might want to use exec(String[],...) 224 * instead. 225 * 226 * @param command the command line to execute. 227 * @param input data to present to the process' standard input, or 228 * <code>null</code> if the process does not require input. 229 * @param stdout a <code>Writer</code> which will be filled with data 230 * from the process' output stream, or <code>null</code> to ignore output. 231 * @param stderr a <code>Writer</code> which will be filled with data 232 * from the process' error stream, or <code>null</code> to ignore output. 233 * @param timeout maximum run-time (in milliseconds) for the child process. A value of 0 indicates no limit. 234 * @return the process' return code. 235 * @throws IOException if an error occurs while starting or communicating 236 * with the process 237 * @throws ProcessTimeoutException if the child process was killed because its timeout had expired. 238 */ 239 public static int exec( 240 String command, 241 String[] envp, 242 File dir, 243 Reader input, 244 Writer stdout, 245 Writer stderr, 246 long timeout) 247 throws IOException, ProcessTimeoutException 248 { 249 String[] cmd = null; 250 // First determine the OS to build the right command string 251 String osName = System.getProperty("os.name"); 252 if (osName.equals("Windows NT") || osName.equals("Windows 2000") || 253 osName.equals("Windows XP")) { 254 cmd = new String[3]; 255 cmd[0] = WINDOWS_NT_2000_COMMAND_1; 256 cmd[1] = WINDOWS_NT_2000_COMMAND_2; 257 cmd[2] = command; 258 } 259 else if ( 260 osName.equals("Windows 95") 261 || osName.equals("Windows 98") 262 || osName.equalsIgnoreCase("Windows ME")) { 263 cmd = new String[3]; 264 cmd[0] = WINDOWS_9X_ME_COMMAND_1; 265 cmd[1] = WINDOWS_9X_ME_COMMAND_2; 266 cmd[2] = command; 267 } 268 else { 269 // Linux (and probably other *nixes) prefers to be called 270 // with each argument supplied separately, so we first 271 // Tokenize it across spaces as the boundary. 272 StringTokenizer st = new StringTokenizer(command, " "); 273 cmd = new String[st.countTokens()]; 274 int token = 0; 275 while (st.hasMoreTokens()) { 276 String tokenString = st.nextToken(); 277 //System.out.println(tokenString); 278 cmd[token++] = tokenString; 279 } 280 } 281 return exec(cmd, envp, dir, input,stdout,stderr, timeout); 282 } 283 284 /** 285 * Check the status of a Pump and re-throw any exception which may 286 * have occured during its lifecycle 287 * 288 289 private static void checkException(Pump p, String msg) 290 throws IOException 291 { 292 IOException ioe = p.getException(); 293 if (ioe != null) { 294 throw new IOException("Exception processing " + msg); 295 } 296 }*/ 297 298 private static void checkException(CharPump p, String msg) 299 throws IOException 300 { 301 IOException ioe = p.getException(); 302 if (ioe != null) { 303 throw new IOException("Exception processing " + msg); 304 } 305 } 306 307 /** 308 * Thread which will kill the specified process if it is not defused before 309 * the timeout expires. 310 */ 311 312 private static class TimeBomb extends Thread { 313 private volatile boolean ticking = false; 314 private volatile boolean fired = false; 315 private final long time; 316 private final Process victim; 317 318 public TimeBomb(Process victim, long time) { 319 this.time = time; 320 this.victim = victim; 321 } 322 323 public void run() { 324 synchronized(this) { 325 try { 326 ticking = true; 327 wait(time); 328 } catch (InterruptedException ex) { 329 System.err.println("Timebomb thread was interrupted -- this shouldn't happen"); 330 } 331 } 332 if (ticking) { 333 // System.err.println("TimeBomb activated -- killing child process"); 334 fired = true; 335 victim.destroy(); 336 } 337 } 338 339 public synchronized void defuse() { 340 ticking = false; 341 notifyAll(); 342 } 343 344 public boolean fired() { 345 return fired; 346 } 347 } 348 349 /** 350 * Base class for threads which pump bytes from a source to a sink. Subclasses 351 * must implement sourceData and sinkData. They may also wish to override 352 * shutdownHook, a dummy method which is called when the pump finishes (usually 353 * because the end of the data source has been reached). 354 * 355 356 private static abstract class Pump extends Thread { 357 private IOException err = null; 358 359 /** 360 * Read bytes of data from some data source. 361 * 362 363 protected abstract int sourceData(byte[] buf) throws IOException; 364 365 /** 366 * Write bytes of data to some data sink. 367 * 368 369 protected abstract void sinkData(byte[] buf, int len) throws IOException; 370 371 /** 372 * Perform any required tidying operations when the Pump's job has finished. 373 * 374 375 protected void shutdownHook() throws IOException {}; 376 377 public void run() { 378 try { 379 byte[] buf = new byte[256]; 380 int cnt; 381 do { 382 cnt = sourceData(buf); 383 if (cnt > 0) { 384 sinkData(buf, cnt); 385 } 386 } while (cnt >= 0); 387 shutdownHook(); 388 } catch (IOException e) { 389 this.err = e; 390 } 391 } 392 393 public IOException getException() { 394 return err; 395 } 396 }*/ 397 398 private static abstract class CharPump extends Thread { 399 private IOException err = null; 400 401 /** 402 * Read bytes of data from some data source. 403 */ 404 405 protected abstract int sourceData(char[] buf) throws IOException; 406 407 /** 408 * Write bytes of data to some data sink. 409 */ 410 411 protected abstract void sinkData(char[] buf, int len) throws IOException; 412 413 /** 414 * Perform any required tidying operations when the Pump's job has finished. 415 */ 416 417 protected void shutdownHook() throws IOException {}; 418 419 public void run() { 420 try { 421 char[] buf = new char[256]; 422 int cnt; 423 do { 424 cnt = sourceData(buf); 425 if (cnt > 0) { 426 sinkData(buf, cnt); 427 } 428 } while (cnt >= 0); 429 shutdownHook(); 430 } catch (IOException e) { 431 this.err = e; 432 } 433 } 434 435 public IOException getException() { 436 return err; 437 } 438 } 439 440 441 /* 442 private static final class PumpStreamToStringBuffer extends Pump { 443 private final InputStream is; 444 private final StringBuffer sb; 445 446 public PumpStreamToStringBuffer(InputStream is, StringBuffer sb) { 447 super(); 448 this.is = is; 449 this.sb = sb; 450 } 451 452 protected int sourceData(byte[] buf) 453 throws IOException 454 { 455 return is.read(buf); 456 } 457 458 protected void sinkData(byte[] buf, int len) { 459 sb.append(new String(buf, 0, len)); 460 } 461 } 462 463 private static final class PumpStreamToNull extends Pump { 464 private final InputStream is; 465 466 public PumpStreamToNull(InputStream is) { 467 super(); 468 this.is = is; 469 } 470 471 protected int sourceData(byte[] buf) 472 throws IOException 473 { 474 return is.read(buf); 475 } 476 477 protected void sinkData(byte[] buf, int len) { 478 } 479 } 480 */ 481 private static final class PumpReaderToNull extends CharPump { 482 private final Reader is; 483 484 public PumpReaderToNull(Reader is) { 485 super(); 486 this.is = is; 487 } 488 489 protected int sourceData(char[] buf) 490 throws IOException 491 { 492 return is.read(buf); 493 } 494 495 protected void sinkData(char[] buf, int len) { 496 } 497 } 498 499 500 private static final class PumpReaderToWriter extends CharPump { 501 private final Reader reader; 502 private final Writer writer; 503 504 public PumpReaderToWriter(Reader reader, Writer writer){ 505 this.reader=reader; 506 this.writer=writer; 507 } 508 509 protected int sourceData(char[] buf) 510 throws IOException 511 { 512 return reader.read(buf, 0, buf.length); 513 } 514 515 protected void sinkData(char[] buf, int len) 516 throws IOException 517 { 518 writer.write(buf, 0, len); 519 writer.flush(); 520 } 521 522 protected void shutdownHook() 523 throws IOException 524 { 525 writer.close(); 526 } 527 528 } 529 530 /* 531 private static final class PumpStreamToStream extends Pump { 532 private final InputStream is; 533 private final OutputStream os; 534 535 public PumpStreamToStream(InputStream is, OutputStream os) { 536 this.is = is; 537 this.os = os; 538 } 539 540 protected int sourceData(byte[] buf) 541 throws IOException 542 { 543 return is.read(buf); 544 } 545 546 protected void sinkData(byte[] buf, int len) 547 throws IOException 548 { 549 os.write(buf, 0, len); 550 os.flush(); 551 } 552 553 protected void shutdownHook() 554 throws IOException 555 { 556 os.close(); 557 } 558 }*/ 559}