diff --git a/Dorkbox-Util/src/dorkbox/util/process/NullOutputStream.java b/Dorkbox-Util/src/dorkbox/util/process/NullOutputStream.java deleted file mode 100644 index 0a04de3..0000000 --- a/Dorkbox-Util/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/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java deleted file mode 100644 index 76e8032..0000000 --- a/Dorkbox-Util/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 e) { - } - } - - @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.notify(); - } - } - } - } - } catch (Exception ignore) { - } - } -} diff --git a/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java b/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java deleted file mode 100644 index 36bff7f..0000000 --- a/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java +++ /dev/null @@ -1,499 +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 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);
- * }
- */ -public -class ShellProcessBuilder { - - // TODO: Add the ability to get the process PID via java for mac/windows/linux. Linux is avail from jvm, windows needs JNA - - private final PrintStream outputStream; - private final PrintStream outputErrorStream; - private final InputStream inputStream; - - protected List arguments = new ArrayList(); - 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 isShell; - private String pipeToNullString = ""; - private List fullCommand; - - /** - * This will cause the spawned process to pipe it's output to null. - */ - public - ShellProcessBuilder() { - this(null, null, null); - } - - public - ShellProcessBuilder(final PrintStream out) { - this(null, out, out); - } - - public - ShellProcessBuilder(final InputStream in, final PrintStream out) { - this(in, out, out); - } - - public - ShellProcessBuilder(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 - ShellProcessBuilder createReadWriterThreads() { - createReadWriterThreads = true; - return this; - } - - /** - * When launched from eclipse, the working directory is USUALLY the root of the project folder - */ - public final - ShellProcessBuilder setWorkingDirectory(final String workingDirectory) { - // MUST be absolute path!! - this.workingDirectory = new File(workingDirectory).getAbsolutePath(); - return this; - } - - public final - ShellProcessBuilder addArgument(final String argument) { - this.arguments.add(argument); - return this; - } - - public final - ShellProcessBuilder addArguments(final String... paths) { - for (String path : paths) { - this.arguments.add(path); - } - return this; - } - - public final - ShellProcessBuilder addArguments(final List paths) { - this.arguments.addAll(paths); - return this; - } - - public final - ShellProcessBuilder setExecutable(final String executableName) { - this.executableName = executableName; - return this; - } - - public - ShellProcessBuilder setExecutableDirectory(final String executableDirectory) { - // MUST be absolute path!! - this.executableDirectory = new File(executableDirectory).getAbsolutePath(); - return this; - } - - /** - * Sends all output data for this process to "null" in a cross platform method - */ - public - ShellProcessBuilder 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() { - fullCommand = new ArrayList(); - - // if no executable, then use the command shell - if (this.executableName == null) { - isShell = true; - - if (OS.isWindows()) { - // windows - this.executableName = "cmd"; - - fullCommand.add(this.executableName); - fullCommand.add("/c"); - } - else { - // *nix - this.executableName = "/bin/bash"; - - File file = new File(this.executableName); - if (!file.canExecute()) { - this.executableName = "/bin/sh"; - } - - fullCommand.add(this.executableName); - fullCommand.add("-c"); - } - } - 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 (isShell && !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); - - 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) { - 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)); - } - - // 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:"); - } else { - System.err.println("There was a problem executing the program. Details:"); - } - ex.printStackTrace(this.outputErrorStream); - - 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() { - ShellProcessBuilder.this.process.destroy(); - } - }); - hook.setName("ShellProcess Shutdown Hook"); - - // 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; - - try { - this.process.waitFor(); - - exitValue = this.process.exitValue(); - - // 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(); - writeToProcess_input.join(); - } - readFromProcess_output.close(); - readFromProcess_output.join(); - if (readFromProcess_error != null) { - readFromProcess_error.close(); - readFromProcess_error.join(); - } - - // forcibly terminate the process when it's streams have closed. - // this is for cleanup ONLY, not to actually do anything. - this.process.destroy(); - } 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; - } - - /** - * 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 ShellProcessBuilder#ShellProcessBuilder(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 ShellProcessBuilder#ShellProcessBuilder(PrintStream)} (or similar - * calls) - * - * @return A string representing the output of the process, null if the thread for this was interrupted - */ - public String - getOutputLineBuffered(final ByteArrayOutputStream byteArrayOutputStream) { - String s; - - synchronized (byteArrayOutputStream) { - try { - byteArrayOutputStream.wait(); - } catch (InterruptedException ignored) { - return null; - } - - s = byteArrayOutputStream.toString(); - byteArrayOutputStream.reset(); - } - - return s; - } -}