310 lines
10 KiB
Java
310 lines
10 KiB
Java
/**
|
|
* The Broad Institute
|
|
* SOFTWARE COPYRIGHT NOTICE AGREEMENT
|
|
* This software and its documentation are copyright 2006 by the
|
|
* Broad Institute/Massachusetts Institute of Technology. All rights are reserved.
|
|
*
|
|
* This software is supplied without any warranty or guaranteed support whatsoever. Neither
|
|
* the Broad Institute nor MIT can be responsible for its use, misuse, or functionality.
|
|
*/
|
|
package edu.mit.broad.dcp;
|
|
|
|
import java.io.*;
|
|
|
|
|
|
/**
|
|
* Utility class to run system commands synchronously and return the output.
|
|
*
|
|
* The interface supports the typical case where you want to return a modest
|
|
* amount of information from the command's standard output or standard error
|
|
* as a string. The caller can override this behavior, however, and provide
|
|
* alternative output destinations if necessary.
|
|
*
|
|
* If setMergeOutput() is true, then this class will attempt to interleave
|
|
* the standard output and standard error streams of the command into one
|
|
* stream (standard output). This may not produce exactly the same results
|
|
* as having the operating system interleave the output, but works well for
|
|
* simple executables that do not heavily intermix stdout and stderr.
|
|
*
|
|
* A typical invocation is:
|
|
* <pre>
|
|
* CommandRunner runner = new CommandRunner();
|
|
* int status = runner.runCommand("ls");
|
|
* if (status == 0) {
|
|
* System.out.print(runner.getStandardOutput());
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @author Bob Handsaker
|
|
*/
|
|
public class CommandRunner {
|
|
|
|
private boolean mMergeOutput = false;
|
|
private Writer mStandardOutputDestination = null;
|
|
private Writer mStandardErrorDestination = null;
|
|
private String mStandardOutputString = null;
|
|
private String mStandardErrorString = null;
|
|
|
|
|
|
/**
|
|
* Default constructor.
|
|
*/
|
|
public CommandRunner() {
|
|
}
|
|
|
|
/**
|
|
* Get the standard output from the last command as a string.
|
|
*
|
|
* If no command has been run or an explicit output destination
|
|
* was set, then this method returns null.
|
|
*/
|
|
public String getStandardOutputString() {
|
|
return mStandardOutputString;
|
|
}
|
|
|
|
/**
|
|
* Get the standard error from the last command as a string.
|
|
*
|
|
* If no command has been run or an explicit output destination
|
|
* was set, then this method returns null.
|
|
*/
|
|
public String getStandardErrorString() {
|
|
return mStandardErrorString;
|
|
}
|
|
|
|
/**
|
|
* If true, the command's standard error stream will be interleaved
|
|
* with the command's standard output stream. The standard error
|
|
* stream destination will not be used.
|
|
*/
|
|
public boolean getMergeOutput() {
|
|
return mMergeOutput;
|
|
}
|
|
|
|
/**
|
|
* If true, the command's standard error stream will be interleaved
|
|
* with the command's standard output stream.
|
|
*/
|
|
public void setMergeOutput(boolean value) {
|
|
mMergeOutput = value;
|
|
}
|
|
|
|
/**
|
|
* The destination for the command's standard output stream.
|
|
* If null, the standard output will be captured in a string.
|
|
*/
|
|
public Writer getStandardOutputDestination() {
|
|
return mStandardOutputDestination;
|
|
}
|
|
|
|
/**
|
|
* The destination for the command's standard output stream.
|
|
* If set to null, the standard output will be captured in a string.
|
|
*/
|
|
public void setStandardOutputDestination(Writer writer) {
|
|
mStandardOutputDestination = writer;
|
|
}
|
|
|
|
/**
|
|
* The destination for the command's standard error stream.
|
|
* If null, the standard error will be captured in a string.
|
|
*/
|
|
public Writer getStandardErrorDestination() {
|
|
return mStandardErrorDestination;
|
|
}
|
|
|
|
/**
|
|
* The destination for the command's standard error stream.
|
|
* If set to null, the standard error will be captured in a string.
|
|
*/
|
|
public void setStandardErrorDestination(Writer writer) {
|
|
mStandardErrorDestination = writer;
|
|
}
|
|
|
|
/**
|
|
* Run a command string as a system command.
|
|
*
|
|
* Returns the exit status of the command.
|
|
*
|
|
* When this method is called, the standard output string
|
|
* and standard error string are updated if no alternative output
|
|
* destinations have been set.
|
|
*
|
|
* This method throws a RuntimeException if running the command fails
|
|
* (for example, if there are not enough system resources to spawn
|
|
* the process).
|
|
*
|
|
* @param commmand The command string to run.
|
|
* @return Command exit status.
|
|
* @throws RuntimeException If command execution fails.
|
|
*/
|
|
public int runCommand(String command)
|
|
throws RuntimeException {
|
|
return runCommand(command.split(" "), null, null);
|
|
}
|
|
|
|
/**
|
|
* Run a command string as a system command.
|
|
*
|
|
* Returns the exit status of the command.
|
|
*
|
|
* When this method is called, the standard output string
|
|
* and standard error string are updated if no alternative output
|
|
* destinations have been set.
|
|
*
|
|
* This method throws a RuntimeException if running the command fails
|
|
* (for example, if there are not enough system resources to spawn
|
|
* the process).
|
|
*
|
|
* @param commmand The command string to run.
|
|
* @param environment The command environment (or null to inherit).
|
|
* @param workingDirectory The working directory (or null to inherit).
|
|
* @return Command exit status.
|
|
* @throws RuntimeException If command execution fails.
|
|
*/
|
|
public int runCommand(String command, String[] environment, File workingDirectory)
|
|
throws RuntimeException {
|
|
return runCommand(command.split(" "), environment, workingDirectory);
|
|
}
|
|
|
|
/**
|
|
* Run a command string as a system command.
|
|
*
|
|
* Returns the exit status of the command.
|
|
*
|
|
* When this method is called, the standard output string
|
|
* and standard error string are updated if no alternative output
|
|
* destinations have been set.
|
|
*
|
|
* This method throws a RuntimeException if running the command fails
|
|
* (for example, if there are not enough system resources to spawn
|
|
* the process).
|
|
*
|
|
* @param commmand The command to run (as a array of arguments).
|
|
* @param environment The command environment (or null to inherit).
|
|
* @param workingDirectory The working directory (or null to inherit).
|
|
* @return Command exit status.
|
|
* @throws RuntimeException If command execution fails.
|
|
*/
|
|
public int runCommand(String[] command, String[] environment, File workingDirectory)
|
|
throws RuntimeException {
|
|
|
|
Writer stdout = mStandardOutputDestination;
|
|
Writer stderr = mStandardErrorDestination;
|
|
if (stdout == null) {
|
|
stdout = new StringWriter();
|
|
}
|
|
if (mMergeOutput) {
|
|
stderr = stdout;
|
|
} else if (stderr == null) {
|
|
stderr = new StringWriter();
|
|
}
|
|
|
|
mStandardOutputString = null;
|
|
mStandardErrorString = null;
|
|
|
|
int commandStatus = 0;
|
|
try {
|
|
Process process =
|
|
Runtime.getRuntime().exec(command, environment, workingDirectory);
|
|
StreamHandler stdoutHandler =
|
|
new StreamHandler(process.getInputStream(), stdout);
|
|
StreamHandler stderrHandler =
|
|
new StreamHandler(process.getErrorStream(), stderr);
|
|
|
|
commandStatus = process.waitFor();
|
|
|
|
// Wait for the streams to drain.
|
|
stdoutHandler.join();
|
|
stderrHandler.join();
|
|
} catch (Exception exc) {
|
|
throw new RuntimeException("Command execution failed: " +
|
|
exc.getMessage(),
|
|
exc);
|
|
}
|
|
|
|
if (mStandardOutputDestination == null) {
|
|
mStandardOutputString = stdout.toString();
|
|
}
|
|
if (mStandardErrorDestination == null && !mMergeOutput) {
|
|
mStandardErrorString = stderr.toString();
|
|
}
|
|
|
|
return commandStatus;
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal class to asynchronously read from the standard output
|
|
* and standard error streams of the command being executed.
|
|
*
|
|
* If you do not handle command output asynchronously, then execution
|
|
* of a command may block in some environments if the program produces
|
|
* too much output. In this case, the call to run the process will
|
|
* never complete.
|
|
*/
|
|
private static class StreamHandler extends Thread {
|
|
|
|
/**
|
|
* Constructor.
|
|
* Create an instance of this class, which is an asynchronous
|
|
* thread that will consume input from the given input stream
|
|
* and send the output to the given output destination.
|
|
*
|
|
* @param input The input stream to read.
|
|
* @param output The output destination.
|
|
*/
|
|
StreamHandler(InputStream input, Writer output) {
|
|
m_input = input;
|
|
m_output = output;
|
|
start();
|
|
}
|
|
|
|
|
|
/**
|
|
* Standard thread run method.
|
|
* Pipe input from the input source to the output destination
|
|
* until there is no more input left.
|
|
*
|
|
* If an IOException occurs, the thread will make sure all
|
|
* available output has been flushed to the destination and
|
|
* then terminate. The IOException is not propagated.
|
|
*/
|
|
public void run() {
|
|
|
|
char[] buffer = new char[4096];
|
|
Reader reader =
|
|
new InputStreamReader(new BufferedInputStream(m_input));
|
|
Writer writer = m_output;
|
|
|
|
try {
|
|
while (true) {
|
|
int count = reader.read(buffer);
|
|
if (count <= 0) {
|
|
break;
|
|
}
|
|
if (writer != null) {
|
|
synchronized (writer) {
|
|
writer.write(buffer, 0, count);
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException ignore) {
|
|
// Ignore IO exceptions
|
|
} finally {
|
|
try {
|
|
reader.close();
|
|
} catch (Exception ignore) {
|
|
}
|
|
try {
|
|
m_output.flush();
|
|
} catch (Exception ignore) {
|
|
}
|
|
}
|
|
}
|
|
|
|
private InputStream m_input;
|
|
private Writer m_output;
|
|
}
|
|
}
|