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 }