package co.codewizards.cloudstore.core.util.childprocess;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Thread continuously reading from an {@link InputStream} and writing everything to an {@link OutputStream}.
* <p>
* While this functionality is useful for multiple purposes, we use {@code DumpStreamThread} primarily for
* child processes. When starting another process (e.g. via the
* {@link java.lang.ProcessBuilder ProcessBuilder}), it is necessary to read the process' output. Without
* doing this, the child process might block forever - especially on Windows (having a very limited
* standard-output-buffer), this is a well-known problem.
* <p>
* Since the main thread (invoking the child process) usually blocks and waits for the child process to
* return (i.e. exit), dumping its standard-out and standard-error to a buffer or a log file is done on a
* separate thread.
* <p>
* Please note that {@code DumpStreamThread} can automatically write everything to the log. This is done via
* an instance of {@link LogDumpedStreamThread}.
* @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
*/
public class DumpStreamThread extends Thread
{
private final Logger logger = LoggerFactory.getLogger(DumpStreamThread.class);
private final InputStream inputStream;
private final OutputStream outputStream;
private volatile boolean ignoreErrors = false;
private volatile boolean forceInterrupt = false;
private final LogDumpedStreamThread logDumpedStreamThread;
public void setIgnoreErrors(final boolean ignoreErrors) {
this.ignoreErrors = ignoreErrors;
}
@Override
public void interrupt() {
forceInterrupt = true;
super.interrupt();
}
@Override
public boolean isInterrupted() {
return forceInterrupt || super.isInterrupted();
}
/**
* Creates an instance of {@code DumpStreamThread}.
* @param inputStream the stream to read from. Must not be <code>null</code>.
* @param outputStream the stream to write to. Must not be <code>null</code>.
* @param childProcessLoggerName the name of the logger. May be <code>null</code> if logging is not
* desired. In case of <code>null</code>, logging is completely disabled.
*/
public DumpStreamThread(final InputStream inputStream, final OutputStream outputStream, final String childProcessLoggerName) {
this(inputStream, outputStream, null, childProcessLoggerName);
}
/**
* Creates an instance of {@code DumpStreamThread}.
* @param inputStream the stream to read from. Must not be <code>null</code>.
* @param outputStream the stream to write to. Must not be <code>null</code>.
* @param childProcessLogger the logger. May be <code>null</code> if logging is not desired.
*/
public DumpStreamThread(final InputStream inputStream, final OutputStream outputStream, final Logger childProcessLogger) {
this(inputStream, outputStream, childProcessLogger, null);
}
private DumpStreamThread(final InputStream inputStream, final OutputStream outputStream,
final Logger childProcessLogger, final String childProcessLoggerName)
{
assertNotNull(inputStream, "inputStream");
assertNotNull(outputStream, "outputStream");
this.inputStream = inputStream;
this.outputStream = outputStream;
if (childProcessLogger != null)
this.logDumpedStreamThread = new LogDumpedStreamThread(childProcessLogger);
else
this.logDumpedStreamThread = childProcessLoggerName == null ? null : new LogDumpedStreamThread(childProcessLoggerName);
}
@Override
public synchronized void start() {
if (logDumpedStreamThread != null)
logDumpedStreamThread.start();
super.start();
}
@Override
public void run() {
try {
final byte[] buffer = new byte[10240];
while (!isInterrupted()) {
try {
final int bytesRead = inputStream.read(buffer);
if (bytesRead > 0) {
outputStream.write(buffer, 0, bytesRead);
if (logDumpedStreamThread != null)
logDumpedStreamThread.write(buffer, bytesRead);
}
} catch (final Throwable e) {
if (!ignoreErrors)
logger.error("run: " + e, e); //$NON-NLS-1$
else
logger.info("run: " + e); //$NON-NLS-1$
return;
}
}
} finally {
if (logDumpedStreamThread != null)
logDumpedStreamThread.interrupt();
try {
outputStream.close();
} catch (final IOException e) {
logger.warn("run: outputStream.close() failed: " + e, e); //$NON-NLS-1$
}
}
}
/**
* Sets a {@link StringBuffer} for capturing all output.
* <p>
* Please note, that only data read from the stream after this was set is captured. You normally want to
* set this {@code StringBuffer}.
* <p>
* This feature is only available, if logging is enabled.
* @param outputStringBuffer the {@link StringBuffer} used for capturing. May be <code>null</code>.
*/
public void setOutputStringBuffer(final StringBuffer outputStringBuffer) {
if (logDumpedStreamThread == null)
throw new IllegalStateException("Not supported, if logging is disabled!");
logDumpedStreamThread.setOutputStringBuffer(outputStringBuffer);
}
public StringBuffer getOutputStringBuffer() {
if (logDumpedStreamThread == null)
throw new IllegalStateException("Not supported, if logging is disabled!");
return logDumpedStreamThread.getOutputStringBuffer();
}
public void setOutputStringBufferMaxLength(final int outputStringBufferMaxLength) {
if (logDumpedStreamThread == null)
throw new IllegalStateException("Not supported, if logging is disabled!");
logDumpedStreamThread.setOutputStringBufferMaxLength(outputStringBufferMaxLength);
}
public int getOutputStringBufferMaxLength() {
if (logDumpedStreamThread == null)
throw new IllegalStateException("Not supported, if logging is disabled!");
return logDumpedStreamThread.getOutputStringBufferMaxLength();
}
public void flushBuffer() {
if (logDumpedStreamThread != null)
logDumpedStreamThread.flushBuffer();
}
}