package org.marketcetera.util.exec;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import org.marketcetera.util.except.ExceptUtils;
import org.marketcetera.util.except.I18NException;
import org.marketcetera.util.except.I18NInterruptedException;
import org.marketcetera.util.log.I18NBoundMessage1P;
import org.marketcetera.util.misc.ClassVersion;
/**
* A simple process executor. The executed process requires no input,
* and the interleaved mix of its standard output and error streams is
* directed to either the JVM's standard output or error stream, or to
* an in-memory array.
*
* @author tlerios@marketcetera.com
* @since 0.5.0
* @version $Id: Exec.java 16154 2012-07-14 16:34:05Z colin $
*/
/* $License$ */
@ClassVersion("$Id: Exec.java 16154 2012-07-14 16:34:05Z colin $")
public final class Exec
{
// CLASS METHODS.
/**
* Executes the process with the given arguments and returns the
* execution result. The first argument must be the required
* executable/binary command. The working directory of the process
* is set to the given one. The interleaved output and error
* streams of the process are redirected/captured as per the given
* setting.
*
* @param directory The working directory. Use null for the
* JVM's current working directory.
* @param disposition The disposition of the output.
* @param args The process arguments.
*
* @return The execution result.
*
* @throws I18NInterruptedException Thrown if process execution
* fails due to an interruption.
* @throws I18NException Thrown if process execution fails for any
* other reason.
*/
public static ExecResult run
(File directory,
Disposition disposition,
String... args)
throws I18NException
{
String command=args[0];
Process process;
InputThread consumer=null;
OutputStream out;
try {
// Configure process builder.
ProcessBuilder builder=new ProcessBuilder(args);
if (directory!=null) {
builder.directory(directory);
}
builder.redirectErrorStream(true);
// Choose output stream.
boolean closeOut=false;
if (disposition==Disposition.MEMORY) {
out=new ByteArrayOutputStream();
closeOut=true;
} else if (disposition==Disposition.STDOUT) {
out=System.out;
} else {
out=System.err;
}
// Start process and input consumer.
process=builder.start();
consumer=new InputThread
(command,process.getInputStream(),out,closeOut);
consumer.start();
} catch (Throwable t) {
if (consumer!=null) {
consumer.interrupt();
}
throw ExceptUtils.wrap(t,new I18NBoundMessage1P
(Messages.CANNOT_EXECUTE,command));
}
/* EXTREME TEST 1: uncomment this comment.
try {
Thread.sleep(1000);
consumer.interrupt();
process.getInputStream().close();
} catch (Throwable t) {}
*/
try {
// Wait for process to end, and retain its exit code.
process.waitFor();
int exitValue=process.exitValue();
// Wait for input consumer to end, and retain the captured
// output.
consumer.join();
consumer=null;
byte[] capture=null;
if (disposition==Disposition.MEMORY) {
capture=((ByteArrayOutputStream)out).toByteArray();
}
// Clean up.
return new ExecResult(exitValue,capture);
} catch (Throwable t) {
if (consumer!=null) {
consumer.interrupt();
}
throw ExceptUtils.wrap(t,new I18NBoundMessage1P
(Messages.UNEXPECTED_TERMINATION,command));
} finally {
process.destroy();
}
}
/**
* Executes the process with the given arguments and returns the
* execution result. The first argument must be the required
* executable/binary command. The name of the working directory of
* the process is set to the given one. The interleaved output and
* error streams of the process are redirected/captured as per the
* given setting.
*
* @param name The name of the working directory. Use null for the
* JVM's current working directory.
* @param disposition The disposition of the output.
* @param args The process arguments.
*
* @return The execution result.
*
* @throws I18NException Thrown if process execution fails.
*/
public static ExecResult run
(String name,
Disposition disposition,
String... args)
throws I18NException
{
File file=null;
if (name!=null) {
file=new File(name);
}
return run(file,disposition,args);
}
}