package de.skuzzle.polly.process;
import java.io.InputStream;
/**
* StreamHandlers allow the parallel consumption of incoming data from a process's
* standard output or standard error output. As by the documentation of the
* {@link Process} class its essential to empty those streams because some native
* implementations only provide limited buffersizes for these stream. Not clearing them
* could then lead into a deadlock.
*
* When creating a process using the {@link ProcessExecutor}, you can set an input and
* an error stream handler using {@link ProcessExecutor#setInputHandler(StreamHandler)}
* and {@link ProcessExecutor#setErrorHandler(StreamHandler)}.
*
* The StreamHandler starts a new thread which handles the parallel consumption.
*
* @author Simon
*/
public abstract class StreamHandler extends Thread {
/**
* This state indicates that the stream was handled successfuly.
*/
public final static int STATE_SUCCESS = 0;
/**
* This state indicates that an error occured during stream handling.
*/
public final static int STATE_ERROR = -1;
/**
* This state indicates that the StreamHandler is currently running.
*/
public final static int STATE_ACTIVE = 2;
/**
* Indicates that the handler has not been run yet.
*/
public final static int STATE_OFF = 3;
/**
* This field is used for the creation of default names for the handlers.
*/
private static int num;
private InputStream stream;
private int state;
private Exception errorState;
private boolean shutdownFlag;
/**
* Creates a new StreamHandler with a certain name. To attach an InputStream, use the
* {@link #setStream(InputStream)} method.
*
* @param name The name of the thread used for stream handling.
*/
public StreamHandler(String name) {
this(null, name);
}
/**
* Creates a new StreamHandler for the given stream using a default name.
*
* @param stream The stream to handle. Note: When using a StreamHandler with a
* {@link ProcessExecutor}, this InputStream will be overridden with the
* InputStream of the created process.
*/
public StreamHandler(InputStream stream) {
this(stream, "STREAM_HANDLER_" + (++num));
}
/**
* Creates a new StreamHandler for the given stream and with given thread name.
*
* @param stream The stream to handle. Note: When using a StreamHandler with a
* {@link ProcessExecutor}, this InputStream will be overridden with the
* InputStream of the created process.
* @param name The name of the thread used for stream handling.
*/
public StreamHandler(InputStream stream, String name) {
super(name);
this.stream = stream;
this.state = STATE_OFF;
}
/**
* Gets the current state of this handler. State may be one of these constants:
* {@link #STATE_OFF}, {@link #STATE_ACTIVE}, {@link #STATE_OFF} or
* {@link #STATE_ERROR}.
*
* When state is STATE_ERROR, the exception which caused this error can be retrieved
* using {@link #getErrorState()}.
*
* @return The current handler state.
*/
public int getStreamState() {
return this.state;
}
/**
* If an exception occured during stream handling, this exception can be retrieved
* with this method.
*
* @return The exception that caused this StreamHandler to fail or <code>null</code>
* if <code>getStreamState() != STATE_ERROR</code>.
*/
public Exception getErrorState() {
return this.errorState;
}
/**
* Sets the {@link InputStream} for this handler. You can only set the stream before
* calling {@link #start()}.
*
* @param stream The stream to handle.
* @throws IllegalStateException If <code>getState() != STATE_OFF</code>.
*/
public void setStream(InputStream stream) {
if (this.state != STATE_OFF) {
throw new IllegalStateException();
}
this.stream = stream;
}
/**
* This method must be implemented by subclasses to handle incoming data.
*
* @param stream The stream to handle.
* @throws Exception Can be thrown by subclasses if they encounter any error. This
* StreamHandler will then stop executing and change its state to
* {@link #STATE_ERROR}. The error can then be retrieved using
* {@link #getErrorState()}
*/
protected abstract void handle(InputStream stream) throws Exception;
void processGone() {
this.shutdownFlag = true;
this.interrupt();
}
/**
* Runs this StreamHandler. First it changes the current state to
* {@link #STATE_ACTIVE}, then calls the {@link #handle(InputStream)} method.
* When returned, the state is changed to {@link #STATE_SUCCESS} or on exception to
* {@link #STATE_ERROR}.
*/
@Override
public void run() {
this.state = STATE_ACTIVE;
try {
this.handle(this.stream);
this.state = STATE_SUCCESS;
} catch (InterruptedException e) {
if (this.shutdownFlag) {
this.state = STATE_SUCCESS;
} else {
this.state = STATE_ERROR;
this.errorState = e;
}
} catch (Exception e) {
this.state = STATE_ERROR;
this.errorState = e;
}
}
}