JDK-5074560 : (process) Provide an easy way to capture external process output
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2004-07-15
  • Updated: 2009-04-28
  • Resolved: 2009-04-28
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7Resolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
Scripting languages like Bourne shell and perl have very simple constructs
to run external commands and capture their output.
For example, in Bourne shell you can run a command simply like this
  jar cf foo.jar foo.class
and you can capture the output of a command simply like this:
  output=`jar tf foo.jar`

Java has all the infrastructure needed for an application programmer to
do the same thing, but it is surprisingly difficult, especially if one
wants results to be reliable and never hang.

The reason for the difficulty is that the stdout and the stderr of a 
Java Process is connected to the parent process via operating system
pipes.  These have fixed size buffers, rather like ArrayBlockingQueue.
If the producer of data writes enough data to fill a buffer, it will
block until the consumer drains some of the data out of the buffer.
If the data is not drained by the consumer, then the producer will
block if and only if the data exceeds the size of the buffer.

As an example, in the simple shell command
  cat file | sleep 10000000
whether the cat command will exit quickly depends on the size of the file *and*
the system-dependent size of the pipe buffer.

The only safe way to capture the output of a command from Java is
to start up two threads, one to read the stderr, and one to read the stdout,
and then to join these two threads.  To force a user to confront 
a difficult concurrent programming problem when the user is thinking that
the problem is serial is very bad.  Even an enlightened programmer who
knows better might be tempted to write an unreliable expression like
  new ProcessBuilder(cmd).start().waitFor();
to run a command that is "known" to never create any output, even when this
is not completely safe, to save the 50 lines of code required to do this
safely.

The core libraries should provide a way.  One possible way is

ProcessResults res = new ProcessBuilder(cmd).run();
String stdout = res.stdout();
String stderr = res.stderr();

Here is some sample code from the regression test for ProcessBuilder:

    private static class StreamAccumulator extends Thread {
	private final InputStream is;
	private final StringBuilder sb = new StringBuilder();
	private Throwable throwable = null;

	public String result () throws Throwable {
	    if (throwable != null)
		throw throwable;
	    return sb.toString();
	}

	StreamAccumulator (InputStream is) {
	    this.is = is;
	}

	public void run() {
	    try {
		Reader r = new InputStreamReader(is);
		char[] buf = new char[4096];
		int n;
		while ((n = r.read(buf)) > 0) {
		    sb.append(buf,0,n);
		}
	    } catch (Throwable t) {
		throwable = t;
	    }
	}
    }

    private static ProcessResults run(Process p) {
	Throwable throwable = null;
	int exitValue = -1;
	String out = "";
	String err = "";

	StreamAccumulator outAccumulator =
	    new StreamAccumulator(p.getInputStream());
	StreamAccumulator errAccumulator =
	    new StreamAccumulator(p.getErrorStream());

	try {
	    outAccumulator.start();
	    errAccumulator.start();

	    exitValue = p.waitFor();

	    outAccumulator.join();
	    errAccumulator.join();

	    out = outAccumulator.result();
	    err = errAccumulator.result();
	} catch (Throwable t) {
	    throwable = t;
	}

	return new ProcessResults(out, err, exitValue, throwable);
    }

    //----------------------------------------------------------------
    // Results of a command
    //----------------------------------------------------------------
    private static class ProcessResults {
	private final String out;
	private final String err;
	private final int exitValue;
	private final Throwable throwable;

	public ProcessResults(String out,
			      String err,
			      int exitValue,
			      Throwable throwable) {
	    this.out = out;
	    this.err = err;
	    this.exitValue = exitValue;
	    this.throwable = throwable;
	}

	public String out()          { return out; }
	public String err()          { return err; }
	public int exitValue()       { return exitValue; }
	public Throwable throwable() { return throwable; }

	public String toString() {
	    StringBuilder sb = new StringBuilder();
	    sb.append("<STDOUT>\n" + out() + "</STDOUT>\n")
		.append("<STDERR>\n" + err() + "</STDERR>\n")
		.append("exitValue = " + exitValue + "\n");
	    if (throwable != null)
		sb.append(throwable.getStackTrace());
	    return sb.toString();
	}
    }


###@###.### 2004-07-15

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mustang
15-08-2004

PUBLIC COMMENTS -
15-08-2004

EVALUATION A fine idea! ###@###.### 2004-07-15
15-07-2004