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}