ProcessStream.java
/*
** Module : ProcessStream.java
** Abstract : represents a child process' stdio input or output pipes
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 GES 20050718 @21744 Created initial version which provides
** input and output semantics for the STDIO
** pipes (either input or both output pipes
** combined) of a child process.
** 002 GES 20050801 @22247 New assign() interface for support of shared
** streams.
** 003 GES 20050824 @22213 Minor addition to make multiple calls to
** close() more safe.
** 004 GES 20060207 @24327 Translate InterruptedException into a
** StopConditionException.
** 005 GES 20060217 @24672 Added writeByte().
** 006 GES 20060225 @24740 Added call to cleanup() in close();
** 007 ECF 20060330 @25260 Throw EOFException from readLn() when no data
** was read in the read loop.
** 008 GES 20060518 @26344 Modifications to bypass J2SE process
** launching to match requirements in
** ProcessDaemon.
** 009 GES 20060811 @28550 Resolve case where output after the last
** newline was never seen in the child process
** because the STDIN of a pty or tty is line
** buffered. An explicit send of an EOF
** character just before we close the stream
** resolves the problem because it notifies
** the child process to flush the stream without
** adding any additional real data that will be
** seen in the output.
** 010 GES 20060817 @28661 Removed H009 EOF processing because a more
** reliable approach to pty configuration has
** been taken. In particular, a full RAW
** (non-canonical) mode has been implemented.
** 011 EVL 20070609 @34005 Renaming reserved in Java 6 enum identifier
** to be able to compile for Java 6
** 012 NVS 20070725 @34688 Implemented methods isIn() and isOut(), which
** are newly added to the base class as abstract.
** 013 SIY 20070928 @35316 Fix for PUT/DISPLAY handling regressions.
** Ref: #35314
** 014 GES 20080412 @38019 Support standard character conversion (just
** the default behavior, no user-definable
** source and/or target codepages yet). This
** allows other languages (and the upper ASCII
** range in the latin codepages) to be properly
** handled. Added support for the no-convert
** option as well, to disable this new support.
** 015 SVL 20090612 @42670 If an output error has occurred, any further
** output will be silently suppressed until the
** buffer size limit will be reached.
** 016 SIY 20090622 @42775 Added method to trigger broken mode externally.
** 017 SVL 20090707 @43085 Some refactoring due to changes in the
** BufferSizeManager.
** 018 SVL 20110403 Added write(byte[]) function to ProcessStream,
** IOHelper, InReader, InStream, OutWriter
** and OutStream.
** 019 GES 20111006 Use launch manager directly instead of thin client.
** 020 EVL 20130425 Modifying Waiter class to have only one instance if we have to wait
** for process with both redirected input and output. The example is
** INPUT-OUTPUT THROUGH implementation. The method alreadyInWait() was
** added to check for another waiting for given pid.
** 021 CA 20140805 Removed ThinClient import.
** 022 OM 20170622 Implemented peekCh() method.
** 023 ECF 20171026 Added write(byte[], int, int) method.
** 024 EVL 20220325 Base refactoring for Stream based classes getting single byte and array of bytes.
** 025 TT 20230804 Added readByteWorker().
** 026 ES 20250424 Handle StopConditionException through ErrorManager.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
/**
* A stream class supporting input and output semantics for the
* <code>STDIO</code> pipes of a child process. This class can be used for
* either the input pipe (<code>STDIN</code>) or for both output pipes
* (<code>STDOUT and STDERR</code>) combined.
* <p>
* Due to the (Progress-driven) requirement that a single read return
* any available data from both the standard output pipes, both pipes are
* checked and read in series. The <code>STDOUT</code> pipe is always
* checked before <code>STDERR</code>. This artificially makes the two
* pipes appear as a single stream, however the approach is inherently
* limited. If an entire line needs to be read it is critical that the
* {@link #readLn} is used, since this will be guaranteed to read the whole
* line from a single stream. The {@link #readCh} will always read from
* <code>STDOUT</code> as long as there are bytes available. However this
* means that there is a natural race condition with <code>STDERR</code>.
* If at one moment <code>STDOUT</code> has no bytes available for reading,
* then a read will occur from <code>STDERR</code>. If bytes arrive on
* <code>STDOUT</code> then the very next call to <code>readCh</code> will
* first read from <code>STDOUT</code> even if there are bytes that arrived
* previously on <code>STDERR</code>. This may make the output appear in
* a different order than expected.
* <p>
* A polling approach is used for all reading to implement the semantic of
* combining the two pipes.
* <p>
* This class does not support getting/setting the length or read/write
* position of this stream.
*/
public class ProcessStream
extends Stream
{
/** Standard message for unsupported operations. */
private static final String UNSUPPORTED = "Not allowed in process STDIO.";
/** Determines if there is any explicit charset to honor. */
private String cset = Utils.getCharsetOverride();
/** The <code>STDOUT</code> from the child process. */
private IOHelper out = null;
/** The <code>STDERR</code> from the child process. */
private IOHelper err = null;
/** The <code>STDIN</code> from the child process. */
private IOHelper in = null;
/** The child process babysitter. */
private Waiter child = null;
/**
* Determines whether an output error has occurred. If true, any further
* output will be silently suppressed.
*/
private boolean broken = false;
/**
* Exception that was raised when the stream become broken.
*/
private IOException storedException = null;
/**
* Default constructor which creates an instance that implements the
* standard character conversion.
*/
public ProcessStream()
{
// nothing to do
}
/**
* Connects the <code>STDIN</code> of the child process to this stream.
*
* @param pid
* The ID of the child process that is reading our input.
* @param in
* The child process' <code>STDIN</code> stream.
*/
public void connectIn(int pid, OutputStream in)
{
startWaiter(pid);
this.in = convert ? new OutWriter(in) : new OutStream(in);
}
/**
* Connects the <code>STDOUT and STDERR</code> of the child process to
* this stream.
*
* @param pid
* The ID of the child process that is generating output.
* @param out
* The child process' <code>STDOUT</code> stream.
* @param err
* The child process' <code>STDERR</code> stream.
*/
public void connectOut(int pid, InputStream out, InputStream err)
{
startWaiter(pid);
this.out = convert ? new InReader(out) : new InStream(out);
this.err = convert ? new InReader(err) : new InStream(err);
}
/**
* Mark stream as broken and it will silently ignore I/O errors.
*/
public void setBroken()
{
// also set special error mode
makeBroken(new IOException());
}
/**
* The number of bytes available to be immediately read without blocking.
*
* @return The number of available bytes.
*
* @throws IOException
*/
public long available()
throws IOException
{
int onum = out.available();
return onum > 0 ? onum : err.available();
}
/**
* The 0-based offset into the stream at which the next read or write will
* occur. This does not work for "streams" that require sequential
* access.
*
* @return The current position in the stream.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public long getPos()
throws UnsupportedOperationException,
IOException
{
throw new UnsupportedOperationException(UNSUPPORTED);
}
/**
* Moves the current read/write position to the specified absolute 0-based
* offset.
*
* @param pos
* The new read/write position in the stream.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public void setPos(long pos)
throws UnsupportedOperationException,
IOException
{
throw new UnsupportedOperationException(UNSUPPORTED);
}
/**
* The length of the stream in bytes. This does not work for "streams"
* that require sequential access.
*
* @return The length of the stream.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public long getLen()
throws UnsupportedOperationException,
IOException
{
throw new UnsupportedOperationException(UNSUPPORTED);
}
/**
* Truncates or extends the stream to the specified length if this stream
* supports such an operation. If truncation is requested, all data
* located after this point in the file is discarded. If extending the
* file is requested, the values of the data in the extended portion of
* the file is undefined.
*
* @param len
* The new length of the file.
*
* @throws UnsupportedOperationException
* @throws IOException
*/
public void setLen(long len)
throws UnsupportedOperationException,
IOException
{
throw new UnsupportedOperationException(UNSUPPORTED);
}
/**
* Write the given character to the output stream.
*
* @param ch
* The character to be written.
*
* @throws IOException
*/
public void writeCh(char ch)
throws IOException
{
in.writeCh(ch);
}
/**
* Write the given byte to the output stream.
*
* @param b
* The byte to be written.
*
* @throws IOException
*/
public void writeByte(byte b)
throws IOException
{
writeCh((char) b);
}
/**
* Write the given string to the output stream.
*
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(String data)
throws IOException
{
in.write(data);
}
/**
* Write the given byte array to the output stream.
*
* @param data
* The data to be written.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data)
throws IOException
{
in.write(data);
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <= (data.length
* - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException
{
in.write(data, off, len);
}
/**
* Peeks at the character from the current read position in the stream (reads a character from
* the current read position in the stream without incrementing stream read position. The next
* {@code peekCh()} and {@code readCh()} will return the same value).
* <p>
* The underlying stream subclass determines the content of the result. Byte oriented streams
* such as pipes or files will return a byte while streams that generate keystrokes or
* characters may return a DBCS or Unicode character.
*
* @return The next character read from the stream, -1 on any failure and -2 upon an
* {@code EOF}.
*
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
public int peekCh()
{
return -1; // TODO: implement some caching of the read value (?)
}
/**
* Read the raw integer value of a character from the current read
* position in the stream. This method DOES NOT convert the value read
* from the stream's character set into Unicode.
* <p>
* At this time, only a single byte will be read no matter what encoding
* is used by the underlying stream. This means that DBCS or Unicode
* encodings will not be properly handled.
*
* @return The next character read from the stream, -1 on any failure
* and -2 upon an <code>EOF</code>.
*
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
@Override
public int readCh()
throws StopConditionException
{
return readChWorker(convert);
}
/**
* Read the raw integer value of a character from the current read
* position in the stream. This method DOES NOT convert the value read
* from the stream's character set into Unicode.
* <p>
* At this time, only a single byte will be read no matter what encoding
* is used by the underlying stream. This means that DBCS or Unicode
* encodings will not be properly handled.
*
* @return The next character read from the stream, -1 on any failure
* and -2 upon an <code>EOF</code>.
*
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
@Override
public int readByte()
throws StopConditionException
{
// we actually read a byte here
int res = readByteWorker();
return res >= 0 ? res & 0x000000FF : res;
}
/**
* Read all characters from the current read position in the stream to the
* next line separator (as determined by the <code>File.separator</code>
* or to the <code>EOF</code>. Any line separator character(s) and the
* <code>EOF</code> character are not returned.
*
* @return The next line read from the stream.
*
* @throws EOFException
* @throws IOException
* @throws InterruptedException
*/
public String readLn()
throws EOFException,
IOException,
InterruptedException
{
StringBuilder sb = new StringBuilder();
// set the initial number of bytes available
int onum = out.available();
int errnum = err.available();
// loop until some input is detected
while (child.isAlive() || onum > 0 || errnum > 0)
{
if (onum > 0)
{
while (sb.indexOf(NEWLINE) < 0 &&
(out.available() > 0 || child.isAlive()))
{
sb.append((char)out.read());
}
break;
}
else
{
if (errnum > 0)
{
while (sb.indexOf(NEWLINE) < 0 &&
(err.available() > 0 || child.isAlive()))
{
sb.append((char)err.read());
}
break;
}
}
// short pause before checking again
Thread.sleep(increment);
// reset num of available bytes
onum = out.available();
errnum = err.available();
}
return readLineCleanup(sb);
}
/**
* Closes the input stream and releases OS resources associated with it.
*/
public void closeIn()
{
try
{
cleanup(true, false);
if (out != null)
{
out.close();
out = null;
}
if (err != null)
{
err.close();
err = null;
}
}
catch (InterruptedIOException iioe)
{
ErrorManager.handleStopException(new StopConditionException(iioe));
}
catch (IOException ioe)
{
// nothing to do
}
}
/**
* Closes the output stream and releases OS resources associated with it.
*/
public void closeOut()
{
try
{
endOutput();
cleanup(false, true);
if (in != null)
{
in.close();
in = null;
}
}
catch (InterruptedIOException iioe)
{
ErrorManager.handleStopException(new StopConditionException(iioe));
}
catch (IOException ioe)
{
// nothing to do
}
try
{
raiseStopOnCloseIfNecessary();
}
finally
{
BufferSizeManager.getInstance().notifyCloseStream(this);
}
}
/**
* Closes the stream and releases OS resources associated with it.
*/
public void close()
{
closeIn();
closeOut();
}
/**
* State of the input side of the stream.
*
* @return <code>true</code> if the input side of the stream is active.
*/
public boolean isIn()
{
return (out != null);
}
/**
* State of the output side of the stream.
*
* @return <code>true</code> if the output side of the stream is active.
*/
public boolean isOut()
{
return (in != null);
}
/**
* Assigns the internal stream reference to the given reference.
*
* @param stream
* The new internal stream reference to use for all operations.
*
* @throws UnsupportedOperationException
*/
public void assign(Stream stream)
throws UnsupportedOperationException
{
throw new UnsupportedOperationException("Cannot handle this.");
}
/**
* Starts the child process monitor.
*
* @param pid
* The ID of the child process that is generating output.
*/
private void startWaiter(int pid)
{
// In case of INPUT-OUTPUT THROUGH statement we need only one waiter per process
if (this.child != null && this.child.alreadyInWait(pid))
{
return;
}
this.child = new Waiter(pid);
Thread t1 = new Thread(this.child);
t1.start();
}
/**
* Check whether we have exceeded the buffer size limit for the flush operations
* and, if it is true, re-throw the exception that was raised when the stream
* become broken.
*
* @throws IOException
* if we have exceeded the buffer size limit for the flush operations.
*/
private void raiseExceptionOnFlushIfNecessary()
throws IOException
{
if (storedException == null)
{
return;
}
BufferSizeManager manager = BufferSizeManager.getInstance();
if (manager.shouldRaiseStop(false, this))
{
manager.notifyBroken(this);
IOException e = storedException;
storedException = null;
throw e;
}
}
/**
* Check whether we have exceeded the buffer size limit for the close operations
* and, if it is true, display the error message and raise the STOP condition.
*
* @throws StopConditionException
* if we have exceeded the buffer size limit for the close operations.
*/
private void raiseStopOnCloseIfNecessary()
throws StopConditionException
{
if (storedException == null)
{
return;
}
BufferSizeManager manager = BufferSizeManager.getInstance();
if (manager.shouldRaiseStop(true, this))
{
manager.notifyBroken(this);
storedException = null;
ErrorManager.showErrorAndAbend(140, PIPE_BROKEN_MESSAGE);
}
}
/**
* Mark the stream as broken and notify BufferSizeManager about it.
*
* @param exc
* Exception because of which the stream became broken.
*/
private void makeBroken(IOException exc)
{
broken = true;
storedException = exc;
BufferSizeManager.getInstance().notifyOutputError(this);
}
/**
* Read the raw integer value of a character from the current read
* position in the stream. This method DOES NOT convert the value read
* from the stream's character set into Unicode.
* <p>
* At this time, only a single byte will be read no matter what encoding
* is used by the underlying stream. This means that DBCS or Unicode
* encodings will not be properly handled.
*
* @param isConvert
* Flag to control if the charset conversion is required.
*
* @return The next character read from the stream, -1 on any failure
* and -2 upon an <code>EOF</code>.
*
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
private int readChWorker(boolean isConvert)
throws StopConditionException
{
int ch = readByteWorker();
// convert back to the raw value if needed since the out or err stream
// handled translation to a Unicode char for us
if (isConvert && ch >= 0)
{
ch = Utils.toInt((char) ch, cset);
}
return ch;
}
/**
* Read the raw integer value of a byte from the current read
* position in the stream.
* <p>
* At this time, only a single byte will be read.
*
* @return The next character read from the stream, -1 on any failure
* and -2 upon an <code>EOF</code>.
*
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
private int readByteWorker()
throws StopConditionException
{
try
{
// set the initial number of bytes available
int onum = out.available();
int errnum = err.available();
// loop until some input is detected
while (child.isAlive() || onum > 0 || errnum > 0)
{
if (onum > 0)
{
return out.read();
}
else if (errnum > 0)
{
return err.read();
}
Thread.sleep(increment); // short pause before checking again
// reset the number of bytes available
onum = out.available();
errnum = err.available();
}
}
catch (EOFException ignored)
{
// The expected return -2 is coalesced with the case when the child is no longer alive and data
// from both its stream is no longer available (that is when the while loop exits naturally).
}
catch (InterruptedException | InterruptedIOException exception)
{
ErrorManager.handleStopException(new StopConditionException(exception));
}
catch (IOException ioe)
{
return -1;
}
return -2;
}
/**
* Provides a simple, common interface to hide the differences between
* streams and readers.
*/
private interface IOHelper
{
/**
* The number of bytes available to be immediately read without
* blocking.
*
* @return The number of available bytes.
*
* @throws IOException
*/
public int available()
throws IOException;
/**
* Write the given character to the output destination.
*
* @param ch
* The character to be written.
*
* @throws IOException
*/
public void writeCh(char ch)
throws IOException;
/**
* Write the given string to the output destination.
*
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(String data)
throws IOException;
/**
* Write the given byte array to the output destination.
*
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(byte[] data)
throws IOException;
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <=
* (data.length - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException;
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte
* while readers that generate keystrokes or characters may return a
* Unicode character.
*
* @return The next character read from the stream.
*
* @throws IOException
*/
public int read()
throws IOException;
/**
* Close and release any OS resources.
*
* @throws IOException
*/
public void close()
throws IOException;
}
/**
* Provides low level stream input support.
*/
private class InStream
implements IOHelper
{
/** The stream from which to read. */
private InputStream in = null;
/**
* Construct an instance with the given stream as the source.
*
* @param in
* The stream source.
*/
InStream(InputStream in)
{
this.in = in;
}
/**
* The number of bytes available to be immediately read without
* blocking.
*
* @return The number of available bytes.
*
* @throws IOException
*/
public int available()
throws IOException
{
return in.available();
}
/**
* Write the given character to the output destination.
*
* @param ch
* The character to be written.
*/
public void writeCh(char ch)
{
throw new UnsupportedOperationException("Input stream only.");
}
/**
* Write the given string to the output destination.
*
* @param data
* The data to be written.
*/
public void write(String data)
{
throw new UnsupportedOperationException("Input stream only.");
}
/**
* Write the given byte array to the output destination.
*
* @param data
* The data to be written.
*/
public void write(byte[] data)
{
throw new UnsupportedOperationException("Input stream only.");
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <=
* (data.length - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException
{
throw new UnsupportedOperationException("Input stream only.");
}
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte
* while readers that generate keystrokes or characters may return a
* Unicode character.
*
* @return The next character read from the stream.
*
* @throws IOException
*/
public int read()
throws IOException
{
return in.read();
}
/**
* Close and release any OS resources.
*
* @throws IOException
*/
public void close()
throws IOException
{
in.close();
}
}
/**
* Provides low level stream output support.
*/
private class OutStream
implements IOHelper
{
/** The stream to which to write. */
private OutputStream out = null;
/**
* Construct an instance with the given stream as the source.
*
* @param out
* The stream source.
*/
OutStream(OutputStream out)
{
this.out = out;
}
/**
* The number of bytes available to be immediately read without
* blocking.
*
* @return The number of available bytes.
*/
public int available()
{
throw new UnsupportedOperationException("Output stream only.");
}
/**
* Write the given character to the output destination.
*
* @param ch
* The character to be written.
*
* @throws IOException
*/
public void writeCh(char ch)
throws IOException
{
if (broken)
{
raiseExceptionOnFlushIfNecessary();
return;
}
try
{
out.write(ch);
out.flush();
}
catch (IOException e)
{
makeBroken(e);
raiseExceptionOnFlushIfNecessary();
}
}
/**
* Write the given string to the output destination.
*
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(String data)
throws IOException
{
if (broken)
{
raiseExceptionOnFlushIfNecessary();
return;
}
try
{
out.write(data.getBytes());
out.flush();
}
catch (IOException e)
{
makeBroken(e);
raiseExceptionOnFlushIfNecessary();
}
}
/**
* Write the given byte array to the output destination.
*
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(byte[] data)
throws IOException
{
write(data, 0, data.length);
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <=
* (data.length - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException
{
if (broken)
{
raiseExceptionOnFlushIfNecessary();
return;
}
try
{
out.write(data, off, len);
out.flush();
}
catch (IOException e)
{
makeBroken(e);
raiseExceptionOnFlushIfNecessary();
}
}
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte
* while readers that generate keystrokes or characters may return a
* Unicode character.
*
* @return The next character read from the stream.
*/
public int read()
{
throw new UnsupportedOperationException("Output stream only.");
}
/**
* Close and release any OS resources.
*
* @throws IOException
*/
public void close()
throws IOException
{
try
{
out.close();
}
catch(IOException e)
{
// suppress exception if the stream is broken
if (!broken)
throw e;
}
}
}
/**
* Provides low level reader input support.
*/
private class InReader
implements IOHelper
{
/** The reader from which to read. */
private Reader in = null;
/**
* Construct an instance with the given stream as the source.
*
* @param in
* The stream source.
*/
InReader(InputStream in)
{
try
{
this.in = (cset == null) ? new InputStreamReader(in)
: new InputStreamReader(in, cset);
}
catch (UnsupportedEncodingException uee)
{
// fall back to the default
this.in = new InputStreamReader(in);
}
}
/**
* The number of bytes available to be immediately read without
* blocking.
*
* @return 1 if there is at least 1 (maybe more) bytes ready to read
* or 0 if there is no data ready to be read.
*
* @throws IOException
*/
public int available()
throws IOException
{
return in.ready() ? 1 : 0;
}
/**
* Write the given character to the output destination.
*
* @param ch
* The character to be written.
*/
public void writeCh(char ch)
{
throw new UnsupportedOperationException("Input reader only.");
}
/**
* Write the given string to the output destination.
*
* @param data
* The data to be written.
*/
public void write(String data)
{
throw new UnsupportedOperationException("Input reader only.");
}
/**
* Write the given byte array to the output destination.
*
* @param data
* The data to be written.
*/
public void write(byte[] data)
{
throw new UnsupportedOperationException("Input reader only.");
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <=
* (data.length - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException
{
throw new UnsupportedOperationException("Input reader only.");
}
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte
* while readers that generate keystrokes or characters may return a
* Unicode character.
*
* @return The next character read from the stream.
*
* @throws IOException
*/
public int read()
throws IOException
{
return in.read();
}
/**
* Close and release any OS resources.
*
* @throws IOException
*/
public void close()
throws IOException
{
in.close();
}
}
/**
* Provides low level writer output support.
*/
private class OutWriter
implements IOHelper
{
/** The stream to which to write. */
private OutputStreamWriter out = null;
/**
* Construct an instance with the given stream as the source.
*
* @param out
* The stream source.
*/
OutWriter(OutputStream out)
{
try
{
this.out = (cset == null) ? new OutputStreamWriter(out)
: new OutputStreamWriter(out, cset);
}
catch (UnsupportedEncodingException uee)
{
// fall back to the default
this.out = new OutputStreamWriter(out);
}
}
/**
* The number of bytes available to be immediately read without
* blocking.
*
* @return The number of available bytes.
*/
public int available()
{
throw new UnsupportedOperationException("Output writer only.");
}
/**
* Write the given character to the output destination.
*
* @param ch
* The character to be written.
*/
public void writeCh(char ch)
throws IOException
{
if (broken)
{
raiseExceptionOnFlushIfNecessary();
return;
}
try
{
out.write(ch);
out.flush();
}
catch (IOException e)
{
makeBroken(e);
raiseExceptionOnFlushIfNecessary();
}
}
/**
* Write the given string to the output destination.
*
* @param data
* The data to be written.
*/
public void write(String data)
throws IOException
{
if (broken)
{
raiseExceptionOnFlushIfNecessary();
return;
}
try
{
out.write(data, 0, data.length());
out.flush();
}
catch(IOException e)
{
makeBroken(e);
raiseExceptionOnFlushIfNecessary();
}
}
/**
* Write the given byte array to the output destination.
*
* @param data
* The data to be written.
*/
public void write(byte[] data)
throws IOException
{
write(data, 0, data.length);
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param data
* The data to be written.
* @param off
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param len
* Length of data to be written. Must be non-negative and {@code <=
* (data.length - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(byte[] data, int off, int len)
throws IOException
{
if (broken)
{
raiseExceptionOnFlushIfNecessary();
return;
}
char[] cbuf = new char[len];
for (int i = off; i < len; i++)
{
cbuf[i] = (char) data[i];
}
try
{
out.write(cbuf);
out.flush();
}
catch(IOException e)
{
makeBroken(e);
raiseExceptionOnFlushIfNecessary();
}
}
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte
* while readers that generate keystrokes or characters may return a
* Unicode character.
*
* @return The next character read from the stream.
*/
public int read()
{
throw new UnsupportedOperationException("Output writer only.");
}
/**
* Close and release any OS resources.
*
* @throws IOException
*/
public void close()
throws IOException
{
try
{
out.close();
}
catch(IOException e)
{
// suppress exception if the stream is broken
if (!broken)
throw e;
}
}
}
/**
* Provides a simple mechanism to wait for the exit of a process on an
* asynchronous basis (on a new thread dedicated to this wait).
*/
private class Waiter
implements Runnable
{
/** The child process for which to wait. */
private int pid = -1;
/** Status of the child process. */
private boolean alive = true;
/**
* Constructs an instance that has the data needed to wait for the
* child process and report on that process' state.
*
* @param pid
* The child process for which to wait.
*/
public Waiter(int pid)
{
this.pid = pid;
}
/**
* Accesses the status of the child process.
*
* @return If <code>true</code>, the process is still running.
*/
public synchronized boolean isAlive()
{
return alive;
}
/**
* Verify if the given process already waited to be done.
*
* @return If <code>true</code>, the process is under waiting for complete.
*/
public synchronized boolean alreadyInWait(int pid)
{
return alive && this.pid == pid;
}
/**
* Waits for the child process to die and then sets the
* <code>alive</code> status to <code>false</code>.
*/
public void run()
{
// wait for the child process to die, then record that fact
try
{
// block until the child process exits
LaunchManager.pseudoTerminalWait(pid);
}
finally
{
synchronized (this)
{
alive = false;
}
}
}
}
}