Added runCommand to execute external processes

This commit is contained in:
igor%mir2.org 2003-01-26 15:43:50 +00:00
parent 47d2d76ac2
commit 7b5c64a220

View File

@ -60,7 +60,7 @@ public class Global extends ImporterTopLevel {
super(cx);
String[] names = { "print", "quit", "version", "load", "help",
"loadClass", "defineClass", "spawn", "sync",
"serialize", "deserialize" };
"serialize", "deserialize", "runCommand" };
try {
defineFunctionProperties(names, Global.class,
ScriptableObject.DONTENUM);
@ -216,8 +216,7 @@ public class Global extends ImporterTopLevel {
{
Class clazz = getClass(args);
if (!Script.class.isAssignableFrom(clazz)) {
throw Context.reportRuntimeError(ToolErrorReporter.getMessage(
"msg.must.implement.Script"));
throw reportRuntimeError("msg.must.implement.Script");
}
Script script = (Script) clazz.newInstance();
script.exec(cx, thisObj);
@ -228,17 +227,14 @@ public class Global extends ImporterTopLevel {
InvocationTargetException
{
if (args.length == 0) {
throw Context.reportRuntimeError(ToolErrorReporter.getMessage(
"msg.expected.string.arg"));
throw reportRuntimeError("msg.expected.string.arg");
}
String className = Context.toString(args[0]);
try {
return Class.forName(className);
}
catch (ClassNotFoundException cnfe) {
throw Context.reportRuntimeError(ToolErrorReporter.getMessage(
"msg.class.not.found",
className));
throw reportRuntimeError("msg.class.not.found", className);
}
}
@ -303,8 +299,7 @@ public class Global extends ImporterTopLevel {
} else if (args.length != 0 && args[0] instanceof Script) {
runner = new Runner(scope, (Script) args[0]);
} else {
throw Context.reportRuntimeError(ToolErrorReporter.getMessage(
"msg.spawn.args"));
throw reportRuntimeError("msg.spawn.args");
}
Thread thread = new Thread(runner);
thread.start();
@ -332,17 +327,160 @@ public class Global extends ImporterTopLevel {
* exit
*/
public static Object sync(Context cx, Scriptable thisObj, Object[] args,
Function funObj)
Function funObj)
{
if (args.length == 1 && args[0] instanceof Function) {
return new Synchronizer((Function)args[0]);
}
else {
throw Context.reportRuntimeError(ToolErrorReporter.getMessage(
"msg.spawn.args"));
throw reportRuntimeError("msg.sync.args");
}
}
/**
* Execute the specified command with the given argument and options
* as a separate process and return the exit status of the process.
* <p>
* Usage:
* <pre>
* runCommand(command)
* runCommand(command, arg1, ..., argN)
* runCommand(command, arg1, ..., argN, options)
* </pre>
* All except the last arguments to runCommand are converted to strings
* and denote command name and its arguments. If the last argument is a
* JavaScript object, it is an option object. Otherwise it is converted to
* string denoting the last argument and options objects assumed to be
* empty.
* Te following properties of the option object are processed:
* <ul>
* <li><tt>args</tt> - provides an array of additional command arguments
* <li><tt>env</tt> - explicit environment object. All its enumeratable
* properties define the corresponding environment variable names.
* <li><tt>input</tt> - the process input. If it is not
* java.io.InputStream, it is converted to string and sent to the process
* as its input. If not specified, no input is provided to the process.
* <li><tt>output</tt> - the process output instead of java.lang.System.out.
* If it is not instance of java.io.OutputStream, the process output is
* read, converted to a string, appended to the output property value
* converted to string and put as the new value of the output property.
* <li><tt>err</tt> - the process error output instead of
* java.lang.System.err. If it is not instance of java.io.OutputStream,
* the process error output is read, converted to a string, appended to
* the err property value converted to string and put as the new
* value of the err property.
* </ul>
*/
public static Object runCommand(Context cx, Scriptable thisObj,
Object[] args, Function funObj)
throws IOException
{
int L = args.length;
if (L == 0 || (L == 1 && args[0] instanceof Scriptable)) {
throw reportRuntimeError("msg.system.args");
}
InputStream in = null;
OutputStream out = null, err = null;
ByteArrayOutputStream outBytes = null, errBytes = null;
Object outObj = null, errObj = null;
String[] environment = null;
Scriptable params = null;
Object[] addArgs = null;
if (args[L - 1] instanceof Scriptable) {
params = (Scriptable)args[L - 1];
--L;
Object envObj = ScriptableObject.getProperty(params, "env");
if (envObj != Scriptable.NOT_FOUND) {
if (envObj == null) {
environment = new String[0];
} else {
if (!(envObj instanceof Scriptable)) {
throw reportRuntimeError("msg.system.args");
}
Scriptable envHash = (Scriptable)envObj;
Object[] ids = ScriptableObject.getPropertyIds(envHash);
environment = new String[ids.length];
for (int i = 0; i != ids.length; ++i) {
Object keyObj = ids[i], val;
String key;
if (keyObj instanceof String) {
key = (String)keyObj;
val = ScriptableObject.getProperty(envHash, key);
} else {
int ikey = ((Number)keyObj).intValue();
key = Integer.toString(ikey);
val = ScriptableObject.getProperty(envHash, ikey);
}
if (val == ScriptableObject.NOT_FOUND) {
val = Undefined.instance;
}
environment[i] = key+'='+ScriptRuntime.toString(val);
}
}
}
Object inObj = ScriptableObject.getProperty(params, "input");
if (inObj != Scriptable.NOT_FOUND) {
in = toInputStream(inObj);
}
outObj = ScriptableObject.getProperty(params, "output");
if (outObj != Scriptable.NOT_FOUND) {
out = toOutputStream(outObj);
if (out == null) {
outBytes = new ByteArrayOutputStream();
out = outBytes;
}
}
errObj = ScriptableObject.getProperty(params, "err");
if (errObj != Scriptable.NOT_FOUND) {
err = toOutputStream(errObj);
if (err == null) {
errBytes = new ByteArrayOutputStream();
err = errBytes;
}
}
Object addArgsObj = ScriptableObject.getProperty(params, "args");
if (addArgsObj != Scriptable.NOT_FOUND) {
Scriptable s = cx.toObject(addArgsObj,
getTopLevelScope(thisObj));
addArgs = cx.getElements(s);
}
}
Global global = getInstance(thisObj);
if (out == null) {
out = (global != null) ? global.getOut() : System.out;
}
if (err == null) {
err = (global != null) ? global.getErr() : System.err;
}
// If no explicit input stream, do not send any input to process,
// in particular, do not use System.in to avoid deadlocks
// when waiting for user input to send to process which is already
// terminated as it is not always possible to interrupt read method.
String[] cmd = new String[(addArgs == null) ? L : L + addArgs.length];
for (int i = 0; i != L; ++i) {
cmd[i] = ScriptRuntime.toString(args[i]);
}
if (addArgs != null) {
for (int i = 0; i != addArgs.length; ++i) {
cmd[L + i] = ScriptRuntime.toString(addArgs[i]);
}
}
int exitCode = runProcess(cmd, environment, in, out, err);
if (outBytes != null) {
String s = ScriptRuntime.toString(outObj) + outBytes.toString();
ScriptableObject.putProperty(params, "output", s);
}
if (errBytes != null) {
String s = ScriptRuntime.toString(errObj) + errBytes.toString();
ScriptableObject.putProperty(params, "err", s);
}
return new Integer(exitCode);
}
public InputStream getIn() {
return inStream == null ? System.in : inStream;
}
@ -376,6 +514,222 @@ public class Global extends ImporterTopLevel {
return null;
}
/**
* If any of in, out, err is null, the corresponding process stream will
* be closed immediately, otherwise it will be closed as soon as
* all data will be read from/written to process
*/
private static int runProcess(String[] cmd, String[] environment,
InputStream in, OutputStream out,
OutputStream err)
throws IOException
{
Process p;
if (environment == null) {
p = Runtime.getRuntime().exec(cmd);
} else {
p = Runtime.getRuntime().exec(cmd, environment);
}
PipeThread inThread = null, errThread = null;
try {
InputStream errProcess = null;
try {
if (err != null) {
errProcess = p.getErrorStream();
} else {
p.getErrorStream().close();
}
InputStream outProcess = null;
try {
if (out != null) {
outProcess = p.getInputStream();
} else {
p.getInputStream().close();
}
OutputStream inProcess = null;
try {
if (in != null) {
inProcess = p.getOutputStream();
} else {
p.getOutputStream().close();
}
if (out != null) {
// Read process output on this thread
if (err != null) {
errThread = new PipeThread(true, errProcess,
err);
errThread.start();
}
if (in != null) {
inThread = new PipeThread(false, in,
inProcess);
inThread.start();
}
pipe(true, outProcess, out);
} else if (in != null) {
// No output, read process input on this thread
if (err != null) {
errThread = new PipeThread(true, errProcess,
err);
errThread.start();
}
pipe(false, in, inProcess);
in.close();
} else if (err != null) {
// No output or input, read process err
// on this thread
pipe(true, errProcess, err);
errProcess.close();
errProcess = null;
}
// wait for process completion
for (;;) {
try { p.waitFor(); break; }
catch (InterruptedException ex) { }
}
return p.exitValue();
} finally {
// pipe will close stream as well, but for reliability
// duplicate it in any case
if (inProcess != null) {
inProcess.close();
}
}
} finally {
if (outProcess != null) {
outProcess.close();
}
}
} finally {
if (errProcess != null) {
errProcess.close();
}
}
} finally {
p.destroy();
if (inThread != null) {
for (;;) {
try { inThread.join(); break; }
catch (InterruptedException ex) { }
}
}
if (errThread != null) {
for (;;) {
try { errThread.join(); break; }
catch (InterruptedException ex) { }
}
}
}
}
static void pipe(boolean fromProcess, InputStream from, OutputStream to)
throws IOException
{
try {
final int SIZE = 4096;
byte[] buffer = new byte[SIZE];
for (;;) {
int n;
if (!fromProcess) {
n = from.read(buffer, 0, SIZE);
} else {
try {
n = from.read(buffer, 0, SIZE);
} catch (IOException ex) {
// Ignore exception as it can be cause by closed pipe
break;
}
}
if (n < 0) { break; }
if (fromProcess) {
to.write(buffer, 0, n);
to.flush();
} else {
try {
to.write(buffer, 0, n);
to.flush();
} catch (IOException ex) {
// Ignore exception as it can be cause by closed pipe
break;
}
}
}
} finally {
if (fromProcess) {
from.close();
} else {
to.close();
}
}
}
private static InputStream toInputStream(Object value)
throws IOException
{
InputStream is = null;
String s = null;
if (value instanceof Wrapper) {
Object unwrapped = ((Wrapper)value).unwrap();
if (unwrapped instanceof InputStream) {
is = (InputStream)unwrapped;
} else if (unwrapped instanceof byte[]) {
is = new ByteArrayInputStream((byte[])unwrapped);
} else if (unwrapped instanceof Reader) {
s = readerToString((Reader)unwrapped);
} else if (unwrapped instanceof char[]) {
s = new String((char[])unwrapped);
}
}
if (is == null) {
if (s == null) { s = ScriptRuntime.toString(value); }
is = new ByteArrayInputStream(s.getBytes());
}
return is;
}
private static OutputStream toOutputStream(Object value) {
OutputStream os = null;
if (value instanceof Wrapper) {
Object unwrapped = ((Wrapper)value).unwrap();
if (unwrapped instanceof OutputStream) {
os = (OutputStream)unwrapped;
}
}
return os;
}
private static String readerToString(Reader reader)
throws IOException
{
char[] buffer = new char[4096];
int offset = 0;
for (;;) {
int n = reader.read(buffer, offset, buffer.length - offset);
if (n < 0) { break; }
offset += n;
if (offset == buffer.length) {
char[] tmp = new char[buffer.length * 2];
System.arraycopy(buffer, 0, tmp, 0, offset);
buffer = tmp;
}
}
return new String(buffer, 0, offset);
}
static RuntimeException reportRuntimeError(String msgId) {
String message = ToolErrorReporter.getMessage(msgId);
return Context.reportRuntimeError(message);
}
static RuntimeException reportRuntimeError(String msgId, String msgArg)
{
String message = ToolErrorReporter.getMessage(msgId, msgArg);
return Context.reportRuntimeError(message);
}
NativeArray history;
public InputStream inStream;
public PrintStream outStream;
@ -398,7 +752,6 @@ class Runner implements Runnable {
public void run() {
Context cx = Main.enterContext();
try {
if (f != null)
f.call(cx, scope, scope, args);
@ -408,9 +761,9 @@ class Runner implements Runnable {
Context.reportError(ToolErrorReporter.getMessage(
"msg.uncaughtJSException",
e.getMessage()));
} finally {
Context.exit();
}
cx.exit();
}
private Scriptable scope;
@ -418,3 +771,27 @@ class Runner implements Runnable {
private Script s;
private Object[] args;
}
class PipeThread extends Thread {
PipeThread(boolean fromProcess, InputStream from, OutputStream to) {
setDaemon(true);
this.fromProcess = fromProcess;
this.from = from;
this.to = to;
}
public void run() {
try {
Global.pipe(fromProcess, from, to);
} catch (IOException ex) {
throw Global.reportRuntimeError(
"msg.uncaughtIOException", ex.getMessage());
}
}
private boolean fromProcess;
private InputStream from;
private OutputStream to;
}