
com.gc.iotools.stream.is.InputStreamFromOutputStream Maven / Gradle / Ivy
package com.gc.iotools.stream.is;
/*
* Copyright (c) 2008,2009 Davide Simonetti.
* This source code is released under the BSD License.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gc.iotools.stream.base.EasyStreamConstants;
import com.gc.iotools.stream.base.ExecutionModel;
import com.gc.iotools.stream.base.ExecutorServiceFactory;
/**
*
* This class allow to read the data written to an OutputStream from an
* InputStream.
*
*
* To use this class you must subclass it and implement the abstract method
* {@linkplain #produce(OutputStream)}. The data who is produced inside this
* function can be written to the sink OutputStream
passed as a
* parameter. Later it can be read back from from the
* InputStreamFromOutputStream
class (whose ancestor is
* java.io.InputStream
).
*
*
*
* final String dataId=//id of some data.
* final InputStreamFromOutputStream<String> isos
* = new InputStreamFromOutputStream<String>() {
* @Override
* public String produce(final OutputStream dataSink) throws Exception {
* //call your application function who produces the data here
* //WARNING: we're in another thread here, so this method shouldn't
* //write any class field or make assumptions on the state of the class.
* return produceMydata(dataId,dataSink)
* }
* };
* try {
* //now you can read from the InputStream the data that was written to the
* //dataSink OutputStream
* byte[] readed=IOUtils.toByteArray(isos);
* //Use data here
* } catch (final IOException e) {
* //Handle exception here
* } finally {
* isos.close();
* }
* //You can get the result of produceMyData after the stream has been closed.
* String resultOfProduction = isos.getResult();
*
*
* This class encapsulates a pipe and a Thread
, hiding the
* complexity of using them. It is possible to select different strategies for
* allocating the internal thread or even specify the
* {@linkplain ExecutorService} for thread execution.
*
*
* @param
* Optional result returned by the function
* {@linkplain #produce(OutputStream)} after the data has been
* written. It can be obtained calling the {@linkplain #getResult()}
*
* @see ExecutionModel
* @author dvd.smnt
* @since 1.0
*/
public abstract class InputStreamFromOutputStream extends InputStream {
/**
* This inner class run in another thread and calls the
* {@link #produce(OutputStream)} method.
*
* @author dvd.smnt
*/
private final class DataProducer implements Callable {
private final String name;
private final OutputStream outputStream;
DataProducer(final String threadName, final OutputStream ostream) {
this.outputStream = ostream;
this.name = threadName;
}
public T call() throws Exception {
final String threadName = getName();
T result;
InputStreamFromOutputStream.ACTIVE_THREAD_NAMES.add(threadName);
InputStreamFromOutputStream.LOG.debug("thread [" + threadName
+ "] started.");
try {
result = produce(this.outputStream);
} finally {
closeStream();
InputStreamFromOutputStream.ACTIVE_THREAD_NAMES
.remove(threadName);
InputStreamFromOutputStream.LOG.debug("thread [" + threadName
+ "] closed.");
}
return result;
}
private void closeStream() {
try {
this.outputStream.close();
} catch (final IOException e) {
if ((e.getMessage() != null)
&& (e.getMessage().indexOf("closed") > 0)) {
InputStreamFromOutputStream.LOG
.debug("Stream already closed");
} else {
InputStreamFromOutputStream.LOG.error(
"IOException closing OutputStream"
+ " Thread might be locked", e);
}
} catch (final Throwable t) {
InputStreamFromOutputStream.LOG.error(
"Error closing InputStream"
+ " Thread might be locked", t);
}
}
String getName() {
return this.name;
}
}
/**
* Extends PipedInputStream to allow set the default buffer size.
*
*/
private final class MyPipedInputStream extends PipedInputStream {
MyPipedInputStream(final int bufferSize) {
super.buffer = new byte[bufferSize];
}
}
/**
* Collection for debugging purpose containing names of threads (name is
* calculated from the instantiation line. See getCaller()
.
*/
private static final List ACTIVE_THREAD_NAMES = Collections
.synchronizedList(new ArrayList());
/**
* The default pipe buffer size for the newly created pipes.
*/
private static int defaultPipeSize = EasyStreamConstants.DEFAULT_PIPE_SIZE;
private static final Logger LOG = LoggerFactory
.getLogger(InputStreamFromOutputStream.DataProducer.class);
/**
* This method can be used for debugging purposes to get a list of the
* currently active threads.
*
* @return Array containing names of the threads currently active.
*/
public static final String[] getActiveThreadNames() {
final String[] result;
synchronized (InputStreamFromOutputStream.ACTIVE_THREAD_NAMES) {
result = InputStreamFromOutputStream.ACTIVE_THREAD_NAMES
.toArray(new String[0]);
}
return result;
}
/**
* Set the size for the pipe circular buffer for the newly created
* InputStreamFromOutputStream
. Default is 4096 bytes.
* will be removed in 1.3.0. Use setDefaultPipeSize instead.
* @deprecated
*
* @since 1.2.0
* @param defaultPipeSize
* the default pipe buffer size in bytes.
*/
public static void setDefaultBufferSize(final int defaultPipeSize) {
InputStreamFromOutputStream.defaultPipeSize = defaultPipeSize;
}
/**
* Set the size for the pipe circular buffer for the newly created
* InputStreamFromOutputStream
. Default is 4096 bytes.
*
* @since 1.2.2
* @param defaultPipeSize
* the default pipe buffer size in bytes.
*/
public static void setDefaultPipeSize(final int defaultPipeSize) {
InputStreamFromOutputStream.defaultPipeSize = defaultPipeSize;
}
private boolean closeCalled = false;
private final Future futureResult;
private final boolean joinOnClose;
private final PipedInputStream pipedIS;
/**
*
* It creates a InputStreamFromOutputStream
with a
* THREAD_PER_INSTANCE thread strategy.
*
*
* @see ExecutionModel#THREAD_PER_INSTANCE
*/
public InputStreamFromOutputStream() {
this(ExecutionModel.THREAD_PER_INSTANCE);
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* choose the thread allocation strategy he likes.
*
*
* This class executes the produce method in a thread created internally.
*
*
* @since 1.2.2
* @see ExecutionModel
* @param executionModel
* Defines how the internal thread is allocated.
* @param joinOnClose
* If true
the {@linkplain #close()} method will
* also wait for the internal thread to finish.
*/
public InputStreamFromOutputStream(final boolean joinOnClose,
final ExecutionModel executionModel) {
this(joinOnClose, ExecutorServiceFactory.getExecutor(executionModel));
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* specify the ExecutorService that will execute the
* {@linkplain #produce(OutputStream)} method.
*
*
* @since 1.2.2
* @see ExecutorService
* @param executor
* Defines the ExecutorService that will allocate the the
* internal thread.
* @param joinOnClose
* If true
the {@linkplain #close()} method will
* also wait for the internal thread to finish.
*/
public InputStreamFromOutputStream(final boolean joinOnClose,
final ExecutorService executor) {
final String callerId = getCaller();
this.joinOnClose = joinOnClose;
PipedOutputStream pipedOS = null;
try {
this.pipedIS = new MyPipedInputStream(defaultPipeSize);
pipedOS = new PipedOutputStream(this.pipedIS);
} catch (final IOException e) {
throw new RuntimeException("Error during pipe creaton", e);
}
final Callable executingCallable = new DataProducer(callerId,
pipedOS);
this.futureResult = executor.submit(executingCallable);
InputStreamFromOutputStream.LOG.debug("thread invoked by[" + callerId
+ "] queued for start.");
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* choose the thread allocation strategy he likes.
*
*
* This class executes the produce method in a thread created internally.
*
*
* @see ExecutionModel
* @param executionModel
* Defines how the internal thread is allocated.
*
*/
public InputStreamFromOutputStream(final ExecutionModel executionModel) {
this(false, executionModel);
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* specify the ExecutorService that will execute the
* {@linkplain #produce(OutputStream)} method.
*
*
* @see ExecutorService
* @param executor
* Defines the ExecutorService that will allocate the the
* internal thread.
*/
public InputStreamFromOutputStream(final ExecutorService executor) {
this(false, executor);
}
/**
* {@inheritDoc}
*/
@Override
public final void close() throws IOException {
if (!this.closeCalled) {
this.closeCalled = true;
this.pipedIS.close();
if (this.joinOnClose) {
try {
getResult();
} catch (final Exception e) {
final IOException e1 = new IOException(
"The internal stream threw exception");
e1.initCause(e);
throw e1;
}
}
}
}
/**
*
* Returns the object that was previously returned by the
* {@linkplain #produce(OutputStream)} method. It performs all the
* synchronization operations needed to read the result and waits for the
* internal thread to terminate.
*
*
* This method must be called after the method {@linkplain #close()},
* otherwise an IllegalStateException is thrown.
*
*
* @since 1.2.0
* @return Object that was returned by the
* {@linkplain #produce(OutputStream)} method.
* @throws Exception
* If the {@linkplain #produce(OutputStream)} method threw an
* Exception this method will throw again the same exception.
* @throws IllegalStateException
* If the {@linkplain #close()} method hasn't been called yet.
*/
public T getResult() throws Exception {
if (!this.closeCalled) {
throw new IllegalStateException(
"getResult() called before close()."
+ "This method can be called only "
+ "after the stream has been closed.");
}
T result;
try {
result = this.futureResult.get();
} catch (final ExecutionException e) {
final Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else {
throw e;
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final int read() throws IOException {
final int result = this.pipedIS.read();
if (result < 0) {
checkException();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final int read(final byte[] b, final int off, final int len)
throws IOException {
final int result = this.pipedIS.read(b, off, len);
if (result < 0) {
checkException();
}
return result;
}
private void checkException() throws IOException {
try {
this.futureResult.get();
} catch (final ExecutionException e) {
final Throwable t = e.getCause();
final IOException e1 = new IOException("Exception producing data");
e1.initCause(t);
throw e1;
} catch (final InterruptedException e) {
final IOException e1 = new IOException("Thread interrupted");
e1.initCause(e);
throw e1;
}
}
private String getCaller() {
final Exception exception = new Exception();
final StackTraceElement[] stes = exception.getStackTrace();
boolean found = false;
StackTraceElement caller = null;
for (int i = 0; (i < stes.length) && !found; i++) {
caller = stes[i];
found = !this.getClass().equals(caller.getClass());
}
final String result = getClass().getName().substring(
getClass().getPackage().getName().length() + 1)
+ "callBy:" + caller.toString();
InputStreamFromOutputStream.LOG.debug("OpenedBy [" + result + "]");
return result;
}
/**
*
* This method must be implemented by the user of this class to produce the
* data that must be read from the external InputStream
.
*
*
* Special care must be paid passing arguments to this method or setting
* global fields because it is executed in another thread.
*
*
* The right way to set a field variable is to return a value in the
* produce
and retrieve it in the getResult().
*
*
* @return The implementing class can use this to return a result of data
* production. The result will be available through the method
* {@linkplain #getResult()}.
* @param sink
* the implementing class should write its data to this stream.
* @throws Exception
* the exception eventually thrown by the implementing class is
* returned by the {@linkplain #read()} methods.
* @see #getResult()
*/
protected abstract T produce(final OutputStream sink) throws Exception;
}