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}