package jsr223.nativeshell.executable;
import jsr223.nativeshell.IOUtils;
import javax.script.*;
import java.io.*;
import java.util.*;
import static jsr223.nativeshell.IOUtils.pipe;
import static jsr223.nativeshell.StringUtils.toEmptyStringIfNull;
public class ExecutableScriptEngine extends AbstractScriptEngine {
@Override
public Object eval(String script, ScriptContext scriptContext) throws ScriptException {
try {
String commandLineWithBindings = expandAndReplaceBindings(script, scriptContext);
ProcessBuilder processBuilder = new ProcessBuilder(CommandLine.translateCommandline(commandLineWithBindings));
Map<String, String> environment = processBuilder.environment();
for (Map.Entry<String, Object> binding : scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) {
environment.put(binding.getKey(), toEmptyStringIfNull(binding.getValue()));
}
final Process process = processBuilder.start();
Thread input = writeProcessInput(process.getOutputStream(), scriptContext.getReader());
Thread output = readProcessOutput(process.getInputStream(), scriptContext.getWriter());
Thread error = readProcessOutput(process.getErrorStream(), scriptContext.getErrorWriter());
input.start();
output.start();
error.start();
process.waitFor();
output.join();
error.join();
input.interrupt();
int exitValue = process.exitValue();
if (exitValue != 0) {
throw new ScriptException("Command execution failed with exit code " + exitValue);
}
return exitValue;
} catch (ScriptException e) {
throw e;
} catch (Exception e) {
throw new ScriptException(e);
}
}
private String expandAndReplaceBindings(String script, ScriptContext scriptContext) {
Bindings collectionBindings = createBindings();
for (Map.Entry<String, Object> binding : scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) {
String bindingKey = binding.getKey();
Object bindingValue = binding.getValue();
if (bindingValue instanceof Object[]) {
addArrayBindingAsEnvironmentVariable(bindingKey, (Object[]) bindingValue, collectionBindings);
} else if (bindingValue instanceof Collection) {
addCollectionBindingAsEnvironmentVariable(bindingKey, (Collection) bindingValue, collectionBindings);
} else if (bindingValue instanceof Map) {
addMapBindingAsEnvironmentVariable(bindingKey, (Map<?, ?>) bindingValue, collectionBindings);
}
}
scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).putAll(collectionBindings);
Set<Map.Entry<String, Object>> bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).entrySet();
ArrayList<Map.Entry<String, Object>> sortedBindings = new ArrayList<Map.Entry<String, Object>>(bindings);
Collections.sort(sortedBindings, LONGER_KEY_FIRST);
for (Map.Entry<String, Object> binding : sortedBindings) {
String bindingKey = binding.getKey();
Object bindingValue = binding.getValue();
if (script.contains("$" + bindingKey)) {
script = script.replaceAll("\\$" + bindingKey, toEmptyStringIfNull(bindingValue));
}
if (script.contains("${" + bindingKey + "}")) {
script = script.replaceAll("\\$\\{" + bindingKey + "\\}", toEmptyStringIfNull(bindingValue));
}
}
return script;
}
private void addMapBindingAsEnvironmentVariable(String bindingKey, Map<?, ?> bindingValue, Bindings bindings) {
for (Map.Entry<?, ?> entry : ((Map<?, ?>) bindingValue).entrySet()) {
bindings.put(bindingKey + "_" + entry.getKey(), (entry.getValue() == null ? "" : toEmptyStringIfNull(entry.getValue())));
}
}
private void addCollectionBindingAsEnvironmentVariable(String bindingKey, Collection bindingValue, Bindings bindings) {
Object[] bindingValueAsArray = bindingValue.toArray();
addArrayBindingAsEnvironmentVariable(bindingKey, bindingValueAsArray, bindings);
}
private void addArrayBindingAsEnvironmentVariable(String bindingKey, Object[] bindingValue, Bindings bindings) {
for (int i = 0; i < bindingValue.length; i++) {
bindings.put(bindingKey + "_" + i, (bindingValue[i] == null ? "" : toEmptyStringIfNull(bindingValue[i])));
}
}
private static Thread readProcessOutput(final InputStream processOutput, final Writer contextWriter) {
return new Thread(new Runnable() {
@Override
public void run() {
try {
pipe(new BufferedReader(new InputStreamReader(processOutput)), new BufferedWriter(contextWriter));
} catch (IOException ignored) {
}
}
});
}
private static Thread writeProcessInput(final OutputStream processOutput, final Reader contextWriter) {
return new Thread(new Runnable() {
@Override
public void run() {
try {
pipe(new BufferedReader(contextWriter), new OutputStreamWriter(processOutput));
} catch (IOException closed) {
try {
processOutput.close();
} catch (IOException ignored) {
}
}
}
});
}
@Override
public Object eval(Reader reader, ScriptContext context) throws ScriptException {
return eval(IOUtils.toString(reader), context);
}
@Override
public Bindings createBindings() {
return new SimpleBindings();
}
@Override
public ScriptEngineFactory getFactory() {
return new ExecutableScriptEngineFactory();
}
public static final Comparator<Map.Entry<String, Object>> LONGER_KEY_FIRST = new Comparator<Map.Entry<String, Object>>() {
@Override
public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
return o2.getKey().length() - o1.getKey().length();
}
};
}