package jsr223.nativeshell; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.util.Collection; import java.util.Map; import javax.script.ScriptContext; import static jsr223.nativeshell.IOUtils.pipe; import static jsr223.nativeshell.StringUtils.toEmptyStringIfNull; public class NativeShellRunner { public static final Integer RETURN_CODE_OK = 0; private NativeShell nativeShell; public NativeShellRunner(NativeShell nativeShell) { this.nativeShell = nativeShell; } public String getInstalledVersion() { try { return runAndGetOutput(nativeShell.getInstalledVersionCommand()); } catch (Throwable e) { return "Could not determine version"; } } public String getMajorVersion() { try { return runAndGetOutput(nativeShell.getMajorVersionCommand()); } catch (Throwable e) { return "Could not determine version"; } } public int run(String command, ScriptContext scriptContext) { File commandAsTemporaryFile = commandAsTemporaryFile(command); int exitValue = run(commandAsTemporaryFile, scriptContext); commandAsTemporaryFile.delete(); return exitValue; } private int run(File command, ScriptContext scriptContext) { ProcessBuilder processBuilder = nativeShell.createProcess(command); addBindingsAsEnvironmentVariables(scriptContext, processBuilder); return run(processBuilder, scriptContext.getReader(), scriptContext.getWriter(), scriptContext.getErrorWriter()); } private String runAndGetOutput(String command) { ProcessBuilder processBuilder = nativeShell.createProcess(command); StringWriter processOutput = new StringWriter(); Reader closedInput = new Reader() { @Override public int read(char[] cbuf, int off, int len) throws IOException { return -1; } @Override public void close() throws IOException { } }; run(processBuilder, closedInput, processOutput, new StringWriter()); return processOutput.toString(); } private static int run(ProcessBuilder processBuilder, Reader processInput, Writer processOutput, Writer processError) { try { final Process process = processBuilder.start(); Thread input = writeProcessInput(process.getOutputStream(), processInput); Thread output = readProcessOutput(process.getInputStream(), processOutput); Thread error = readProcessOutput(process.getErrorStream(), processError); input.start(); output.start(); error.start(); process.waitFor(); output.join(); error.join(); input.interrupt(); // TODO better thing to do? return process.exitValue(); } catch (Exception e) { throw new RuntimeException(e); } } private void addBindingsAsEnvironmentVariables(ScriptContext scriptContext, ProcessBuilder processBuilder) { Map<String, String> environment = processBuilder.environment(); 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, environment); } else if (bindingValue instanceof Collection) { addCollectionBindingAsEnvironmentVariable(bindingKey, (Collection) bindingValue, environment); } else if (bindingValue instanceof Map) { addMapBindingAsEnvironmentVariable(bindingKey, (Map<?, ?>) bindingValue, environment); } else { environment.put(bindingKey, toEmptyStringIfNull(binding.getValue())); } } } private void addMapBindingAsEnvironmentVariable(String bindingKey, Map<?, ?> bindingValue, Map<String, String> environment) { for (Map.Entry<?, ?> entry : ((Map<?, ?>) bindingValue).entrySet()) { environment.put(bindingKey + "_" + entry.getKey(), (entry.getValue() == null ? "" : toEmptyStringIfNull(entry.getValue()))); } } private void addCollectionBindingAsEnvironmentVariable(String bindingKey, Collection bindingValue, Map<String, String> environment) { Object[] bindingValueAsArray = bindingValue.toArray(); addArrayBindingAsEnvironmentVariable(bindingKey, bindingValueAsArray, environment); } private void addArrayBindingAsEnvironmentVariable(String bindingKey, Object[] bindingValue, Map<String, String> environment) { for (int i = 0; i < bindingValue.length; i++) { environment.put(bindingKey + "_" + i, (bindingValue[i] == null ? "" : toEmptyStringIfNull(bindingValue[i].toString()))); } } 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) { } } } }); } private File commandAsTemporaryFile(String command) { try { File commandAsFile = File.createTempFile("jsr223nativeshell-", nativeShell.getFileExtension()); IOUtils.writeStringToFile(command, commandAsFile); return commandAsFile; } catch (IOException e) { throw new RuntimeException(e); } } }