diff --git a/src/dorkbox/util/process/TeeOutputStream.java b/src/dorkbox/util/TeeOutputStream.java similarity index 94% rename from src/dorkbox/util/process/TeeOutputStream.java rename to src/dorkbox/util/TeeOutputStream.java index f9223aa..448dd18 100644 --- a/src/dorkbox/util/process/TeeOutputStream.java +++ b/src/dorkbox/util/TeeOutputStream.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.util.process; +package dorkbox.util; import java.io.IOException; import java.io.OutputStream; @@ -37,30 +37,35 @@ class TeeOutputStream extends OutputStream { } } + @Override public void write(int b) throws IOException { this.out.write(b); this.tee.write(b); } + @Override public void write(byte[] b) throws IOException { this.out.write(b); this.tee.write(b); } + @Override public void write(byte[] b, int off, int len) throws IOException { this.out.write(b, off, len); this.tee.write(b, off, len); } + @Override public void flush() throws IOException { this.out.flush(); this.tee.flush(); } + @Override public void close() throws IOException { this.out.close(); diff --git a/src/dorkbox/util/process/JvmExecutor.java b/src/dorkbox/util/process/JvmExecutor.java deleted file mode 100644 index 2cb67c6..0000000 --- a/src/dorkbox/util/process/JvmExecutor.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util.process; - -import java.io.File; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; - -import dorkbox.util.FileUtil; -import dorkbox.util.OS; - -/** - * This will FORK the java process initially used to start the currently running JVM. Changing the java executable will change this behaviors - */ -public -class JvmExecutor extends ShellExecutor { - - /** - * The directory into which a local VM installation should be unpacked. - */ - public static final String LOCAL_JAVA_DIR = "java_vm"; - - /** - * Reconstructs the path to the JVM used to launch this process. - * - * @param windebug if true we will use java.exe instead of javaw.exe on Windows. - */ - public static - String getJVMPath(File appdir, boolean windebug) { - // first look in our application directory for an installed VM - String vmpath = checkJvmPath(new File(appdir, LOCAL_JAVA_DIR).getPath(), windebug); - - // then fall back to the VM in which we're already running - if (vmpath == null) { - vmpath = checkJvmPath(System.getProperty("java.home"), windebug); - } - - // then throw up our hands and hope for the best - if (vmpath == null) { - System.err.println("Unable to find java [appdir=" + appdir + ", java.home=" + System.getProperty("java.home") + "]!"); - vmpath = "java"; - } - - // Oddly, the Mac OS X specific java flag -Xdock:name will only work if java is launched - // from /usr/bin/java, and not if launched by directly referring to /bin/java, - // even though the former is a symlink to the latter! To work around this, see if the - // desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead. - if (OS.isMacOsX()) { - String localVM = FileUtil.normalize("/usr/bin/java").getAbsolutePath(); - String vmCheck = FileUtil.normalize(vmpath).getAbsolutePath(); - if (localVM.equals(vmCheck)) { - vmpath = "/usr/bin/java"; - } - } - - return vmpath; - } - - /** - * Checks whether a Java Virtual Machine can be located in the supplied path. - */ - private static - String checkJvmPath(String vmhome, boolean windebug) { - // linux does this... - String vmbase = vmhome + File.separator + "bin" + File.separator; - String vmpath = vmbase + "java"; - if (new File(vmpath).exists()) { - return vmpath; - } - - // windows does this - if (!windebug) { - vmpath = vmbase + "javaw.exe"; - } - else { - vmpath = vmbase + "java.exe"; // open a console on windows - } - - if (new File(vmpath).exists()) { - return vmpath; - } - - return null; - } - // this is NOT related to JAVA_HOME, but is instead the location of the JRE that was used to launch java initially. - private String javaLocation = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; - private String mainClass; - private int startingHeapSizeInMegabytes = 40; - private int maximumHeapSizeInMegabytes = 128; - private List jvmOptions = new ArrayList(); - private List classpathEntries = new ArrayList(); - - // what version of java?? - // so, this starts a NEW java, from an ALREADY existing java. - private List mainClassArguments = new ArrayList(); - private String jarFile; - - public - JvmExecutor() { - super(null, null, null); - } - - public - JvmExecutor(InputStream in, PrintStream out, PrintStream err) { - super(in, out, err); - } - - public final - void setMainClass(String mainClass) { - this.mainClass = mainClass; - } - - public final - void setStartingHeapSizeInMegabytes(int startingHeapSizeInMegabytes) { - this.startingHeapSizeInMegabytes = startingHeapSizeInMegabytes; - } - - public final - void setMaximumHeapSizeInMegabytes(int maximumHeapSizeInMegabytes) { - this.maximumHeapSizeInMegabytes = maximumHeapSizeInMegabytes; - } - - public final - void addJvmClasspath(String classpathEntry) { - this.classpathEntries.add(classpathEntry); - } - - public final - void addJvmClasspaths(List paths) { - this.classpathEntries.addAll(paths); - } - - public final - void addJvmOption(String argument) { - this.jvmOptions.add(argument); - } - - public final - void addJvmOptions(List paths) { - this.jvmOptions.addAll(paths); - } - - public final - void setJarFile(String jarFile) { - this.jarFile = jarFile; - } - - private - String getClasspath() { - StringBuilder builder = new StringBuilder(); - int count = 0; - final int totalSize = this.classpathEntries.size(); - final String pathseparator = File.pathSeparator; - - // DO NOT QUOTE the elements in the classpath! - for (String classpathEntry : this.classpathEntries) { - try { - // make sure the classpath is ABSOLUTE pathname - classpathEntry = FileUtil.normalize(classpathEntry).getAbsolutePath(); - - // fix a nasty problem when spaces aren't properly escaped! - classpathEntry = classpathEntry.replaceAll(" ", "\\ "); - - builder.append(classpathEntry); - count++; - } catch (Exception e) { - e.printStackTrace(); - } - - if (count < totalSize) { - builder.append(pathseparator); // ; on windows, : on linux - } - } - return builder.toString(); - } - - /** - * Specify the JAVA exectuable to launch this process. By default, this will use the same java exectuable - * as was used to start the current JVM. - */ - public - void setJava(String javaLocation) { - this.javaLocation = javaLocation; - } - - @Override - public - int start() { - setExecutable(this.javaLocation); - - // save off the original arguments - List origArguments = new ArrayList(this.arguments.size()); - origArguments.addAll(this.arguments); - this.arguments = new ArrayList(0); - - - // two versions, java vs not-java - this.arguments.add("-Xms" + this.startingHeapSizeInMegabytes + "M"); - this.arguments.add("-Xmx" + this.maximumHeapSizeInMegabytes + "M"); - this.arguments.add("-server"); - - for (String option : this.jvmOptions) { - this.arguments.add(option); - } - - //same as -cp - String classpath = getClasspath(); - - // two more versions. jar vs classs - if (this.jarFile != null) { - this.arguments.add("-jar"); - this.arguments.add(this.jarFile); - - // interesting note. You CANNOT have a classpath specified on the commandline - // when using JARs!! It must be set in the jar's MANIFEST. - if (!classpath.isEmpty()) { - System.err.println("WHOOPS. You CANNOT have a classpath specified on the commandline when using JARs."); - System.err.println(" It must be set in the JARs MANIFEST instead."); - System.exit(1); - } - - } - // if we are running classes! - else if (this.mainClass != null) { - if (!classpath.isEmpty()) { - this.arguments.add("-classpath"); - this.arguments.add(classpath); - } - - // main class must happen AFTER the classpath! - this.arguments.add(this.mainClass); - } - else { - System.err.println("WHOOPS. You must specify a jar or main class when running java!"); - System.exit(1); - } - - - for (String arg : this.mainClassArguments) { - if (arg.contains(" ")) { - // individual arguments MUST be in their own element in order to - // be processed properly (this is how it works on the command line!) - String[] split = arg.split(" "); - for (String s : split) { - this.arguments.add(s); - } - } - else { - this.arguments.add(arg); - } - } - - this.arguments.addAll(origArguments); - - return super.start(); - } -} diff --git a/src/dorkbox/util/process/NullOutputStream.java b/src/dorkbox/util/process/NullOutputStream.java deleted file mode 100644 index 0a04de3..0000000 --- a/src/dorkbox/util/process/NullOutputStream.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util.process; - -import java.io.IOException; -import java.io.OutputStream; - -public -class NullOutputStream extends OutputStream { - @Override - public - void write(int i) throws IOException { - //do nothing - } - - @Override - public - void write(byte[] b) throws IOException { - //do nothing - } - - @Override - public - void write(byte[] b, int off, int len) throws IOException { - //do nothing - } - - @Override - public - void flush() throws IOException { - //do nothing - } -} diff --git a/src/dorkbox/util/process/ProcessProxy.java b/src/dorkbox/util/process/ProcessProxy.java deleted file mode 100644 index a6f67d3..0000000 --- a/src/dorkbox/util/process/ProcessProxy.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util.process; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.CountDownLatch; - -public -class ProcessProxy extends Thread { - - private final InputStream is; - private final OutputStream os; - private final CountDownLatch countDownLatch = new CountDownLatch(1); - - // when reading from the stdin and outputting to the process - public - ProcessProxy(String processName, InputStream inputStreamFromConsole, OutputStream outputStreamToProcess) { - this.is = inputStreamFromConsole; - this.os = outputStreamToProcess; - - setName(processName); - setDaemon(true); - } - - public - void close() { - this.interrupt(); - try { - if (os != null) { - os.flush(); // this goes to the console, so we don't want to close it! - } - this.is.close(); - } catch (IOException ignored) { - } - } - - @Override - public synchronized - void start() { - super.start(); - - // now we have to for it to actually start up. The process can run & complete before this starts, resulting in no input/output - // captured - try { - countDownLatch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - @Override - public - void run() { - final OutputStream os = this.os; - final InputStream is = this.is; - - countDownLatch.countDown(); - - try { - // this thread will read until there is no more data to read. (this is generally what you want) - // the stream will be closed when the process closes it (usually on exit) - int readInt; - - if (os == null) { - // just read so it won't block. - while (is.read() != -1) { - } - } - else { - while ((readInt = is.read()) != -1) { - os.write(readInt); - - - // flush the output on new line. (same for both windows '\r\n' and linux '\n') - if (readInt == '\n') { - os.flush(); - - synchronized (os) { - os.notifyAll(); - } - } - } - } - } catch (Exception ignore) { - } - } -} diff --git a/src/dorkbox/util/process/ShellAsyncExecutor.java b/src/dorkbox/util/process/ShellAsyncExecutor.java deleted file mode 100644 index 76cfcee..0000000 --- a/src/dorkbox/util/process/ShellAsyncExecutor.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util.process; - -public -class ShellAsyncExecutor extends ShellExecutor { - /** - * This is a convenience method to easily create a default process. Will immediately return, and does not wait for the process to finish - * - * @param executableName the name of the executable to run - * @param args the arguments for the executable - * - * @return true if the process ran successfully (exit value was 0), otherwise false - */ - public static - boolean run(String executableName, String... args) { - ShellAsyncExecutor shell = new ShellAsyncExecutor(); - shell.setExecutable(executableName); - shell.addArguments(args); - - return shell.start() == 0; - } - - /** - * This is a convenience method to easily create a default process. Will immediately return, and does not wait for the process to finish - * - * @param executableName the name of the executable to run - * @param args the arguments for the executable - * - * @return true if the process ran successfully (exit value was 0), otherwise false - */ - public static - boolean runShell(String executableName, String... args) { - ShellAsyncExecutor shell = new ShellAsyncExecutor(); - shell.setExecutable(executableName); - shell.addArguments(args); - shell.executeAsShellCommand(); - - return shell.start() == 0; - } - - @Override - public - int start() { - // always have to make sure separate threads are started, otherwise the calling process can hang. - createReadWriterThreads(); - return super.start(false); - } -} diff --git a/src/dorkbox/util/process/ShellExecutor.java b/src/dorkbox/util/process/ShellExecutor.java deleted file mode 100644 index 202660e..0000000 --- a/src/dorkbox/util/process/ShellExecutor.java +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util.process; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import dorkbox.util.OS; - -/** - * If you want to save off the output from the process, set a PrintStream to the following: - *
 {@code
- *
- * ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
- * PrintStream outputStream = new PrintStream(byteArrayOutputStream);
- * ...
- *
- * String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
- * }
- */ -@SuppressWarnings({"UnusedReturnValue", "unused", "ManualArrayToCollectionCopy", "UseBulkOperation", "Convert2Diamond", "Convert2Lambda", - "Anonymous2MethodRef", "WeakerAccess"}) -public -class ShellExecutor { - - // TODO: Add the ability to get the process PID via java for mac/windows/linux. Linux is avail from jvm, windows needs JNA - - private static String defaultShell = null; - - private final PrintStream outputStream; - private final PrintStream outputErrorStream; - private final InputStream inputStream; - - protected List arguments = new ArrayList(); - private Map environment = null; - private String workingDirectory = null; - private String executableName = null; - private String executableDirectory = null; - - private Process process = null; - - private ProcessProxy writeToProcess_input = null; - private ProcessProxy readFromProcess_output = null; - private ProcessProxy readFromProcess_error = null; - - private boolean createReadWriterThreads = false; - - private boolean executeAsShell; - private String pipeToNullString = ""; - private ByteArrayOutputStream byteArrayOutputStream; - - private List fullCommand; - - /** - * This is a convenience method to easily create a default process. Will block until the process is finished running - * - * @param executableName the name of the executable to run - * @param args the arguments for the executable - * - * @return true if the process ran successfully (exit value was 0), otherwise false - */ - public static boolean run(String executableName, String... args) { - ShellExecutor shell = new ShellExecutor(); - shell.setExecutable(executableName); - shell.addArguments(args); - - // blocks until finished - return shell.start() == 0; - } - - /** - * This is a convenience method to easily create a default process. Will immediately return, and does not wait for the process to finish - * - * @param executableName the name of the executable to run - * @param args the arguments for the executable - * - * @return true if the process ran successfully (exit value was 0), otherwise false - */ - public static - boolean runShell(String executableName, String... args) { - ShellExecutor shell = new ShellExecutor(); - shell.setExecutable(executableName); - shell.addArguments(args); - shell.executeAsShellCommand(); - - // blocks until finished - return shell.start() == 0; - } - - /** - * This will cause the spawned process to pipe it's output to a String, so it can be retrieved. - */ - public - ShellExecutor() { - byteArrayOutputStream = new ByteArrayOutputStream(8196); - PrintStream outputStream = new PrintStream(byteArrayOutputStream); - - this.inputStream = null; - this.outputStream = outputStream; - this.outputErrorStream = outputStream; - } - - public - ShellExecutor(final PrintStream out) { - this.inputStream = null; - this.outputStream = out; - this.outputErrorStream = out; - } - - public - ShellExecutor(final InputStream in, final PrintStream out) { - this.inputStream = in; - this.outputStream = out; - this.outputErrorStream = out; - } - - public - ShellExecutor(final InputStream in, final PrintStream out, final PrintStream err) { - this.inputStream = in; - this.outputStream = out; - this.outputErrorStream = err; - } - - /** - * Creates extra reader/writer threads for the sub-process. This is useful depending on how the sub-process is designed to run. - *

- * For a process you want interactive IO with, this is required. - *

- * For a long-running sub-process, with no interactive IO, this is what you'd want. - *

- * For a run-and-get-the-results process, this isn't recommended. - * - */ - public final - ShellExecutor createReadWriterThreads() { - createReadWriterThreads = true; - return this; - } - - /** - * When launched from eclipse, the working directory is USUALLY the root of the project folder - */ - public final - ShellExecutor setWorkingDirectory(final String workingDirectory) { - // MUST be absolute path!! - this.workingDirectory = new File(workingDirectory).getAbsolutePath(); - return this; - } - - /** - * The Shell's execution environment variables. Set to `null` to only use the default environment variables (From what - * {@link System#getenv} returns) - */ - public final - ShellExecutor setEnvironment(final Map environment) { - this.environment = environment; - return this; - } - - public final - ShellExecutor addArgument(final String argument) { - this.arguments.add(argument); - return this; - } - - public final - ShellExecutor addArguments(final String... args) { - for (String path : args) { - this.arguments.add(path); - } - return this; - } - - public final - ShellExecutor addArguments(final List paths) { - this.arguments.addAll(paths); - return this; - } - - public final - ShellExecutor setExecutable(final String executableName) { - this.executableName = executableName; - return this; - } - - public - ShellExecutor setExecutableDirectory(final String executableDirectory) { - // MUST be absolute path!! - this.executableDirectory = new File(executableDirectory).getAbsolutePath(); - return this; - } - - /** - * This will execute as a shell command (bash/cmd/etc) instead of as a forked process. - */ - public - ShellExecutor executeAsShellCommand() { - this.executeAsShell = true; - return this; - } - - - - - /** - * Sends all output data for this process to "null" in a cross platform method - */ - public - ShellExecutor pipeOutputToNull() throws IllegalArgumentException { - if (outputStream != null || outputErrorStream != null) { - throw new IllegalArgumentException("Cannot pipe shell command to 'null' if an output stream is specified"); - } - - if (OS.isWindows()) { - // >NUL on windows - pipeToNullString = ">NUL"; - } - else { - // we will "pipe" it to /dev/null on *nix - pipeToNullString = ">/dev/null 2>&1"; - } - - return this; - } - - /** - * @return the executable command issued to the shell - */ - public - String getCommand() { - StringBuilder execCommand = new StringBuilder(); - - Iterator iterator = fullCommand.iterator(); - while (iterator.hasNext()) { - String s = iterator.next(); - - execCommand.append(s); - - if (iterator.hasNext()) { - execCommand.append(" "); - } - } - - return execCommand.toString(); - } - - public - int start() { - return start(true); - } - - public - int start(final boolean waitForProcesses) { - fullCommand = new ArrayList(); - if (executeAsShell) { - if (OS.isWindows()) { - fullCommand.add("cmd"); - fullCommand.add("/c"); - } - else { - if (defaultShell == null) { - String[] shells = new String[] {"/bin/bash", "/usr/bin/bash", - "/bin/pfbash", "/usr/bin/pfbash", - "/bin/csh", "/usr/bin/csh", - "/bin/pfcsh", "/usr/bin/pfcsh", - "/bin/jsh", "/usr/bin/jsh", - "/bin/ksh", "/usr/bin/ksh", - "/bin/pfksh", "/usr/bin/pfksh", - "/bin/ksh93", "/usr/bin/ksh93", - "/bin/pfksh93", "/usr/bin/pfksh93", - "/bin/pfsh", "/usr/bin/pfsh", - "/bin/tcsh", "/usr/bin/tcsh", - "/bin/pftcsh", "/usr/bin/pftcsh", - "/usr/xpg4/bin/sh", "/usr/xp4/bin/pfsh", - "/bin/zsh", "/usr/bin/zsh", - "/bin/pfzsh", "/usr/bin/pfzsh", - "/bin/sh", "/usr/bin/sh",}; - - for (String shell : shells) { - if (new File(shell).canExecute()) { - defaultShell = shell; - break; - } - } - } - - if (defaultShell == null) { - throw new RuntimeException("Unable to determine the default shell for the linux/unix environment."); - } - - // *nix - fullCommand.add(defaultShell); - fullCommand.add("-c"); - } - - // fullCommand.add(this.executableName); // done elsewhere! - } else { - // shell and working/exe directory are mutually exclusive - if (this.workingDirectory != null) { - if (!this.workingDirectory.endsWith(File.separator)) { - this.workingDirectory += File.separator; - } - } - - if (this.executableDirectory != null) { - if (!this.executableDirectory.endsWith(File.separator)) { - this.executableDirectory += File.separator; - } - - fullCommand.add(0, this.executableDirectory + this.executableName); - } else { - fullCommand.add(this.executableName); - } - } - - - // if we don't want output... - boolean pipeToNull = !pipeToNullString.isEmpty(); - - if (executeAsShell && !OS.isWindows()) { - // when a shell AND on *nix, we have to place ALL the args into a single "arg" that is passed in - final StringBuilder stringBuilder = new StringBuilder(1024); - - stringBuilder.append(this.executableName).append(" "); - - for (String arg : this.arguments) { - stringBuilder.append(arg).append(" "); - } - - if (!arguments.isEmpty()) { - if (pipeToNull) { - stringBuilder.append(pipeToNullString); - } - else { - // delete last " " - stringBuilder.delete(stringBuilder.length() - 1, stringBuilder.length()); - } - } - - fullCommand.add(stringBuilder.toString()); - - } else { - for (String arg : this.arguments) { - if (arg.contains(" ")) { - // individual arguments MUST be in their own element in order to be processed properly - // (this is how it works on the command line!) - String[] split = arg.split(" "); - for (String s : split) { - s = s.trim(); - if (!s.isEmpty()) { - fullCommand.add(s); - } - } - } else { - fullCommand.add(arg); - } - } - - if (pipeToNull) { - fullCommand.add(pipeToNullString); - } - } - - - - ProcessBuilder processBuilder = new ProcessBuilder(fullCommand); - if (this.workingDirectory != null) { - processBuilder.directory(new File(this.workingDirectory)); - } - - - // These env variables are a copy of System.getenv() - Map environment = processBuilder.environment(); - - // Make sure all shell calls are LANG=en_US.UTF-8 THIS CAN BE OVERRIDDEN - if (OS.isMacOsX()) { - // Enable LANG overrides - environment.put("SOFTWARE", ""); - } - - // "export LANG=en_US.UTF-8" - environment.put("LANG", "C"); - - if (this.environment != null) { - for (Map.Entry e : this.environment.entrySet()) { - environment.put(e.getKey(), e.getValue()); - } - } - - // combine these so output is properly piped to null. - if (pipeToNull || this.outputErrorStream == null) { - processBuilder.redirectErrorStream(true); - } - - try { - this.process = processBuilder.start(); - } catch (Exception ex) { - if (outputErrorStream != null) { - this.outputErrorStream.println("There was a problem executing the program. Details:"); - ex.printStackTrace(this.outputErrorStream); - } else { - System.err.println("There was a problem executing the program. Details:"); - ex.printStackTrace(); - } - - if (this.process != null) { - try { - this.process.destroy(); - this.process = null; - } catch (Exception e) { - if (outputErrorStream != null) { - this.outputErrorStream.println("Error destroying process:"); - } else { - System.err.println("Error destroying process:"); - } - e.printStackTrace(this.outputErrorStream); - } - } - } - - if (this.process != null) { - if (this.outputErrorStream == null && this.outputStream == null) { - if (!pipeToNull) { - NullOutputStream nullOutputStream = new NullOutputStream(); - - // readers (read process -> write console) - // have to keep the output buffers from filling in the target process. - readFromProcess_output = new ProcessProxy("Process Reader: " + this.executableName, - this.process.getInputStream(), - nullOutputStream); - } - } - // we want to pipe our input/output from process to ourselves - else { - /* - * Proxy the System.out and System.err from the spawned process back - * to the user's window. This is important or the spawned process could block. - */ - // readers (read process -> write console) - readFromProcess_output = new ProcessProxy("Process Reader: " + this.executableName, - this.process.getInputStream(), - this.outputStream); - - if (this.outputErrorStream != this.outputStream) { - readFromProcess_error = new ProcessProxy("Process Reader: " + this.executableName, - this.process.getErrorStream(), - this.outputErrorStream); - } - } - - if (this.inputStream != null) { - /* - * Proxy System.in from the user's window to the spawned process - */ - // writer (read console -> write process) - writeToProcess_input = new ProcessProxy("Process Writer: " + this.executableName, - this.inputStream, - this.process.getOutputStream()); - } - - - // the process can be killed in two ways - // If not in IDE, by this shutdown hook. (clicking the red square to terminate a process will not run it's shutdown hooks) - // Typing "exit" will always terminate the process - Thread hook = new Thread(new Runnable() { - @Override - public - void run() { - try { - // wait for the READER threads to die (meaning their streams have closed/EOF'd) - if (writeToProcess_input != null) { - // the INPUT (from stdin). It should be via the InputConsole, but if it's in eclipse,etc -- then this doesn't do anything - // We are done reading input, since our program has closed... - writeToProcess_input.close(); - if (createReadWriterThreads) { - writeToProcess_input.join(); - } - } - - readFromProcess_output.close(); - if (createReadWriterThreads) { - readFromProcess_output.join(); - } - - if (readFromProcess_error != null) { - readFromProcess_error.close(); - if (createReadWriterThreads) { - readFromProcess_error.join(); - } - } - } catch (InterruptedException e) { - Thread.currentThread() - .interrupt(); - } - - // forcibly terminate the process when it's streams have closed. - // this is for cleanup ONLY, not to actually do anything. - ShellExecutor.this.process.destroy(); - } - }); - hook.setName("ShellExecutor Shutdown Hook for " + this.executableName); - - // add a shutdown hook to make sure that we properly terminate our spawned processes. - // hook is NOT set to daemon mode, because this is run during shutdown - // add a shutdown hook to make sure that we properly terminate our spawned processes. - try { - Runtime.getRuntime() - .addShutdownHook(hook); - } catch (IllegalStateException ignored) { - // can happen, safe to ignore - } - - if (writeToProcess_input != null) { - if (createReadWriterThreads) { - writeToProcess_input.start(); - } - else { - writeToProcess_input.run(); - } - } - - if (createReadWriterThreads) { - readFromProcess_output.start(); - } - else { - readFromProcess_output.run(); - } - - if (readFromProcess_error != null) { - if (createReadWriterThreads) { - readFromProcess_error.start(); - } - else { - readFromProcess_error.run(); - } - } - - int exitValue = 0; - - if (waitForProcesses) { - try { - this.process.waitFor(); - exitValue = this.process.exitValue(); - hook.run(); - } catch (InterruptedException e) { - Thread.currentThread() - .interrupt(); - } - - // remove the shutdown hook now that we've shutdown. - try { - Runtime.getRuntime().removeShutdownHook(hook); - } catch (IllegalStateException ignored) { - // can happen, safe to ignore - } - } - - return exitValue; - } - - // 1 means a problem - return 1; - } - - /** - * There will never be a trailing newline character at the end of this output. - * - * @return A string representing the output of the process, null if the thread for this was interrupted or the output wasn't saved - */ - public - String getOutput() { - if (byteArrayOutputStream != null) { - return getOutput(byteArrayOutputStream); - } - - return null; - } - - /** - * Converts the baos to a string in a safe way. There will never be a trailing newline character at the end of this output. This will - * block until there is a line of input available. - * - * @return A string representing the output of the process, null if the thread for this was interrupted or the output wasn't saved - */ - public - String getOutputLineBuffered() { - if (byteArrayOutputStream != null) { - return getOutputLineBuffered(byteArrayOutputStream); - } - - return null; - } - - /** - * Converts the baos to a string in a safe way. There will never be a trailing newline character at the end of this output. - * - * @param byteArrayOutputStream the baos that is used in the {@link ShellExecutor#ShellExecutor(PrintStream)} (or similar - * calls) - * - * @return A string representing the output of the process, null if the thread for this was interrupted - */ - public static - String getOutput(final ByteArrayOutputStream byteArrayOutputStream) { - String s; - synchronized (byteArrayOutputStream) { - s = byteArrayOutputStream.toString(); - byteArrayOutputStream.reset(); - } - - // remove trailing newline character(s) - int endIndex = s.lastIndexOf(OS.LINE_SEPARATOR); - if (endIndex > -1) { - return s.substring(0, endIndex); - } - - return s; - } - - /** - * Converts the baos to a string in a safe way. There will never be a trailing newline character at the end of this output. This will - * block until there is a line of input available. - * - * @param byteArrayOutputStream the baos that is used in the {@link ShellExecutor#ShellExecutor(PrintStream)} (or similar - * calls) - * - * @return A string representing the output of the process, null if the thread for this was interrupted - */ - public static - String getOutputLineBuffered(final ByteArrayOutputStream byteArrayOutputStream) { - String s; - synchronized (byteArrayOutputStream) { - try { - byteArrayOutputStream.wait(); - } catch (InterruptedException ignored) { - return null; - } - - s = byteArrayOutputStream.toString(); - byteArrayOutputStream.reset(); - } - - return s; - } -}