Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-5074560

(process) Provide an easy way to capture external process output

    XMLWordPrintable

    Details

    • Type: Enhancement
    • Status: Closed
    • Priority: P3
    • Resolution: Duplicate
    • Affects Version/s: 6
    • Fix Version/s: 7
    • Component/s: core-libs
    • Labels:
    • Subcomponent:
    • CPU:
      generic
    • OS:
      generic

      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

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              Unassigned Unassigned
              Reporter:
              martin Martin Buchholz
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved:
                Imported:
                Indexed: