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 }