001: package org.antmod.util;
002:
003: import java.io.*;
004: import java.util.ArrayList;
005: import java.util.Iterator;
006: import java.util.logging.Level;
007: import java.util.logging.Logger;
008:
009: /**
010: * Launches a process, redirecting the output of that sub-process
011: * to the output of this (the parent) process.
012: *
013: * @author Klaas Waslander
014: */
015: public final class ProcessLauncher {
016: /** the logger for this class */
017: private final static Logger LOGGER = Logger
018: .getLogger(ProcessLauncher.class.getName());
019:
020: private String commandLine;
021: private String[] commandArray;
022: private File baseDir;
023:
024: private ArrayList listeners = new ArrayList(1);
025:
026: private Process subProcess;
027: private boolean finished = false;
028:
029: StringBuffer out = new StringBuffer();
030: StringBuffer err = new StringBuffer();
031:
032: /**
033: * Constructs new process launcher with the given command line.
034: */
035: public ProcessLauncher(String commandLine) {
036: this (commandLine, null);
037: }
038:
039: public ProcessLauncher(String commandLine, File baseDir) {
040: this .commandLine = commandLine;
041: this .baseDir = baseDir;
042: }
043:
044: /**
045: * Constructs new process launcher with the given command array.
046: */
047: public ProcessLauncher(String[] commandArray) {
048: this (commandArray, null);
049: }
050:
051: public ProcessLauncher(String[] commandArray, File baseDir) {
052: this .commandArray = commandArray;
053: this .baseDir = baseDir;
054: }
055:
056: /**
057: * Constructs new process launcher with the given command element list.
058: */
059: public ProcessLauncher(ArrayList commandList) {
060: this (commandList, null);
061: }
062:
063: public ProcessLauncher(ArrayList commandList, File baseDir) {
064: this (toStringArray(commandList), baseDir);
065: }
066:
067: private static String[] toStringArray(ArrayList list) {
068: String[] result = new String[list.size()];
069: Iterator iter = list.iterator();
070: int arrayIndex = 0;
071: while (iter.hasNext()) {
072: result[arrayIndex++] = iter.next().toString();
073: }
074: return result;
075: }
076:
077: /**
078: * Classes implementing this interface can receive
079: * output generated by processes launched using the ProcessLauncher.
080: */
081: public interface OutputListener {
082: public void standardOutput(char[] output);
083:
084: public void errorOutput(char[] output);
085: }
086:
087: /**
088: * Add a listener for output from the to-be-launched process.
089: */
090: public void addOutputListener(OutputListener listener) {
091: this .listeners.add(listener);
092: }
093:
094: /** fire error output event */
095: private void fireErr(char[] err) {
096: if (this .listeners.isEmpty()) {
097: this .err.append(out);
098: }
099:
100: Iterator iter = this .listeners.iterator();
101: while (iter.hasNext()) {
102: ((OutputListener) iter.next()).errorOutput(err);
103: }
104: }
105:
106: /** fire standard output event */
107: private void fireOut(char[] out) {
108: if (this .listeners.isEmpty()) {
109: this .out.append(out);
110: }
111:
112: Iterator iter = this .listeners.iterator();
113: while (iter.hasNext()) {
114: ((OutputListener) iter.next()).standardOutput(out);
115: }
116: }
117:
118: /**
119: * Get standard output, in case no listeners were registered - never returns null.
120: */
121: public String getStandardOutput() {
122: if (!this .listeners.isEmpty()) {
123: throw new IllegalStateException(
124: "Cannot get standard output, because outputlisteners have been registered.");
125: }
126: return this .out.toString();
127: }
128:
129: /**
130: * Get error output, in case no listeners were registered - never returns null.
131: */
132: public String getErrorOutput() {
133: if (!this .listeners.isEmpty()) {
134: throw new IllegalStateException(
135: "Cannot get error output, because outputlisteners have been registered.");
136: }
137: return this .err.toString();
138: }
139:
140: /**
141: * Get the commandline that is used to launch the process.
142: */
143: public String getCommandLine() {
144: String usedCommand = this .commandLine;
145: if (this .commandLine == null && this .commandArray != null) {
146: usedCommand = "";
147: for (int i = 0; i < this .commandArray.length; i++) {
148: if (i > 0) {
149: usedCommand += " ";
150: }
151: usedCommand += this .commandArray[i];
152: }
153: }
154: return usedCommand;
155: }
156:
157: /**
158: * Check whether execution has finished.
159: */
160: public boolean hasFinished() {
161: return finished;
162: }
163:
164: /**
165: * Launches the process, and blocks until that process completes execution.
166: * @throws CommandNotExistsException If the command could not be executed because it does not exist
167: */
168: public int launch() throws CommandNotExistsException {
169: this .err.setLength(0);
170: this .out.setLength(0);
171:
172: BackgroundPrinter stdout = null;
173: BackgroundPrinter stderr = null;
174: try {
175: if (this .commandArray != null) {
176: this .subProcess = Runtime.getRuntime().exec(
177: this .commandArray, null, this .baseDir);
178: } else {
179: this .subProcess = Runtime.getRuntime().exec(
180: this .commandLine, null, this .baseDir);
181: }
182:
183: stdout = new BackgroundPrinter(subProcess.getInputStream(),
184: false);
185: stderr = new BackgroundPrinter(subProcess.getErrorStream(),
186: true);
187: stdout.start();
188: stderr.start();
189:
190: // kill process and wait max 10 seconds for output to complete
191: int exitValue = this .subProcess.waitFor();
192: stdout.join(10000);
193: stderr.join(10000);
194:
195: /*
196: if (exitValue != 0) {
197: LOGGER.fine("WARNING: exit value " + exitValue + " for command \"" + getCommandLine() + "\"");
198: }
199: */
200:
201: return exitValue;
202: } catch (IOException ioe) {
203: // usually caused if the command does not exist at all
204: throw new CommandNotExistsException(
205: "Command probably does not exist: " + ioe);
206: } catch (Exception e) {
207: LOGGER.log(Level.SEVERE,
208: "Exception while running/launching \""
209: + getCommandLine() + "\".", e);
210: } finally {
211: if (this .subProcess != null) {
212: this .subProcess.destroy();
213: this .subProcess = null;
214: }
215: if (stdout != null) {
216: stdout.close();
217: }
218: if (stderr != null) {
219: stderr.close();
220: }
221: this .finished = true;
222: }
223: return -1;
224: }
225:
226: /**
227: * Tries to abort the currently running process.
228: */
229: public void abort() {
230: if (this .subProcess != null) {
231: this .subProcess.destroy();
232: this .subProcess = null;
233: }
234: }
235:
236: /**
237: * Catches output from a "java.lang.Process" and writes it
238: * to either System.err or System.out.
239: *
240: * @author Klaas Waslander - Sun Java Center
241: */
242: private class BackgroundPrinter extends Thread {
243: private InputStream in;
244: boolean isErrorOutput;
245:
246: public BackgroundPrinter(InputStream in, boolean isErrorOutput) {
247: this .in = in;
248: this .isErrorOutput = isErrorOutput;
249: }
250:
251: public void run() {
252: try {
253: BufferedReader reader = new BufferedReader(
254: new InputStreamReader(this .in));
255:
256: // read buffer
257: char[] buf = new char[1024];
258:
259: // write data to target, until no more data is left to read
260: int numberOfReadBytes;
261: while ((numberOfReadBytes = reader.read(buf)) != -1) {
262: char[] clearedbuf = new char[numberOfReadBytes];
263: System.arraycopy(buf, 0, clearedbuf, 0,
264: numberOfReadBytes);
265:
266: if (this .isErrorOutput) {
267: fireErr(clearedbuf);
268: } else {
269: fireOut(clearedbuf);
270: }
271: }
272: /* } catch (IOException ioe) {
273: // ignore this: process has ended, causing IOException
274: } catch (NullPointerException ioe) {
275: // ignore this: there was no resulting output
276: */
277: } catch (Exception e) {
278: LOGGER
279: .log(
280: Level.FINE,
281: "Exception while reading from stream from subprocess.",
282: e);
283: }
284: }
285:
286: public void close() {
287: try {
288: this .in.close();
289: } catch (Exception e) {
290: LOGGER
291: .log(
292: Level.WARNING,
293: "Closing background stream for launched process caused exception.",
294: e);
295: }
296: }
297: }
298:
299: /**
300: * Exception that is thrown when a command could not be executed because it (probably) does not exist at all.
301: *
302: * @author Klaas Waslander
303: */
304: public static class CommandNotExistsException extends
305: RuntimeException {
306: /**
307: * Construct a new exception for a command that does not exist.
308: * @param msg The message for this exception.
309: */
310: public CommandNotExistsException(String msg) {
311: super(msg);
312: }
313: }
314: }
|