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

com.gc.iotools.stream.writer.WriterToReader 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.writer;

/*
 * Copyright (c) 2008, 2015 Gabriele Contini. This source code is released
 * under the BSD License.
 */
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Reader;
import java.io.Writer;
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.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.reader.CloseShieldReader;
import com.gc.iotools.stream.utils.LogUtils;

/**
 * 

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

*

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

*

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

*

* The {@linkplain #doRead(Reader)} 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(Reader)} you shouldn't change the variables * of the outer class. *

*

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

*

* The method {@link #getResults()} 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: *

* *
 * WriterToReader<String> oStream2Reader =
 * new WriterToReader<String>() {
 * 	protected String doRead(final Reader reader) throws Exception {
 * 		// Users of this class should place all the code that need to read data
 *      // from the Reader in this method. Data available through the
 *      // Reader passed as a parameter is the data that is written to the
 * 		// Writer oStream2Reader through its write method.
 * 		final String result = IOUtils.toString(reader);
 * 		return result + " was processed.";
 * 	}
 * };
 * try {
 * 	// some data is written to the Writer, will be passed to the method
 * 	// doRead(Reader i) above and after close() is called the results
 * 	// will be available through the getResults() method.
 * 	oStream2Reader.write("test".getBytes());
 * } finally {
 * 	// don't miss the close (or a thread would not terminate correctly).
 * 	oStream2Reader.close();
 * }
 * String result = oStream2Reader.getResults();
 * //result now contains the string "test was processed."
 * 
* * @param * Type returned by the method {@link #getResults()} after the * thread has finished. * @since 1.2.7 * @author dvd.smnt * @version $Id: WriterToReader.java 576 2015-03-28 00:03:33Z gcontini $ */ public abstract class WriterToReader extends Writer { /** * This class executes in the second thread. * * @author dvd.smnt */ private final class DataConsumer implements Callable { private final Reader reader; DataConsumer(final Reader reader) { this.reader = reader; } @Override public synchronized T call() throws Exception { T processResult; try { // avoid the internal class close the stream. final CloseShieldReader reader = new CloseShieldReader( this.reader); processResult = doRead(reader); } catch (final Exception e) { WriterToReader.this.abort = true; throw e; } finally { emptyReader(); this.reader.close(); } return processResult; } private void emptyReader() { try { final char[] buffer = new char[EasyStreamConstants.SKIP_BUFFER_SIZE]; while (this.reader.read(buffer) >= 0) { ; // empty block: just throw bytes away } } catch (final IOException e) { if ((e.getMessage() != null) && (e.getMessage().indexOf("closed") > 0)) { WriterToReader.LOG.debug("Stream already closed"); } else { WriterToReader.LOG.error( "IOException while empty Reader a " + "thread can be locked", e); } } catch (final Throwable e) { WriterToReader.LOG.error("IOException while empty Reader a " + "thread can be locked", e); } } } // 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(WriterToReader.class); /** * Set the size for the pipe circular buffer. This setting has effect for * the newly created WriterToReader. Default is 4096 bytes. * * @since 1.2.3 * @param defaultPipeSize * The default pipe buffer size in bytes. */ public static void setDefaultPipeSize(final int defaultPipeSize) { WriterToReader.defaultPipeSize = defaultPipeSize; } private boolean abort = false; private boolean closeCalled = false; private final boolean joinOnClose; private final PipedWriter pipedWriter; private final Future writingResult; /** *

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

*

* 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 WriterToReader() { this(true, ExecutionModel.THREAD_PER_INSTANCE); } /** *

* Creates a new WriterToReader. 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 WriterToReader(final boolean joinOnClose, final ExecutionModel executionModel) { this(joinOnClose, ExecutorServiceFactory.getExecutor(executionModel)); } /** *

* Creates a new WriterToReader. 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 WriterToReader(final boolean joinOnClose, final ExecutorService executorService) { this(joinOnClose, executorService, defaultPipeSize); } /** *

* Creates a new WriterToReader. 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 WriterToReader(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()); this.pipedWriter = new PipedWriter(); final PipedReader pipedIS = new PipedReader(pipeBufferSize); try { pipedIS.connect(this.pipedWriter); } catch (final IOException e) { throw new IllegalStateException("Error during pipe creaton", e); } final DataConsumer executingProcess = new DataConsumer(pipedIS); this.joinOnClose = joinOnClose; LOG.debug("invoked by[{}] queued for start.", callerId); this.writingResult = executorService.submit(executingProcess); } /** {@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 Writer from the * Reader passed as a parameter. *

*

* Any exception eventually threw inside this method will be propagated to * the external Writer. When the next * {@linkplain #write(char[])} 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 reader * The Reader 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 * Writer and will be available calling the * method {@link #getResults()}. */ protected abstract T doRead(Reader reader) throws Exception; /** {@inheritDoc} */ @Override public final void flush() throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedWriter.flush(); } } /** *

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

*

* This method suspend the calling thread and waits for the function * {@link #doRead(Reader)} 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 ExecutionException * Thrown if the method {@linkplain #doRead(Reader)} threw * an Exception. The getCause() returns the * original Exception. * @throws java.lang.IllegalStateException * When it is called before the method {@link #close()} has * been called. * @return the object returned from the doRead() method. * @throws InterruptedException if the running thread is interrupted. * @throws ExecutionException if the internal method launched an exception. * @throws IllegalStateException if {@link #close()} was not called before. */ public final T getResults() throws InterruptedException, ExecutionException { if (!this.closeCalled) { throw new IllegalStateException("Method close() must be called" + " before getResults"); } return this.writingResult.get(); } private void internalClose(final boolean join, final TimeUnit timeUnit, final long timeout) throws IOException { if (!this.closeCalled) { this.closeCalled = true; this.pipedWriter.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; } } } } /** {@inheritDoc} */ @Override public final void write(final char[] bytes) throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedWriter.write(bytes); } } /** {@inheritDoc} */ @Override public final void write(final char[] bytes, final int offset, final int length) throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedWriter.write(bytes, offset, length); } } /** {@inheritDoc} */ @Override public final void write(final int chartowrite) throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedWriter.write(chartowrite); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy