001: /*
002: * Convenience methods for executing non-Java processes.
003: * Copyright (C) 2005 Stephen Ostermiller
004: * http://ostermiller.org/contact.pl?regarding=Java+Utilities
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * See COPYING.TXT for details.
017: */
018:
019: package com.Ostermiller.util;
020:
021: import java.io.*;
022:
023: /**
024: * Convenience methods for executing non-Java processes.
025: * More information about this class is available from <a target="_top" href=
026: * "http://ostermiller.org/utils/ExecHelper.html">ostermiller.org</a>.
027: *
028: * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
029: * @since ostermillerutils 1.06.00
030: */
031: public final class ExecHelper {
032:
033: /**
034: * Executes the specified command and arguments in a separate process, and waits for the
035: * process to finish.
036: * <p>
037: * Output from the process is expected to be text in the system's default character set.
038: * <p>
039: * No input is passed to the process on STDIN.
040: *
041: * @param cmdarray array containing the command to call and its arguments.
042: * @return The results of the execution in an ExecHelper object.
043: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
044: * @throws IOException - if an I/O error occurs
045: * @throws NullPointerException - if cmdarray is null
046: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
047: *
048: * @since ostermillerutils 1.06.00
049: */
050: public static ExecHelper exec(String[] cmdarray) throws IOException {
051: return new ExecHelper(Runtime.getRuntime().exec(cmdarray), null);
052: }
053:
054: /**
055: * Executes the specified command and arguments in a separate process, and waits for the
056: * process to finish.
057: * <p>
058: * Output from the process is expected to be text in the system's default character set.
059: * <p>
060: * No input is passed to the process on STDIN.
061: *
062: * @param cmdarray array containing the command to call and its arguments.
063: * @param envp array of strings, each element of which has environment variable settings in format name=value.
064: * @return The results of the execution in an ExecHelper object.
065: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
066: * @throws IOException - if an I/O error occurs
067: * @throws NullPointerException - if cmdarray is null
068: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
069: *
070: * @since ostermillerutils 1.06.00
071: */
072: public static ExecHelper exec(String[] cmdarray, String[] envp)
073: throws IOException {
074: return new ExecHelper(
075: Runtime.getRuntime().exec(cmdarray, envp), null);
076: }
077:
078: /**
079: * Executes the specified command and arguments in a separate process, and waits for the
080: * process to finish.
081: * <p>
082: * Output from the process is expected to be text in the system's default character set.
083: * <p>
084: * No input is passed to the process on STDIN.
085: *
086: * @param cmdarray array containing the command to call and its arguments.
087: * @param envp array of strings, each element of which has environment variable settings in format name=value.
088: * @param dir the working directory of the subprocess, or null if the subprocess should inherit the working directory of the current process.
089: * @return The results of the execution in an ExecHelper object.
090: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
091: * @throws IOException - if an I/O error occurs
092: * @throws NullPointerException - if cmdarray is null
093: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
094: *
095: * @since ostermillerutils 1.06.00
096: */
097: public static ExecHelper exec(String[] cmdarray, String[] envp,
098: File dir) throws IOException {
099: return new ExecHelper(Runtime.getRuntime().exec(cmdarray, envp,
100: dir), null);
101: }
102:
103: /**
104: * Executes the specified command and arguments in a separate process, and waits for the
105: * process to finish.
106: * <p>
107: * No input is passed to the process on STDIN.
108: *
109: * @param cmdarray array containing the command to call and its arguments.
110: * @param charset Output from the executed command is expected to be in this character set.
111: * @return The results of the execution in an ExecHelper object.
112: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
113: * @throws IOException - if an I/O error occurs
114: * @throws NullPointerException - if cmdarray is null
115: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
116: *
117: * @since ostermillerutils 1.06.00
118: */
119: public static ExecHelper exec(String[] cmdarray, String charset)
120: throws IOException {
121: return new ExecHelper(Runtime.getRuntime().exec(cmdarray),
122: charset);
123: }
124:
125: /**
126: * Executes the specified command and arguments in a separate process, and waits for the
127: * process to finish.
128: * <p>
129: * No input is passed to the process on STDIN.
130: *
131: * @param cmdarray array containing the command to call and its arguments.
132: * @param envp array of strings, each element of which has environment variable settings in format name=value.
133: * @param charset Output from the executed command is expected to be in this character set.
134: * @return The results of the execution in an ExecHelper object.
135: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
136: * @throws IOException - if an I/O error occurs
137: * @throws NullPointerException - if cmdarray is null
138: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
139: *
140: * @since ostermillerutils 1.06.00
141: */
142: public static ExecHelper exec(String[] cmdarray, String[] envp,
143: String charset) throws IOException {
144: return new ExecHelper(
145: Runtime.getRuntime().exec(cmdarray, envp), charset);
146: }
147:
148: /**
149: * Executes the specified command and arguments in a separate process, and waits for the
150: * process to finish.
151: * <p>
152: * No input is passed to the process on STDIN.
153: *
154: * @param cmdarray array containing the command to call and its arguments.
155: * @param envp array of strings, each element of which has environment variable settings in format name=value.
156: * @param dir the working directory of the subprocess, or null if the subprocess should inherit the working directory of the current process.
157: * @param charset Output from the executed command is expected to be in this character set.
158: * @return The results of the execution in an ExecHelper object.
159: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
160: * @throws IOException - if an I/O error occurs
161: * @throws NullPointerException - if cmdarray is null
162: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
163: *
164: * @since ostermillerutils 1.06.00
165: */
166: public static ExecHelper exec(String[] cmdarray, String[] envp,
167: File dir, String charset) throws IOException {
168: return new ExecHelper(Runtime.getRuntime().exec(cmdarray, envp,
169: dir), charset);
170: }
171:
172: /**
173: * Executes the specified command using a shell. On windows uses cmd.exe or command.exe.
174: * On other platforms it uses /bin/sh.
175: * <p>
176: * A shell should be used to execute commands when features such as file redirection, pipes,
177: * argument parsing are desired.
178: * <p>
179: * Output from the process is expected to be text in the system's default character set.
180: * <p>
181: * No input is passed to the process on STDIN.
182: *
183: * @param command String containing a command to be parsed by the shell and executed.
184: * @return The results of the execution in an ExecHelper object.
185: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
186: * @throws IOException - if an I/O error occurs
187: * @throws NullPointerException - if command is null
188: *
189: * @since ostermillerutils 1.06.00
190: */
191: public static ExecHelper execUsingShell(String command)
192: throws IOException {
193: return execUsingShell(command, null);
194: }
195:
196: /**
197: * Executes the specified command using a shell. On windows uses cmd.exe or command.exe.
198: * On other platforms it uses /bin/sh.
199: * <p>
200: * A shell should be used to execute commands when features such as file redirection, pipes,
201: * argument parsing are desired.
202: * <p>
203: * No input is passed to the process on STDIN.
204: *
205: * @param command String containing a command to be parsed by the shell and executed.
206: * @param charset Output from the executed command is expected to be in this character set.
207: * @return The results of the execution in an ExecHelper object.
208: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
209: * @throws IOException - if an I/O error occurs
210: * @throws NullPointerException - if command is null
211: *
212: * @since ostermillerutils 1.06.00
213: */
214: public static ExecHelper execUsingShell(String command,
215: String charset) throws IOException {
216: if (command == null)
217: throw new NullPointerException();
218: String[] cmdarray;
219: String os = System.getProperty("os.name");
220: if (os.equals("Windows 95") || os.equals("Windows 98")
221: || os.equals("Windows ME")) {
222: cmdarray = new String[] { "command.exe", "/C", command };
223: } else if (os.startsWith("Windows")) {
224: cmdarray = new String[] { "cmd.exe", "/C", command };
225: } else {
226: cmdarray = new String[] { "/bin/sh", "-c", command };
227: }
228: return new ExecHelper(Runtime.getRuntime().exec(cmdarray),
229: charset);
230: }
231:
232: /**
233: * Take a process, record its standard error and standard out streams, wait for it to finish
234: *
235: * @param process process to watch
236: * @throws SecurityException if a security manager exists and its checkExec method doesn't allow creation of a subprocess.
237: * @throws IOException - if an I/O error occurs
238: * @throws NullPointerException - if cmdarray is null
239: * @throws IndexOutOfBoundsException - if cmdarray is an empty array (has length 0).
240: *
241: * @since ostermillerutils 1.06.00
242: */
243: private ExecHelper(Process process, String charset)
244: throws IOException {
245: StringBuffer output = new StringBuffer();
246: StringBuffer error = new StringBuffer();
247:
248: Reader stdout;
249: Reader stderr;
250:
251: if (charset == null) {
252: // This is one time that the system charset is appropriate,
253: // don't specify a character set.
254: stdout = new InputStreamReader(process.getInputStream());
255: stderr = new InputStreamReader(process.getErrorStream());
256: } else {
257: stdout = new InputStreamReader(process.getInputStream(),
258: charset);
259: stderr = new InputStreamReader(process.getErrorStream(),
260: charset);
261: }
262: char[] buffer = new char[1024];
263:
264: boolean done = false;
265: boolean stdoutclosed = false;
266: boolean stderrclosed = false;
267: while (!done) {
268: boolean readSomething = false;
269: // read from the process's standard output
270: if (!stdoutclosed && stdout.ready()) {
271: readSomething = true;
272: int read = stdout.read(buffer, 0, buffer.length);
273: if (read < 0) {
274: readSomething = true;
275: stdoutclosed = true;
276: } else if (read > 0) {
277: readSomething = true;
278: output.append(buffer, 0, read);
279: }
280: }
281: // read from the process's standard error
282: if (!stderrclosed && stderr.ready()) {
283: int read = stderr.read(buffer, 0, buffer.length);
284: if (read < 0) {
285: readSomething = true;
286: stderrclosed = true;
287: } else if (read > 0) {
288: readSomething = true;
289: error.append(buffer, 0, read);
290: }
291: }
292: // Check the exit status only we haven't read anything,
293: // if something has been read, the process is obviously not dead yet.
294: if (!readSomething) {
295: try {
296: this .status = process.exitValue();
297: done = true;
298: } catch (IllegalThreadStateException itx) {
299: // Exit status not ready yet.
300: // Give the process a little breathing room.
301: try {
302: Thread.sleep(100);
303: } catch (InterruptedException ix) {
304: process.destroy();
305: throw new IOException(
306: "Interrupted - processes killed");
307: }
308: }
309: }
310: }
311:
312: this .output = output.toString();
313: this .error = error.toString();
314: }
315:
316: /**
317: * The output of the job that ran.
318: *
319: * @since ostermillerutils 1.06.00
320: */
321: private String output;
322:
323: /**
324: * Get the output of the job that ran.
325: *
326: * @return Everything the executed process wrote to its standard output as a String.
327: *
328: * @since ostermillerutils 1.06.00
329: */
330: public String getOutput() {
331: return output;
332: }
333:
334: /**
335: * The error output of the job that ran.
336: *
337: * @since ostermillerutils 1.06.00
338: */
339: private String error;
340:
341: /**
342: * Get the error output of the job that ran.
343: *
344: * @return Everything the executed process wrote to its standard error as a String.
345: *
346: * @since ostermillerutils 1.06.00
347: */
348: public String getError() {
349: return error;
350: }
351:
352: /**
353: * The status of the job that ran.
354: *
355: * @since ostermillerutils 1.06.00
356: */
357: private int status;
358:
359: /**
360: * Get the status of the job that ran.
361: *
362: * @return exit status of the executed process, by convention, the value 0 indicates normal termination.
363: *
364: * @since ostermillerutils 1.06.00
365: */
366: public int getStatus() {
367: return status;
368: }
369: }
|