001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022/* 023 * ExternalProcess.java 024 */ 025package org.biojava.utils.process; 026 027import java.io.File; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.io.StringReader; 032import java.io.StringWriter; 033import java.util.Enumeration; 034import java.util.Properties; 035 036import org.biojava.utils.SimpleThreadPool; 037import org.biojava.utils.ThreadPool; 038 039/** 040 * Utility class to execute an external process and to handle 041 * the <code>STDOUT</code>, <code>STDERR</code> and <code>STDIN</code> streams 042 * in multiple threads managed by a thread pool. 043 * <p>This class is intended for applications that call an external program many 044 * times, e.g. in a loop, and that need high performance throughput, i.e. 045 * the program's input and output should not be written to disk. The Java 046 * {@link java.lang.Runtime#exec} methods requires the application to read/write 047 * the external program's input and output streams in multiple threads. 048 * Otherwise the calling application may block. However, instantiating multiple 049 * threads for each call is extensive. On Linux systems there is also the 050 * problem that each Java thread is represented by a single process and the 051 * number of processes is limited on Linux. Because the Java garbage collector 052 * does not free the {@link java.lang.Thread} objects properly, an application 053 * might run out of threads (indicated by a {@link java.lang.OutOfMemoryError} 054 * exception) after multiple iterations. Therefore, the 055 * <code>ExternalProcess</code> class uses a 056 * {@linkplain org.biojava.utils.ThreadPool thread pool}.</p> 057 * <p>The simplest way to use this class is by calling the static methods 058 * {@link #execute(String)} and 059 * {@link #execute(String, String, StringWriter, StringWriter)}. However, these 060 * methods are not thread safe and no configuration is possible. In the former 061 * case the program's input, output and error output is redirected to <code>STDIN</code>, 062 * <code>STDOUT</code> and <code>STDERR</code> of the calling program. In the 063 * latter case input is provided as string and output and error output is 064 * written to {@link java.io.StringWriter} objects. The environment, i.e. 065 * the current working directory and the environment variables, are inherited 066 * from the calling process. In both cases, a static thread pool of size 067 * {@link #THREAD_POOL_SIZE} is used. The command that should be executed is 068 * provided as a string argument.</p> 069 * <p>In scenarios where the environment has to be changed, the program input 070 * is generated just in time, or the program's output is parsed just in time, 071 * the use of an explicit instance of the <code>ExternalProcess</code> class 072 * is recommended. This instance could be initialized with a custom thread pool. 073 * Otherwise a {@link org.biojava.utils.SimpleThreadPool} of size 3 is used. 074 * The input and output is managed by multithreaded 075 * {@linkplain org.biojava.utils.process.InputHandler input handler} and 076 * {@linkplain org.biojava.utils.process.OutputHandler output handler} objects. 077 * There are four predefined handlers that read the program's input from a 078 * {@link java.io.Reader} object or a {@link java.io.InputStream} object and 079 * write the program's output to a {@link java.io.Writer} object or a 080 * {@link java.io.OutputStream} object. These classes are called: 081 * {@link org.biojava.utils.process.ReaderInputHandler}, 082 * {@link org.biojava.utils.process.SimpleInputHandler}, 083 * {@link org.biojava.utils.process.WriterOutputHandler} and 084 * {@link org.biojava.utils.process.SimpleOutputHandler}. If no handlers are 085 * specified the input and output is redirected to the standards streams of 086 * the calling process.</p> 087 * <p>Before one of the methods {@link #execute()} or 088 * {@link #execute(Properties)} is called, the {@linkplain #setCommands(String) 089 * commands} property should be set. One may include placeholders of the form 090 * <code>%PARAM%</code> within the commands. If a 091 * {@link java.util.Properties} object is passed to the 092 * {@link #execute(Properties)} method, the placeholders are replaced by the 093 * particular property value. Therefore, the <code>Properties</code> object 094 * must contain a key named <code>PARAM</code> (case doesn't matter). The 095 * environment for calling the external program can be configured using the 096 * properties {@linkplain #setWorkingDirectory(File) workingDirectory} and 097 * {@linkplain #setEnvironmentProperties(String[]) environmentProperties}.</p> 098 * <p>Finally, the {@linkplain #setSleepTime(int) sleepTime} property can be 099 * increased, in case the output handlers are not able to catch the whole 100 * program's output within the given time. The default value is 101 * {@link #SLEEP_TIME} [in milliseconds].</p> 102 * @author <a href="mailto:Martin.Szugat@GMX.net">Martin Szugat</a> 103 * @version $Revision$ 104 * @see java.lang.Process 105 */ 106public final class ExternalProcess { 107 108 /* STATIC FIELDS */ 109 110 /** 111 * Size of the thread pool for the static execute methods. 112 */ 113 public static final int THREAD_POOL_SIZE = 9; 114 115 /** 116 * Number of milliseconds the execute method should pauses after the 117 * external process has finished the execution. 118 */ 119 public static final int SLEEP_TIME = 10; 120 121 /** 122 * Static thread pool for static execute method. 123 */ 124 private static final ThreadPool THREAD_POOL = 125 new SimpleThreadPool(THREAD_POOL_SIZE, true); 126 127 128 /* MAIN METHOD */ 129 130 /** 131 * Runs an external program from the command line. The external process 132 * inherits the environment variables and the current working directory from 133 * the parent process. 134 * @param args the path or the name of the external program and its command 135 * line arguments 136 */ 137 public static void main(/*@non_null@*/ String[] args) { 138 try { 139 System.exit(execute(joinCommands(args))); 140 } catch (Exception e) { 141 e.printStackTrace(); 142 } 143 } 144 145 /* STATIC METHODS */ 146 147 /** 148 * Resolves the given command line by replacing all placeholder of the 149 * format <code>%NAME%</code> with the values from the given properties 150 * for the corresponding keys of the format <code>NAME</code>. 151 * @param commands the given command line 152 * @param variables the placeholders or <code>null</code> if no resolvement 153 * should be performed 154 * @return the new command line 155 * @throws NullPointerException if <code>commands</code> is 156 * <code>null</code>. 157 */ 158 public static String resolveCommands(/*@non_null@*/ String commands, 159 Properties variables) throws NullPointerException { 160 if (commands == null) { 161 throw new NullPointerException("commands is null."); 162 } 163 if (variables != null && !variables.isEmpty()) { 164 Enumeration keys = variables.keys(); 165 while (keys.hasMoreElements()) { 166 String key = (String) keys.nextElement(); 167 String value = variables.getProperty(key); 168 // TODO quotting 169 commands = commands.replaceAll("%" + key + "%", value); 170 } 171 } 172 return commands; 173 } 174 175 /** 176 * Executes an external program. The working directory and the environment 177 * variables are inherited from the parent process. The program input is 178 * read from <code>STDIN</code>, the program output is written to 179 * <code>STDOUT</code> and the program error output is written to 180 * <code>STDERR</code>. 181 * <p><b>Note:</b> This method is not thread-safe.</p> 182 * @param commands the command line including the path 183 * or the name of the external program and its command line arguments 184 * @return the exit code from the external program 185 * @throws SecurityException if a security manager exists and its 186 * <code>checkExec</code> method doesn't allow creation of a subprocess. 187 * @throws IOException if an I/O error occurs. 188 * @throws NullPointerException if <code>commands</code> is 189 * <code>null</code>. 190 * @throws IllegalArgumentException if <code>commandList</code> is empty. 191 * @throws InterruptedException if the current thread is 192 * {@link Thread#interrupt() interrupted} by another thread 193 * while it is waiting, then the wait is ended and an 194 * <code>InterruptedException</code> is thrown. 195 */ 196 public static int execute(/*@non_null@*/ String commands) 197 throws IOException, InterruptedException, NullPointerException, 198 SecurityException, IllegalArgumentException { 199 return execute(commands, null, null, null); 200 } 201 202 /** 203 * Executes an external program. The working directory and the environment 204 * variables are inherited from the parent process. 205 * <p><b>Note:</b> This method is not thread-safe.</p> 206 * @param commands the command line including the path 207 * or the name of the external program and its command line arguments 208 * @param inputString the input for the external programm or 209 * <code>null</code> if the input should be read from <code>STDIN</code> 210 * @param outputString the output of the external programm or 211 * <code>null</code> if the output should be written to <code>STDOUT</code> 212 * @param errorString the error output of the external program or 213 * <code>null</code> if the error output should be written to 214 * <code>STDERR</code> 215 * @return the exit code from the external program 216 * @throws SecurityException if a security manager exists and its 217 * <code>checkExec</code> method doesn't allow creation of a subprocess. 218 * @throws IOException if an I/O error occurs. 219 * @throws NullPointerException if <code>commandList</code> is 220 * <code>null</code>. 221 * @throws IllegalArgumentException if <code>commandList</code> is empty. 222 * @throws InterruptedException if the current thread is 223 * {@link Thread#interrupt() interrupted} by another thread 224 * while it is waiting, then the wait is ended and an 225 * <code>InterruptedException</code> is thrown. 226 */ 227 public static int execute(/*@non_null@*/ String commands, 228 String inputString, StringWriter outputString, 229 StringWriter errorString) 230 throws IOException, InterruptedException, NullPointerException, 231 SecurityException, IllegalArgumentException { 232 233 ExternalProcess ep = new ExternalProcess(THREAD_POOL); 234 235 ep.setCommands(commands); 236 237 if (inputString != null) { 238 ep.setInputHandler(new ReaderInputHandler( 239 new StringReader(inputString), "STDIN")); 240 } 241 if (outputString != null) { 242 ep.setOutputHandler(new WriterOutputHandler(outputString, 243 "STDOUT")); 244 } 245 if (errorString != null) { 246 ep.setErrorHandler(new WriterOutputHandler(errorString, "STDERR")); 247 } 248 249 return ep.execute(); 250 } 251 252 /** 253 * Joins a command list to a single command string. 254 * @param commandList the list of the command and its arguments 255 * @return the joined command line 256 * @throws NullPointerException if <code>commandList</code> is 257 * <code>null</code>. 258 */ 259 public static String joinCommands(/*@non_null@*/ Object[] commandList) 260 throws NullPointerException { 261 if (commandList == null) { 262 throw new NullPointerException("commandList is null."); 263 } 264 StringBuffer sb = new StringBuffer(); 265 for (int i = 0; i < commandList.length; i++) { 266 // TODO quoting 267 if (i == commandList.length - 1) { 268 sb.append(commandList[i].toString()); 269 } else { 270 sb.append(commandList[i].toString() + " "); 271 } 272 } 273 return sb.toString(); 274 } 275 276 /* PRIVATE FIELDS */ 277 278 /** 279 * The command including the external program and its arguments. 280 */ 281 private String commands = null; 282 283 /** 284 * The working directory of the external program. 285 */ 286 private File workingDirectory = null; 287 288 /** 289 * The list of environment variables for the external program. 290 */ 291 private String[] environmentProperties = null; 292 293 /** 294 * The input handler for STDIN. 295 */ 296 private InputHandler inputHandler = null; 297 298 /** 299 * The output handler for STDOUT. 300 */ 301 private OutputHandler outputHandler = null; 302 303 /** 304 * The output handler for STDERR. 305 */ 306 private OutputHandler errorHandler = null; 307 308 /** 309 * The thread pool for the input and output handlers. 310 */ 311 private ThreadPool threadPool = null; 312 313 /** 314 * Should threads be stopped when finalized? 315 */ 316 private boolean stopThreads = true; 317 318 /** 319 * Number of seconds to wait for completion of stream handlers. 320 */ 321 private int sleepTime = SLEEP_TIME; 322 323 /* PUBLIC CONSTRUCTORS */ 324 325 /** 326 * Initializes the external process. 327 */ 328 public ExternalProcess() { 329 this(null); 330 } 331 332 /** 333 * Initializes the external process. 334 * @param threadPool a thread pool with at least three threads or 335 * <code>null</code> if the default thread pool should be used 336 */ 337 public ExternalProcess(ThreadPool threadPool) { 338 if (threadPool == null) { 339 this.threadPool = new SimpleThreadPool(3, true); 340 stopThreads = true; 341 } else { 342 this.threadPool = threadPool; 343 stopThreads = false; 344 } 345 setEnvironmentProperties(null); 346 setWorkingDirectory(null); 347 setInputHandler(null); 348 setOutputHandler(null); 349 setErrorHandler(null); 350 } 351 352 /* PUBLIC METHODS */ 353 354 /** 355 * Executes the external process and waits for its termination. 356 * @return the exit code from the external process 357 * @throws IllegalArgumentException if the command is empty 358 * @throws SecurityException if a security manager exists and its 359 * <code>checkExec</code> method doesn't allow creation of a subprocess. 360 * @throws IOException if an I/O error occurs. 361 * @throws InterruptedException if the current thread is 362 * {@link Thread#interrupt() interrupted} by another thread 363 * while it is waiting, then the wait is ended and an 364 * <code>InterruptedException</code> is thrown. 365 */ 366 public /*@pure@*/ int execute() throws IOException, InterruptedException, 367 SecurityException, IllegalArgumentException { 368 return execute((Properties) null); 369 } 370 371 /** 372 * Executes the external process and waits for its termination. 373 * @param variables a list of key-value-pairs that should be used to replace 374 * placeholders in the command line. May be <code>null</code>. 375 * @return the exit code from the external process 376 * @throws SecurityException if a security manager exists and its 377 * <code>checkExec</code> method doesn't allow creation of a subprocess. 378 * @throws IllegalArgumentException if the command is empty 379 * @throws IOException if an I/O error occurs. 380 * @throws InterruptedException if the current thread is 381 * {@link Thread#interrupt() interrupted} by another thread 382 * while it is waiting, then the wait is ended and an 383 * <code>InterruptedException</code> is thrown. 384 */ 385 public /*@pure@*/ int execute(Properties variables) 386 throws IOException, InterruptedException, SecurityException, 387 IllegalArgumentException { 388 389 Runtime runtime = Runtime.getRuntime(); 390 String commands = resolveCommands(this.commands, variables); 391 392 Process process = runtime.exec(commands, environmentProperties, 393 workingDirectory); 394 395 OutputStream in = process.getOutputStream(); 396 InputStream out = process.getInputStream(); 397 InputStream err = process.getErrorStream(); 398 399 inputHandler.setOutput(in); 400 outputHandler.setInput(out); 401 errorHandler.setInput(err); 402 403 threadPool.addRequest(inputHandler); 404 threadPool.addRequest(outputHandler); 405 threadPool.addRequest(errorHandler); 406 407 // start input and output handlers 408 Thread.yield(); 409 410 int exitCode = process.waitFor(); 411 412 // give stream handlers time to complete 413 Thread.sleep(sleepTime); 414 415 in.close(); 416 out.close(); 417 err.close(); 418 419 return exitCode; 420 } 421 422 /** 423 * Gets the command line including the path or name of the external program 424 * and its command line arguments. 425 * @return the command line 426 */ 427 public /*@pure non_null@*/ String getCommands() { 428 return commands; 429 } 430 431 /** 432 * Sets the command line including the path or name of the external program 433 * and its command line arguments. 434 * @param commands the command line 435 * @throws NullPointerException if <code>commands</code> is 436 * <code>null</code>. 437 */ 438 public void setCommands(/*@non_null@*/ String commands) 439 throws NullPointerException { 440 if (commands == null) { 441 throw new NullPointerException("commands is null."); 442 } 443 this.commands = commands; 444 } 445 446 /** 447 * Gets environment variables for the external process. 448 * @return a list of strings in the format 449 * <code>name=value</code> or <code>null</code> if the environment variables 450 * should be inherited from the parent process 451 */ 452 public /*@pure@*/ String[] getEnvironmentProperties() { 453 String[] environmentProperties = null; 454 if (this.environmentProperties != null) { 455 environmentProperties = 456 (String[]) this.environmentProperties.clone(); 457 } 458 return environmentProperties; 459 } 460 461 /** 462 * Sets environment variables for the external process. 463 * @param environmentProperties a list of strings in the format 464 * <code>name=value</code> or <code>null</code> if the environment variables 465 * should be inherited from the parent process 466 */ 467 public void setEnvironmentProperties(String[] environmentProperties) { 468 if (environmentProperties == null) { 469 this.environmentProperties = null; 470 } else { 471 this.environmentProperties = 472 (String[]) environmentProperties.clone(); 473 } 474 } 475 476 /** 477 * Gets the output error handler which is responsible for the standard error 478 * output of the external process. 479 * @return the error output handler 480 */ 481 public /*@pure non_null@*/ OutputHandler getErrorHandler() { 482 return errorHandler; 483 } 484 485 /** 486 * Sets the output error handler which is responsible for the standard error 487 * output of the external process. 488 * @param errorHandler the error output handler or <code>null</code> if the 489 * error output should be redirected to <code>STDERR</code> 490 */ 491 public void setErrorHandler(OutputHandler errorHandler) { 492 if (errorHandler == null) { 493 this.errorHandler = new SimpleOutputHandler(System.err, "STDERR"); 494 } else { 495 this.errorHandler = errorHandler; 496 } 497 } 498 499 /** 500 * Gets the input handler which is responsible for the standard input 501 * of the external process. 502 * @return the input handler 503 */ 504 public /*@pure non_null@*/ InputHandler getInputHandler() { 505 return inputHandler; 506 } 507 508 /** 509 * Sets the input handler which is responsible for the standard input 510 * of the external process. 511 * @param inputHandler the input handler or <code>null</code> if the 512 * input should be read from <code>STDIN</code> 513 */ 514 public void setInputHandler(InputHandler inputHandler) { 515 if (inputHandler == null) { 516 this.inputHandler = new SimpleInputHandler(System.in, "STDINS"); 517 } else { 518 this.inputHandler = inputHandler; 519 } 520 } 521 522 /** 523 * Gets the output handler which is responsible for the standard output 524 * of the external process. 525 * @return the output handler 526 */ 527 public /*@pure non_null@*/ OutputHandler getOutputHandler() { 528 return outputHandler; 529 } 530 531 /** 532 * Sets the output handler which is responsible for the standard output 533 * of the external process. 534 * @param outputHandler the output handler or <code>null</code> if the 535 * output should be redirected to <code>STDOUT</code> 536 */ 537 public void setOutputHandler(OutputHandler outputHandler) { 538 if (outputHandler == null) { 539 this.outputHandler = new SimpleOutputHandler(System.out, "STDOUT"); 540 } else { 541 this.outputHandler = outputHandler; 542 } 543 } 544 545 /** 546 * Gets the thread pool which is used for the input and output handlers. 547 * @return a thread pool with at least three threads 548 */ 549 public /*@pure non_null@*/ ThreadPool threadPool() { 550 return threadPool; 551 } 552 553 /** 554 * Gets the working directory for the external process. 555 * @return the working directory or <code>null</code> if it should be 556 * inherited from the parent process 557 */ 558 public /*@pure@*/ File getWorkingDirectory() { 559 return workingDirectory; 560 } 561 562 /** 563 * Sets the working directory for the external process. 564 * @param workingDirectory the working directory or <code>null</code> if it 565 * should be inherited from the parent process 566 */ 567 public void setWorkingDirectory(File workingDirectory) { 568 this.workingDirectory = workingDirectory; 569 } 570 571 /*@ ensures \result >= 0; @*/ 572 /** 573 * Gets the number of milliseconds the {@linkplain #execute(Properties)} 574 * method should pauses after the external process is terminated. This gives 575 * the stream handlers the time to complete their work. 576 * @return time in milliseconds 577 */ 578 public /*@pure@*/ int getSleepTime() { 579 return sleepTime; 580 } 581 582 /*@ requires sleepTime >= 0; @*/ 583 /** 584 * Sets the number of milliseconds the {@linkplain #execute(Properties)} 585 * method should pauses after the external process is terminated. Increase 586 * this value if the output handlers didn't catch the whole program output. 587 * @param sleepTime time in milliseconds 588 * @throws IllegalArgumentException if <code>sleepTime</code> is negative. 589 */ 590 public void setSleepTime(int sleepTime) 591 throws IllegalArgumentException { 592 if (sleepTime < 0.0) { 593 throw new IllegalArgumentException( 594 "sleepTime must be zero or positive."); 595 } 596 this.sleepTime = sleepTime; 597 } 598 599 /* CLASS Object */ 600 601 /** 602 * {@inheritDoc} 603 */ 604 protected void finalize() { 605 if (stopThreads) { 606 threadPool.stopThreads(); 607 } 608 } 609}