gw.util.process.ProcessRunner Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 Guidewire Software, Inc.
*/
package gw.util.process;
import gw.util.OSPlatform;
import gw.util.StreamUtil;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ProcessRunner {
private final List _rawCmd = new ArrayList();
private OutputBuffer _buffer = null;
private Integer _exitCode = null;
// properties set by builders:
private boolean _withCMD;
private File _workingDirectory;
private Map _env = new HashMap();
private boolean _echo;
private String _input;
private boolean _bufferStdOut;
private boolean _bufferStdErr;
private List _stdOutHandlers = new ArrayList();
private List _stdErrHandlers = new ArrayList();
private String _charset = "UTF-8";
public static String execWithCharset(String charset, String... command) {
return new ProcessRunner(command)
.withStdOutBuffered()
.withCharset(charset)
.withCMD()
.exec()
.getBuffer();
}
public static String exec(String... command) {
return new ProcessRunner(command)
.withStdOutBuffered()
.withCMD()
.exec()
.getBuffer();
}
public ProcessRunner(String... command) {
this(Arrays.asList(command));
}
public ProcessRunner(List command) {
for (String elt : command) {
_rawCmd.add(elt);
}
}
/**
* Executes the given command as if it had been executed from the command line of the host OS
* (cmd.exe on windows, /bin/sh on *nix). The resulting exit code and the output buffer are
* accessible after this call, with {@link #getExitCode()} and {@link #getBuffer()}, respectively.
*
*
This method blocks on the execution of the command.
*
*
* Example Usages:
*
* var currentDir = new ProcessRunner("dir").exec() // windows
* var currentDir = new ProcessRunner("ls").exec() // *nix
* new ProcessRunner( "rm -rf " + directoryToNuke ).exec()
*
*
*
* @return this object for chaining
*/
public ProcessRunner exec()
{
List command = new ArrayList();
if (OSPlatform.isWindows() && _withCMD) {
command.add("CMD.EXE");
command.add("/C");
}
command.addAll(_rawCmd);
ProcessBuilder pb = new ProcessBuilder(command);
if (_workingDirectory != null) {
pb.directory(_workingDirectory);
}
for (Map.Entry entry : _env.entrySet()) {
if (entry.getValue() != null) {
pb.environment().put(entry.getKey(), entry.getValue());
}
else {
pb.environment().remove(entry.getKey());
}
}
ChainedOutputHandler stdOut = new ChainedOutputHandler();
ChainedOutputHandler stdErr = new ChainedOutputHandler();
if (_bufferStdOut || _bufferStdErr) {
_buffer = new OutputBuffer();
if (_bufferStdOut) {
stdOut.add(_buffer);
}
if (_bufferStdErr) {
stdErr.add(_buffer);
}
}
EchoOutputEmitter echo = null;
if (_echo) {
echo = new EchoOutputEmitter(getRawCmdStr(), System.out, System.err);
stdOut.add(echo.getStdOutHandler());
stdErr.add(echo.getStdErrHandler());
}
for (OutputHandler handler : _stdOutHandlers) {
stdOut.add(handler);
}
for (OutputHandler handler : _stdErrHandlers) {
stdErr.add(handler);
}
try {
Process process = pb.start();
feedInput(process, _input, _charset);
if (echo != null) {
echo.processStarted();
}
_exitCode = nomNomNom(process, stdOut.maybeReduce(), stdErr.maybeReduce(), _charset);
if (echo != null) {
echo.processFinished();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
StreamUtil.close(stdOut, stdErr);
} catch (IOException e) {
e.printStackTrace();
}
}
return this;
}
private static void feedInput(Process process, String input, String charset) throws IOException {
Writer inputEmitter = null;
try {
if (input != null) {
inputEmitter = new OutputStreamWriter(process.getOutputStream(), charset);
inputEmitter.write(input);
}
} finally {
try {
StreamUtil.close(inputEmitter);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Integer nomNomNom(Process process, OutputHandler stdOut, OutputHandler stdErr, String charset) {
Gobbler outputGobbler = new Gobbler( process.getInputStream(), stdOut, charset );
Gobbler errorGobbler = new Gobbler( process.getErrorStream(), stdErr, charset );
// kick off the gobblers
errorGobbler.start();
outputGobbler.start();
try {
int exitCode = process.waitFor();
errorGobbler.join();
outputGobbler.join();
return exitCode;
} catch (InterruptedException e) {
//ignore
} finally {
try {
StreamUtil.close(process.getErrorStream(), process.getInputStream());
} catch ( IOException e) {
e.printStackTrace();
}
}
return null;
}
public String getRawCmdStr() {
StringBuilder rawCmdStr = new StringBuilder();
for (String item : _rawCmd) {
if (rawCmdStr.length() > 0) {
rawCmdStr.append(' ');
}
if (item.contains(" ")) {
rawCmdStr.append('"').append(item).append('"');
}
else {
rawCmdStr.append(item);
}
}
return rawCmdStr.toString();
}
//=================================================================================
// Process results accessors
//=================================================================================
/**
* Returns any output buffered from the process' stdout or stderr, depending on if
* {@link #withStdOutBuffered()} and/or {@link #withStdErrBuffered()} were used.
*
* If a buffer was desired and the process printed nothing out, an empty string is
* returned.
*
* @return the buffer, or null if nothing was to be buffered
*/
public String getBuffer() {
return _buffer == null ? null : _buffer.toString();
}
/**
* Returns the process' exit code, if it finished.
*
* @return the exit code, or null if the process never completed
*/
public Integer getExitCode() {
return _exitCode;
}
//=================================================================================
// Builder methods
//=================================================================================
/**
* Sets this process' working directory.
*
* @param dir this process' working directory
* @return this object for chaining
*/
public ProcessRunner withWorkingDirectory(File dir) {
_workingDirectory = dir;
return this;
}
/**
* Adds an argument to the command.
*
* @param arg the command line argument to add
* @return this object for chaining
*/
public ProcessRunner withArg(String arg) {
_rawCmd.add(arg);
return this;
}
/**
* Adds a name-value pair into this process' environment.
* This can be called multiple times in a chain to set multiple
* environment variables.
*
* @param name the variable name
* @param value the variable value
* @return this object for chaining
*
* @see ProcessBuilder
* @see System#getenv()
*/
public ProcessRunner withEnvironmentVariable(String name, String value) {
_env.put(name, value);
return this;
}
/**
* Sets the charset with which to write to this process' input and read its output.
*
* If unused, this process will by default use UTF-8.
*
* @param cs the charset to use
* @return this object for chaining
*/
@SuppressWarnings({"UnusedDeclaration"})
public ProcessRunner withCharset(String cs) {
_charset = cs;
return this;
}
/**
* Sets this process' stdout stream to be stored in the buffer accessible by {@link #getBuffer()}.
*
* @return this object for chaining
*/
public ProcessRunner withStdOutBuffered() {
_bufferStdOut = true;
return this;
}
/**
* Sets this process' stdout stream to be stored in the buffer accessible by {@link #getBuffer()}.
*
* @return this object for chaining
*/
public ProcessRunner withStdErrBuffered() {
_bufferStdErr = true;
return this;
}
/**
* Sets this process' output to be displayed the parent process' stdout and stderr.
*
* @return this object for chaining
*/
public ProcessRunner withEcho() {
_echo = true;
return this;
}
/**
* Sets the text to be directed into this process' stdin.
*
* @param input the text to direct into stdin
* @return this object for chaining
*/
public ProcessRunner input(String input) {
_input = input;
return this;
}
/**
* The process built up will used CMD.EXE if this is a windows platform. This is necessary because on windows certain
* basic commands such as "dir" are not programs, but rather are built into CMD. Thanks, Microsoft.
*
* @return this object for chaining
*/
@SuppressWarnings({"UnusedDeclaration"})
public ProcessRunner withCMD()
{
_withCMD = true;
return this;
}
/**
* Adds a block to handle lines output this process' stderr.
*
* This can be called multiple times in a chain to add multiple handlers.
*
* @param stdErrHandler handler that will be called with every line of output to stderr
* @return this object for chaining
*/
public ProcessRunner withStdErrHandler( OutputHandler stdErrHandler )
{
_stdErrHandlers.add(stdErrHandler);
return this;
}
/**
* Adds a block to handle lines output this process' stdout.
*
* This can be called multiple times in a chain to add multiple handlers.
*
* @param stdOutHandler handler that will be called with every line of output to stdout
* @return this object for chaining
*/
public ProcessRunner withStdOutHandler( OutputHandler stdOutHandler )
{
_stdOutHandlers.add(stdOutHandler);
return this;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy