JDK-8191490 : Add Reader and Writer access to java.lang.Process
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 17
  • Submitted: 2017-11-17
  • Updated: 2021-06-07
  • Resolved: 2021-06-07
Related Reports
CSR :  
Relates :  
Description
Summary
-------

Add `BufferedReader` to read characters and lines from Process standard output and standard error streams and `BufferedWriter` to write to the Process standard input.

Problem
-------

The current access to the standard streams of processes use `java.io.InputStream` and `java.io.OutputStream`. Typically, applications wrap the streams using `InputStreamReader` and `OutputStreamWriter` to convert the bytes using the system default `Charset`.
The lack of direct access to character streams and/or lines of process input and output requires each developer to reimplement the same code needed to conveniently access the streams.

Solution
--------
Add methods to `Process` that return `BufferedReader` for the process output and error streams and a method to return a `BufferedWriter` for the standard input to the process using the system property `native.encoding` for Charset. Additional methods allow the Charset to be supplied.

BufferedReader provides convenient access to individual lines of output from the process and a fluent interface to utilize `java.util.Stream` functions to process process output as streams of strings.

BufferedWriter provides methods to write characters, arrays of characters and strings to the process using the native.encoding to encode characters to bytes.

The system property `native.encoding` is used as the default value for the Charset for the methods that do not provide the Charset explicitly.

I/O errors that occur on the underlying byte streams throw IOException and can be handled by the caller of the BufferedReader and BufferedWriters.

Specification
-------------
Add a new intro sentence in the class javadoc after the mention of the get*methods.

```
 * The I/O streams of characters and lines can be written and read using the methods
 * {@link #outputWriter()}, {@link #outputWriter(Charset)}},
 * {@link #inputReader()}, {@link #inputReader(Charset)},
 * {@link #errorReader()}, and {@link #errorReader(Charset)}.
```

New methods in `java.lang.Process`:

```
    /**
     * Returns a {@link BufferedReader BufferedReader} connected to the standard
     * output of the process. The {@link Charset} for the native encoding is used
     * to read characters, lines, or stream lines from standard output.
     *
     * <p>This method delegates to {@link #inputReader(Charset)} using the
     * {@link Charset} named by the {@code native.encoding} system property.
     * If the {@code native.encoding} is not a valid charset name or not supported
     * the {@link Charset#defaultCharset()} is used.
     *
     * @return a {@link BufferedReader BufferedReader} using the
     *          {@code native.encoding} if supported, otherwise, the
     *          {@link Charset#defaultCharset()}
     * @since 17
     */
    public final BufferedReader inputReader() {...}

    /**
     * Returns a {@link BufferedReader BufferedReader} connected to the
     * standard output of this process using a Charset.
     * The {@code BufferedReader} can be used to read characters, lines,
     * or stream lines of the standard output.
     *
     * <p>Characters are read by an InputStreamReader that reads and decodes bytes
     * from this process {@link #getInputStream()}. Bytes are decoded to characters
     * using the {@code charset}; malformed-input and unmappable-character
     * sequences are replaced with the charset's default replacement.
     * The {@code BufferedReader} reads and buffers characters from the InputStreamReader.
     *
     * <p>The first call to this method creates the {@link BufferedReader BufferedReader},
     * if called again with the same {@code charset} the same {@code BufferedReader} is returned.
     * It is an error to call this method again with a different {@code charset}.
     *
     * <p>If the standard output of the process has been redirected using
     * {@link ProcessBuilder#redirectOutput(Redirect) ProcessBuilder.redirectOutput}
     * then the {@code InputStreamReader} will be reading from a
     * <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
     *
     * <p>Otherwise, if the standard error of the process has been redirected using
     * {@link ProcessBuilder#redirectErrorStream(boolean)
     * ProcessBuilder.redirectErrorStream} then the input reader returned by
     * this method will receive the merged standard output and the standard error
     * of the process.
     *
     * @apiNote
     * Using both {@link #getInputStream} and {@link #inputReader(Charset)} has
     * unpredictable behavior since the buffered reader reads ahead from the
     * input stream.
     *
     * <p>When the process has terminated, and the standard input has not been redirected,
     * reading of the bytes available from the underlying stream is on a best effort basis and
     * may be unpredictable.
     *
     * @param charset the {@code Charset} used to decode bytes to characters
     * @return a {@code BufferedReader} for the standard output of the process using the {@code charset}
     * @throws NullPointerException if the {@code charset} is {@code null}
     * @throws IllegalStateException if called more than once with different charset arguments
     * @since 17
     */
    public final BufferedReader inputReader(Charset charset) {...}

    /**
     * Returns a {@link BufferedReader BufferedReader} connected to the standard
     * error of the process. The {@link Charset} for the native encoding is used
     * to read characters, lines, or stream lines from standard error.
     *
     * <p>This method delegates to {@link #errorReader(Charset)} using the
     * {@link Charset} named by the {@code native.encoding} system property.
     * If the {@code native.encoding} is not a valid charset name or not supported
     * the {@link Charset#defaultCharset()} is used.
     *
     * @return a {@link BufferedReader BufferedReader} using the
     *          {@code native.encoding} if supported, otherwise, the
     *          {@link Charset#defaultCharset()}
     * @since 17
     */
    public final BufferedReader errorReader() {...}

    /**
     * Returns a {@link BufferedReader BufferedReader} connected to the
     * standard error of this process using a Charset.
     * The {@code BufferedReader} can be used to read characters, lines,
     * or stream lines of the standard error.
     *
     * <p>Characters are read by an InputStreamReader that reads and decodes bytes
     * from this process {@link #getErrorStream()}. Bytes are decoded to characters
     * using the {@code charset}; malformed-input and unmappable-character
     * sequences are replaced with the charset's default replacement.
     * The {@code BufferedReader} reads and buffers characters from the InputStreamReader.
     *
     * <p>The first call to this method creates the {@link BufferedReader BufferedReader},
     * if called again with the same {@code charset} the same {@code BufferedReader} is returned.
     * It is an error to call this method again with a different {@code charset}.
     *
     * <p>If the standard error of the process has been redirected using
     * {@link ProcessBuilder#redirectError(Redirect) ProcessBuilder.redirectError} or
     * {@link ProcessBuilder#redirectErrorStream(boolean) ProcessBuilder.redirectErrorStream}
     * then the {@code InputStreamReader} will be reading from a
     * <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
     *
     * @apiNote
     * Using both {@link #getErrorStream} and {@link #errorReader(Charset)} has
     * unpredictable behavior since the buffered reader reads ahead from the
     * error stream.
     *
     * <p>When the process has terminated, and the standard error has not been redirected,
     * reading of the bytes available from the underlying stream is on a best effort basis and
     * may be unpredictable.
     *
     * @param charset the {@code Charset} used to decode bytes to characters
     * @return a {@code BufferedReader} for the standard error of the process using the {@code charset}
     * @throws NullPointerException if the {@code charset} is {@code null}
     * @throws IllegalStateException if called more than once with different charset arguments
     * @since 17
     */
    public final BufferedReader errorReader(Charset charset) {...}

    /**
     * Returns a {@code BufferedWriter} connected to the normal input of the process
     * using the native encoding.
     * Writes text to a character-output stream, buffering characters so as to provide
     * for the efficient writing of single characters, arrays, and strings.
     *
     * <p>This method delegates to {@link #outputWriter(Charset)} using the
     * {@link Charset} named by the {@code native.encoding} system property.
     * If the {@code native.encoding} is not a valid charset name or not supported
     * the {@link Charset#defaultCharset()} is used.
     *
     * @return a {@code BufferedWriter} to the standard input of the process using the charset
     *          for the {@code native.encoding} system property
     * @since 17
     */
    public final BufferedWriter outputWriter() {...}

    /**
     * Returns a {@code BufferedWriter} connected to the normal input of the process
     * using a Charset.
     * Writes text to a character-output stream, buffering characters so as to provide
     * for the efficient writing of single characters, arrays, and strings.
     *
     * <p>Characters written by the writer are encoded to bytes using {@link OutputStreamWriter}
     * and the {@link Charset} are written to the standard input of the process represented
     * by this {@code Process}.
     * Malformed-input and unmappable-character sequences are replaced with the charset's
     * default replacement.
     *
     * <p>The first call to this method creates the {@link BufferedWriter BufferedWriter},
     * if called again with the same {@code charset} the same {@code BufferedWriter} is returned.
     * It is an error to call this method again with a different {@code charset}.
     *
     * <p>If the standard input of the process has been redirected using
     * {@link ProcessBuilder#redirectInput(Redirect)
     * ProcessBuilder.redirectInput} then the {@code OutputStreamWriter} writes to a
     * <a href="ProcessBuilder.html#redirect-input">null output stream</a>.
     *
     * @apiNote
     * A {@linkplain BufferedWriter} writes characters, arrays of characters, and strings.
     * Wrapping the {@link BufferedWriter} with a {@link PrintWriter} provides
     * efficient buffering and formatting of primitives and objects as well as support
     * for auto-flush on line endings.
     * Call the {@link BufferedWriter#flush()} method to flush buffered output to the process.
     * <p>
     * When writing to both {@link #getOutputStream()} and either {@link #outputWriter()}
     * or {@link #outputWriter(Charset)}, {@linkplain BufferedWriter#flush BufferedWriter.flush}
     * should be called before writes to the {@code OutputStream}.
     *
     * @param charset the {@code Charset} to encode characters to bytes
     * @return a {@code BufferedWriter} to the standard input of the process using the {@code charset}
     * @throws NullPointerException if the {@code charset} is {@code null}
     * @throws IllegalStateException if called more than once with different charset arguments
     * @since 17
     */
    public final BufferedWriter outputWriter(Charset charset) {...}

```
Comments
Moving to Approved after corpus can.
07-06-2021

A corpus scan of 144+k Maven artifacts found 60 that include classes that extend java.lang.Process. None of the subclasses defined methods named inputReader, errorReader, or outputWriter. The Maven repository represents a large number of open source projects and does not include any proprietary artifacts.
06-06-2021

Moving back to Provisional, not Approved. Given that process is a long-standing subclassable class, adding new final methods to it would be source and binary incompatible with any existing subclasses that happened to have these methods already. Please conduct a search to see if any such methods are known to exist in extant subclasss.
04-06-2021

It's a bit annoying that Process has a public constructor and there may be a case for deprecating this constructor in the future. So it is possible that there is a Process implementations outside of the JDK but even so, I think it should be low risk to add final methods to this class.
02-06-2021

Implementation review comments are addressed in javadoc spec updates: - warnings about using both readers and input streams, and writers and output streams. - notes about process termination - clarification about use of system property native.encoding - requesting a reader or writer with a different charset throwing IllegalStateException intead of IAE. - the new methods are final, instead of non-final and specified with @ implspec
02-06-2021

Though Process is abstract, many methods are not final, leading to a loser and unpredictable implementation. My preference would be to make the methods final instead. The reader/writer behavior would be more predictable.
26-05-2021

I think the new method would be better as "default"-style method that implSpec-ed what they did. Moving to Provisional.
26-05-2021

PrintStream does also and it is used frequently for Stdout and Stderr. OutputStreamWriter would be a better choice with that in mind. It does not have the convenience methods for converting various types to strings but would not hide the exceptions.
20-05-2021

PrintWriter shallows IOExceptions, it doesn't seem a good idea to use it in this particular case.
20-05-2021

I've changed the fixVersion to tbd_major temporarily as I think it will require further discussion before seeing if this is the right thing to do.
19-11-2017