001    // Copyright 2000-2004 Crispin Perdue <cris@perdues.com>
002    // 
003    // This is free software, and comes with ABSOLUTELY NO WARRANTY.
004    // You may distribute it under the terms of the Library GNU Public License
005    // Version 2.
006    
007    package com.perdues;
008    
009    import java.io.*;
010    import java.util.logging.*;
011    import java.util.regex.*;
012    
013    /**
014      Convenient class to run a native command-line program from Java and
015      receive its output and exit status. The simplest method returns the
016      program's standard output to you as a String and throws an exception
017      in case of nonzero exit status or error output. This class was
018      inspired by the convenience of the Tcl "exec" function.
019    
020      @author Cris Perdue <cris@perdues.com>
021      */
022    public class ProcessUtils {
023    
024      /**
025        This class is not instantiable.
026        */
027      private ProcessUtils() {}
028    
029    
030      /**
031         Runs a command-line program and returns all of its standard
032         output as a String.  If the process exit status is nonzero or
033         there is any output to System.err, this throws a
034         ProcessUtils.Exception containing the exit status and any output.
035         This method allows the process to write any quantity of character
036         data to its standard output and error output using the system
037         default character encoding.
038         
039         @throws ProcessUtils.Exception in case of nonzero exit status or error
040         output.
041         @return the program's standard output as a String.
042      */
043      public static String exec(final String[] cmd)
044          throws IOException, InterruptedException {
045        StringWriter w = new StringWriter();
046        StringWriter e = new StringWriter();
047        int status = exec(cmd, w, e);
048        if (status!=0 || e.getBuffer().length()>0)
049          throw new Exception(status, w.toString(), e.toString());
050        else
051          return w.toString();
052      }
053    
054    
055      /**
056         Runs a command-line program, passes its standard output and error
057         output to two Writers, and returns its final exit status.
058         Outputs to the Writer arguments using the system default
059         character encoding.  Note that this retrieves the Process output
060         and actually writes it to the Writers you provide.  Contrast this
061         with java.lang.Runtime.exec, which obliges you to write
062         additional (threaded) code to read from the streams it makes
063         available to you.
064         
065         @return the program's exit status.
066      */
067      public static int exec(final String[] cmd, Writer output, Writer errors)
068          throws IOException, InterruptedException {
069        return exec(cmd, null, output, errors);
070      }
071    
072    
073      /**
074         Runs a command-line program, writes all of its standard output
075         and error output to two Writers, returns its final exit status,
076         and makes your Reader available to the program.  Waits for all
077         output and process completion, writes it to the Writer arguments
078         using the system default character encoding, then returns the
079         process exit status.  Contrast this with java.lang.Runtime.exec,
080         which obliges you to write additional (threaded) code to read
081         from and write to the streams it makes available to you.  The
082         Reader argument may be null, and in this case this method
083         supplies no input to the program.
084         <p>
085         This will complete even if the program only reads part of its
086         input, and before returning it interrupts the thread that
087         feeds input to the program.  Reads on some Readers do not
088         unblock on interrupts, so the input thread may live until the
089         last read completes.
090         
091         @return the program's exit status.
092      */
093      public static int exec(final String[] cmd, Reader input,
094                             Writer output, Writer errors)
095          throws IOException, InterruptedException {
096        Process program = Runtime.getRuntime().exec(cmd);
097        CharPump inpump = null;
098        if (input!=null) {
099          inpump = new CharPump
100            (input, new OutputStreamWriter(program.getOutputStream()));
101        }
102        CharPump outpump
103          = new CharPump(new InputStreamReader(program.getInputStream()), output);
104        CharPump errpump
105          = new CharPump(new InputStreamReader(program.getErrorStream()), errors);
106        outpump.start();
107        errpump.start();
108        if (inpump!=null)
109          inpump.start();
110        int status = program.waitFor();
111        outpump.join();
112        errpump.join();
113        if (inpump!=null) {
114          inpump.interrupt();
115          // Do not join here, it is not necessary and might block this thread.
116        }
117        return status;
118      }
119    
120    
121      /**
122         Runs a command-line program, writes all of its standard output
123         and error output to two OutputStreams, and returns its final exit
124         status.  Waits for all output and process completion, writes it
125         to the OutputStreams, then returns the process exit status.
126         Contrast this with java.lang.Runtime.exec, which obliges you to
127         write additional (threaded) code to read from and write to the
128         streams it makes available to you.
129      */
130      public static int exec(final String[] cmd,
131                             OutputStream output, OutputStream errors)
132          throws IOException, InterruptedException {
133        return exec(cmd, null, output, errors);
134      }
135    
136    
137      /**
138         Runs a command-line program, writes all of its standard output
139         and error output to two OutputStreams, returns its final exit
140         status, and makes your InputStream available to the program.
141         Waits for all output and process completion, writes it to the
142         OutputStreams, then returns the process exit status.  Contrast
143         this with java.lang.Runtime.exec, which obliges you to write
144         additional (threaded) code to read from and write to the streams
145         it makes available to you.
146         <p>
147         This will complete even if the program only reads part of its
148         input, and before returning it interrupts the thread that feeds
149         input to the program.  Reads on some InputStreams do not unblock
150         on interrupts, so the input thread may live until the last read
151         completes.
152         
153         @return the program's exit status.
154      */
155      public static int exec(final String[] cmd, InputStream input,
156                             OutputStream output, OutputStream errors)
157          throws IOException, InterruptedException {
158        Process program = Runtime.getRuntime().exec(cmd);
159        BytePump inpump = null;
160        OutputStream toProg = null;
161        if (input!=null) {
162          toProg = program.getOutputStream();
163          inpump = new BytePump(input, toProg);
164          inpump.setCloseOnExit(true);
165        }
166        BytePump outpump = new BytePump(program.getInputStream(), output);
167        BytePump errpump = new BytePump(program.getErrorStream(), errors);
168        outpump.start();
169        errpump.start();
170        if (input!=null)
171          inpump.start();
172        logger.fine("waiting");
173        int status = program.waitFor();
174        logger.fine("done");
175        outpump.join();
176        logger.fine("out joined");
177        errpump.join();
178        logger.fine("err joined");
179        if (input!=null) {
180          toProg.close();
181          inpump.interrupt();
182          // We do not join with the input thread.  The join is not necessary
183          // and could block indefinitely.
184        }
185        return status;
186      }
187    
188    
189      /**
190        This Exception class reports error exit status, all standard output
191        and error output from an external program.  It is thrown in case of
192        nonzero exit status from some of the <tt>exec</tt> methods.
193        */
194      public static class Exception extends RuntimeException {
195    
196        public Exception(int status, String stdout, String stderr) {
197          super();
198          this.status = status;
199          this.stdout = stdout;
200          this.stderr = stderr;
201        }
202    
203        /**
204          Returns the process exit status of the process as reported by
205          Process.waitFor.
206          */
207        public int getStatus() {
208          return status;
209        }
210    
211        /**
212          Returns all output sent to the process's standard output, an empty
213          string if there was none.
214          */
215        public String getStdout() {
216          return stdout;
217        }
218    
219        /**
220          Returns all output sent to the process's error output, an empty
221          string if there was none.
222          */
223        public String getStderr() {
224          return stderr;
225        }
226    
227    
228        public String getMessage() {
229          String newline = System.getProperty("line.separator");
230          
231          Matcher ematch = 
232            Pattern.compile("(^.*$((\\s*?)^.*$)?)(\\s)?^", Pattern.MULTILINE)
233              .matcher(stderr);
234          ematch.lookingAt();
235          String dots = ematch.group(4)==null ? "" : " ...";
236          // (pos<0) ? stderr : stderr.substring(0, pos)+"... ";
237          return "status="+status+", stdout["+stdout.length()
238            +"], stderr["+stderr.length()+"]"+newline+ematch.group(1)+dots;
239        }
240    
241        private String stdout;
242        private String stderr;
243        private int status;
244    
245      }
246      
247      //// PRIVATE DATA ////
248      
249      private static Logger logger =
250        Logger.getLogger(ProcessUtils.class.getName());
251    
252    }