From: <jrb...@us...> - 2012-01-10 23:51:26
|
Revision: 1300 http://cishell.svn.sourceforge.net/cishell/?rev=1300&view=rev Author: jrbibers Date: 2012-01-10 23:51:20 +0000 (Tue, 10 Jan 2012) Log Message: ----------- New "process" subpackage for CIShell utilities. Three new classes for working with java.lang.Process. Core functionality is (1) emptying the standard output and standard error streams as a running process fills them since the process would otherwise block indefinitely and (2) capturing the exit value and standard output and standard error messages of the completed process as a ProcessResult. Manifest updated to export this new package. Reviewed by David. Modified Paths: -------------- trunk/core/org.cishell.utilities/META-INF/MANIFEST.MF Added Paths: ----------- trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/ trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/OutputGobblingProcessRunner.java trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/ProcessResult.java trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/StreamGobbler.java Modified: trunk/core/org.cishell.utilities/META-INF/MANIFEST.MF =================================================================== --- trunk/core/org.cishell.utilities/META-INF/MANIFEST.MF 2012-01-09 16:19:32 UTC (rev 1299) +++ trunk/core/org.cishell.utilities/META-INF/MANIFEST.MF 2012-01-10 23:51:20 UTC (rev 1300) @@ -36,4 +36,5 @@ org.cishell.utilities.mutateParameter, org.cishell.utilities.mutateParameter.defaultvalue, org.cishell.utilities.mutateParameter.dropdown, - org.cishell.utilities.osgi.logging + org.cishell.utilities.osgi.logging, + org.cishell.utilities.process Added: trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/OutputGobblingProcessRunner.java =================================================================== --- trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/OutputGobblingProcessRunner.java (rev 0) +++ trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/OutputGobblingProcessRunner.java 2012-01-10 23:51:20 UTC (rev 1300) @@ -0,0 +1,64 @@ +package org.cishell.utilities.process; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.google.common.base.Objects; + +/** + * {@link Process#waitFor()} may wait indefinitely if the running process's stdout and stderr + * streams are not emptied as they fill. + * + * See + * <a href="http://stackoverflow.com/questions/2150723/process-waitfor-threads-and-inputstreams">here</a> + * or + * <a href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html">here</a> for details. + * + */ +public class OutputGobblingProcessRunner { + private ProcessBuilder processBuilder; + private String charsetName; + + public OutputGobblingProcessRunner(ProcessBuilder processBuilder, String charsetName) { + this.processBuilder = processBuilder; + this.charsetName = charsetName; + } + + public ProcessResult run() throws IOException, InterruptedException { + // Start the process + Process process = processBuilder.start(); + + // Set up and start the stdout and stderr gobblers + ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream(); + StreamGobbler stdoutGobbler = new StreamGobbler(process.getInputStream(), stdoutStream); + + ByteArrayOutputStream stderrStream = new ByteArrayOutputStream(); + StreamGobbler stderrGobbler = new StreamGobbler(process.getErrorStream(), stderrStream); + + stdoutGobbler.start(); + stderrGobbler.start(); + + // Wait for the process to finish + int exitValue = process.waitFor(); + + // Interrupt the gobblers and wait for both to die + stdoutGobbler.interrupt(); + stderrGobbler.interrupt(); + stdoutGobbler.join(); + stderrGobbler.join(); + + // Dump gobbler messages + String stdoutMessage = stdoutStream.toString(charsetName).trim(); + String stderrMessage = stderrStream.toString(charsetName).trim(); + + return new ProcessResult(exitValue, stdoutMessage, stderrMessage); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("processBuilder", processBuilder) + .add("charsetName", charsetName) + .toString(); + } +} Added: trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/ProcessResult.java =================================================================== --- trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/ProcessResult.java (rev 0) +++ trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/ProcessResult.java 2012-01-10 23:51:20 UTC (rev 1300) @@ -0,0 +1,98 @@ +package org.cishell.utilities.process; + +import com.google.common.base.Joiner; +import com.google.common.base.Objects; + +/** + * Value class for the result of {@link OutputGobblingProcessRunner#run()}. + * Represents a process's exit value and messages to standard out and standard error, + * both as a single String. + */ +public class ProcessResult { + private int exitValue; + private String stdoutMessage; + private String stderrMessage; + + /** + * @param exitValue A completed {@link Process}'s exit value. + * @param stdoutMessage A completed {@link Process}'s messages to standard output as one String. + * @param stderrMessage A completed {@link Process}'s messages to standard error as one String. + */ + public ProcessResult(int exitValue, String stdoutMessage, String stderrMessage) { + this.exitValue = exitValue; + this.stdoutMessage = stdoutMessage; + this.stderrMessage = stderrMessage; + } + + public int getExitValue() { + return exitValue; + } + + /** + * @return The message to standard output. + */ + public String getStdoutMessage() { + return stdoutMessage; + } + + /** + * @return The message to standard error. + */ + public String getStderrMessage() { + return stderrMessage; + } + + /** + * @return True if and only if the exit value is zero. + */ + public boolean isExitNormal() { + return (exitValue == 0); + } + + /** + * @return A plain-text report of the exit value and standard output/error messages. + */ + public String report() { + return Joiner.on(" ").join( + String.format("The program returned exit value %d.", exitValue), + reportStreamContents("standard output", stdoutMessage), + reportStreamContents("standard error", stderrMessage)); + } + + private static String reportStreamContents(String streamName, String contents) { + if (contents.isEmpty()) { + return String.format("No messages to %s.", streamName); + } else { + return String.format("Message to %s: [[%s]].", streamName, contents); + } + } + + @Override + public boolean equals(Object thatObject) { + if (!(thatObject instanceof ProcessResult)) { + return false; + } + + ProcessResult that = (ProcessResult) thatObject; + + return (Objects.equal(this.exitValue, + that.exitValue) + && Objects.equal(this.stdoutMessage, + that.stdoutMessage) + && Objects.equal(this.stderrMessage, + that.stderrMessage)); + } + @Override + public int hashCode() { + return Objects.hashCode(exitValue, stdoutMessage, stderrMessage); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("exitValue", exitValue) + .add("stdoutMessage", stdoutMessage) + .add("stderrMessage", stderrMessage) + .toString(); + } +} \ No newline at end of file Added: trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/StreamGobbler.java =================================================================== --- trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/StreamGobbler.java (rev 0) +++ trunk/core/org.cishell.utilities/src/org/cishell/utilities/process/StreamGobbler.java 2012-01-10 23:51:20 UTC (rev 1300) @@ -0,0 +1,60 @@ +package org.cishell.utilities.process; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; + +import com.google.common.base.Objects; + +/** + * Running this thread empties {@link #inputStream} into {@link #outputStream}. + * <p/> + * Adapted from + * <a href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4">this guide</a>. + */ +public class StreamGobbler extends Thread { + private InputStream inputStream; + private OutputStream outputStream; + + /** + * @param inputStream Stream for reading. + * @param outputStream Stream for writing. + */ + public StreamGobbler(InputStream inputStream, OutputStream outputStream) { + this.inputStream = inputStream; + this.outputStream = outputStream; + } + + /** + * Empties this gobbler's {@link InputStream} into its {@link OutputStream}. + */ + @Override + public void run() { + try { + PrintWriter printWriter = new PrintWriter(outputStream); + + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + String line = null; + while ((line = bufferedReader.readLine()) != null) { + printWriter.println(line); + } + + printWriter.flush(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Problem reading messages from process.", e); + } + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("inputStream", inputStream) + .add("outputStream", outputStream) + .toString(); + } +} \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |