All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gc.iotools.stream.os.OutputStreamToInputStream Maven / Gradle / Ivy

Go to download

EasyStream is a small set of utilities for dealing with streams (InputStreams and OutputStreams). The aim is to ease the use of pipes when they're required. Main features are: * "Convert" an OutputStream to an InputStream. * Count the number of bytes read or wrote to a given stream. * While reading the data from an InputStream copy it to a supplied OutputStream. * Read the content of an InputStream multiple times or seek to a definite position

The newest version!
package com.gc.iotools.stream.os;

/*
 * Copyright (c) 2008, 2015 Gabriele Contini. This source code is released
 * under the BSD License.
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.io.input.CloseShieldInputStream;
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;
import com.gc.iotools.stream.utils.LogUtils;

/**
 * 

* This class allow to read from an InputStream the data who has * been written to an OutputStream (performs an * OutputStream -> InputStream conversion). *

*

* More detailiy it is an OutputStream that, when extended, allows * to read the data written to it from the InputStream inside the * method {@linkplain #doRead(InputStream)}. *

*

* To use this class you must extend it and implement the method * {@linkplain #doRead(InputStream)}. Inside this method place the logic that * needs to read the data from the InputStream. Then the data can * be written to this class that implements OutputStream. When * {@linkplain #close()} method is called on the outer OutputStream * an EOF is generated in the InputStream passed in the * {@linkplain #doRead(InputStream)}. *

*

* The {@linkplain #doRead(InputStream)} call executes in another thread, so * there is no warranty on when it will start and when it will end. Special care * must be taken in passing variables to it: all the arguments must be final and * inside {@linkplain #doRead(InputStream)} you shouldn't change the variables * of the outer class. *

*

* Any Exception threw inside the {@linkplain #doRead(InputStream)} method is * propagated to the outer OutputStream on the next * write operation. *

*

* The method {@link #getResult()} suspend the outer thread and wait for the * read from the internal stream is over. It returns when the * doRead() terminates and has produced its result. *

*

* Some sample code: *

* *
 * OutputStreamToInputStream<String> oStream2IStream =
 * new OutputStreamToInputStream<String>() {
 * 	protected String doRead(final InputStream istream) throws Exception {
 * 		// Users of this class should place all the code that need to read data
 *      // from the InputStream in this method. Data available through the
 *      // InputStream passed as a parameter is the data that is written to the
 * 		// OutputStream oStream2IStream through its write() methods.
 * 		final String result = IOUtils.toString(istream);
 *      //"result" is here only for example of how returning data to the external OutputStream. 
 *      //Real implementations might return "null" if they doesn't need to return a value.
 * 		return result + " was processed.";
 * 	}
 * };
 * try {
 * 	// some data is written to the OutputStream, will be passed to the method
 * 	// doRead(InputStream i) above and after close() is called the results
 * 	// will be available through the getResults() method.
 * 	oStream2IStream.write("test".getBytes());
 * } finally {
 * 	// don't miss the close (or a thread would not terminate correctly).
 * 	oStream2IStream.close();
 * }
 * String result = oStream2IStream.getResult();
 * //result now contains the string "test was processed."
 * 
* * @param * Type returned by the method {@link #getResults()} after the thread * has finished. * @since 1.0 * @author dvd.smnt */ public abstract class OutputStreamToInputStream extends PipedOutputStream { /** * This class executes in the second thread. * * @author dvd.smnt */ private final class DataConsumer implements Callable { @Override public synchronized T call() throws Exception { T processResult; try { // avoid the internal class close the stream. final CloseShieldInputStream istream = new CloseShieldInputStream( OutputStreamToInputStream.this.inputstream); processResult = doRead(istream); } catch (final Exception e) { OutputStreamToInputStream.this.abort = true; throw e; } finally { // empty the internal inputstream so the outer thread doesn't // lock emptyInputStream(); OutputStreamToInputStream.this.inputstream.close(); } return processResult; } private void emptyInputStream() { try { final byte[] buffer = new byte[EasyStreamConstants.SKIP_BUFFER_SIZE]; while (OutputStreamToInputStream.this.inputstream.read(buffer) >= 0) { ; // empty block: just throw bytes away } } catch (final IOException e) { if ((e.getMessage() != null) && (e.getMessage().indexOf("closed") > 0)) { OutputStreamToInputStream.LOG .debug("Stream already closed"); } else { OutputStreamToInputStream.LOG.error( "IOException while empty InputStream a " + "thread can be locked", e); } } catch (final Throwable e) { OutputStreamToInputStream.LOG.error( "IOException while empty InputStream a " + "thread can be locked", e); } } } /** * Extends PipedInputStream to allow set the default buffer size. */ private final class MyPipedInputStream extends PipedInputStream { MyPipedInputStream(final int bufferSize) { super.buffer = new byte[bufferSize]; } } // Default timeout in milliseconds. private static final int DEFAULT_TIMEOUT = 15 * 60 * 1000; /** * 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(OutputStreamToInputStream.class); /** * Set the size for the pipe circular buffer. This setting has effect for * the newly created OutputStreamToInputStream. Default is 4096 * bytes. * * @since 1.2.3 * @param defaultPipeSize * The default pipe buffer size in bytes. */ public static void setDefaultPipeSize(final int defaultPipeSize) { OutputStreamToInputStream.defaultPipeSize = defaultPipeSize; } private boolean abort = false; private boolean closeCalled = false; private final ExecutorService executorService; private final InputStream inputstream; private final boolean joinOnClose; private Future writingResult = null; /** *

* Creates a new OutputStreamToInputStream. It uses the default * {@link ExecutionModel#THREAD_PER_INSTANCE} thread instantiation strategy. * This means that a new thread is created for every instance of * OutputStreamToInputStream. *

*

* When the {@linkplain #close()} method is called this class wait for the * internal thread to terminate. *

* * @throws java.lang.IllegalStateException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream() { this(true, ExecutionModel.THREAD_PER_INSTANCE); } /** *

* Creates a new OutputStreamToInputStream. It uses the default * {@link ExecutionModel#THREAD_PER_INSTANCE} thread instantiation strategy. * This means that a new thread is created for every instance of * OutputStreamToInputStream. *

*

* If startImmediately is true the internal thread * will start before the constructor completes. This is the best way if * you're doing anonymous subclassing. While if you do explicit sublcassing * you should set this parameter to false to allow the constructor of the * superclass to complete before the threads are started. *

*

* When the {@linkplain #close()} method is called this class wait for the * internal thread to terminate. *

* * @since 1.2.13 * @throws java.lang.IllegalStateException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean startImmediately) { this(startImmediately, true, ExecutorServiceFactory .getExecutor(ExecutionModel.THREAD_PER_INSTANCE), defaultPipeSize); } /** *

* Creates a new OutputStreamToInputStream. It let the user * specify the thread instantiation service and what will happen upon the * invocation of close() method. *

*

* If startImmediately is true the internal thread * will start before the constructor completes. This is the best way if * you're doing anonymous subclassing. While if you do explicit sublcassing * you should set this parameter to false to allow the constructor of the * superclass to complete before the threads are started. *

* If joinOnClose is true when the * close() method is invoked this class will wait for the * internal thread to terminate.

*

* It also let the user specify the size of the pipe buffer to allocate. *

* * @since 1.2.13 * @param startImmediately * if true the internal thread will start * immediately after this constructor completes. * @param joinOnClose * if true the internal thread will be joined when * close is invoked. * @param executorService * Service for executing the internal thread. * @param pipeBufferSize * The size of the pipe buffer to allocate. * @throws java.lang.IllegalStateException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean startImmediately, final boolean joinOnClose, final ExecutorService executorService, final int pipeBufferSize) { if (executorService == null) { throw new IllegalArgumentException("executor service can't be null"); } final String callerId = LogUtils.getCaller(getClass()); final PipedInputStream pipedIS = new MyPipedInputStream(pipeBufferSize); try { pipedIS.connect(this); } catch (final IOException e) { throw new IllegalStateException("Error during pipe creaton", e); } this.joinOnClose = joinOnClose; this.inputstream = pipedIS; this.executorService = executorService; LOG.debug("invoked by[{}] queued for start.", callerId); if (startImmediately) { initializeIfNecessary(); } } /** *

* Creates a new OutputStreamToInputStream. It let the user * specify the thread instantiation strategy and what will happen upon the * invocation of close() method. *

*

* If joinOnClose is true when the * close() method is invoked this class will wait for the * internal thread to terminate. *

* * @see ExecutionModel * @param joinOnClose * if true the internal thread will be joined when * close is invoked. * @param executionModel * The strategy for allocating threads. * @throws java.lang.IllegalStateException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean joinOnClose, final ExecutionModel executionModel) { this(joinOnClose, ExecutorServiceFactory.getExecutor(executionModel)); } /** *

* Creates a new OutputStreamToInputStream. It let the user * specify the thread instantiation service and what will happen upon the * invocation of close() method. *

*

* If joinOnClose is true when the * close() method is invoked this class will wait for the * internal thread to terminate. *

* * @since 1.2.6 * @param joinOnClose * if true the internal thread will be joined when * close is invoked. * @param executorService * Service for executing the internal thread. * @throws java.lang.IllegalStateException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean joinOnClose, final ExecutorService executorService) { this(joinOnClose, executorService, defaultPipeSize); } /** *

* Creates a new OutputStreamToInputStream. It let the user * specify the thread instantiation service and what will happen upon the * invocation of close() method. *

*

* If joinOnClose is true when the * close() method is invoked this class will wait for the * internal thread to terminate. *

*

* It also let the user specify the size of the pipe buffer to allocate. *

* * @since 1.2.6 * @param joinOnClose * if true the internal thread will be joined when * close is invoked. * @param executorService * Service for executing the internal thread. * @param pipeBufferSize * The size of the pipe buffer to allocate. * @throws java.lang.IllegalStateException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean joinOnClose, final ExecutorService executorService, final int pipeBufferSize) { this(false, joinOnClose, executorService, pipeBufferSize); } /** *

* This method is called just before the close method completes, and after * the eventual join with the internal thread. *

*

* It is an extension point designed for applications that need to perform * some operation when the OutputStream is closed. *

* * @since 1.2.9 * @throws IOException * threw when the extension point wants to launch an exception. */ protected void afterClose() throws IOException { // extension point; } /** {@inheritDoc} */ @Override public final void close() throws IOException { internalClose(this.joinOnClose, TimeUnit.MILLISECONDS, DEFAULT_TIMEOUT); } /** * When this method is called the internal thread is always waited for * completion. * * @param timeout * maximum time to wait for the internal thread to finish. * @param tu * Time unit for the timeout. * @throws java.io.IOException * Threw if some problem (timeout or internal exception) occurs. * see the getCause() method for the explanation. */ public final void close(final long timeout, final TimeUnit tu) throws IOException { internalClose(true, tu, timeout); } /** *

* This method has to be implemented to use this class. It allows to * retrieve the data written to the outer OutputStream from the * InputStream passed as a parameter. *

*

* Any exception eventually threw inside this method will be propagated to * the external OutputStream. When the next * {@linkplain #write(byte[])} operation is called an * IOException will be thrown and the original exception can be * accessed calling the getCause() method on the IOException. It will also * be available by calling the method {@link #getResults()}. *

* * @param istream * The InputStream where the data can be retrieved. * @return Optionally returns a result of the elaboration. * @throws java.lang.Exception * If an java.lang.Exception occurs during the * elaboration it can be thrown. It will be propagated to the * external OutputStream and will be available * calling the method {@link #getResults()}. */ protected abstract T doRead(InputStream istream) throws Exception; /** {@inheritDoc} */ @Override public final void flush() throws IOException { initializeIfNecessary(); if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { super.flush(); } } /** *

* This method returns the result of the method {@link #doRead(InputStream)} * and ensure the previous method is over. *

*

* This method suspend the calling thread and waits for the function * {@link #doRead(InputStream)} to finish. It returns when the * doRead() terminates and has produced its result. *

*

* It must be called after the method {@link #close()} otherwise a * IllegalStateException is thrown. *

* * @exception InterruptedException * Thrown when the thread is interrupted. * @exception Exception * Thrown if the method {@linkplain #doRead(InputStream)} * threw an Exception. * * @return the object returned from the doRead() method. */ public final T getResult() throws Exception { initializeIfNecessary(); if (!this.closeCalled) { this.closeCalled = true; super.close(); } T result; try { result = this.writingResult.get(); } catch (ExecutionException e) { if (e.getCause() instanceof Exception) { throw (Exception)e.getCause(); } else { throw e; } } return result; } private void initializeIfNecessary() { if (this.writingResult == null) { final DataConsumer executingProcess = new DataConsumer(); this.writingResult = this.executorService.submit(executingProcess); } } private void internalClose(final boolean join, final TimeUnit timeUnit, final long timeout) throws IOException { if (!this.closeCalled) { initializeIfNecessary(); this.closeCalled = true; super.close(); if (join) { // waiting for thread to finish.. try { this.writingResult.get(timeout, timeUnit); } catch (final ExecutionException e) { final IOException e1 = new IOException( "The doRead() threw exception. Use " + "getCause() for details."); e1.initCause(e.getCause()); throw e1; } catch (final InterruptedException e) { final IOException e1 = new IOException( "Waiting of the thread has been interrupted"); e1.initCause(e); throw e1; } catch (final TimeoutException e) { if (!this.writingResult.isDone()) { this.writingResult.cancel(true); } final IOException e1 = new IOException( "Waiting for the internal " + "thread to finish took more than [" + timeout + "] " + timeUnit); e1.initCause(e); throw e1; } } afterClose(); } } /** {@inheritDoc} */ @Override public final void write(final byte[] bytes) throws IOException { initializeIfNecessary(); if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { super.write(bytes); } } /** {@inheritDoc} */ @Override public final void write(final byte[] bytes, final int offset, final int length) throws IOException { initializeIfNecessary(); if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { super.write(bytes, offset, length); } } /** {@inheritDoc} */ @Override public final void write(final int bytetowr) throws IOException { initializeIfNecessary(); if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { super.write(bytetowr); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy