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

com.gc.iotools.stream.reader.TeeReaderWriter 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.reader;

/*
 * Copyright (c) 2008, 2015 Gabriele Contini. This source code is released
 * under the BSD License.
 */
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;

import com.gc.iotools.stream.base.EasyStreamConstants;

/**
 * 

* Copies the data from the underlying Reader to the * Writer(s) passed in the constructor. The data copied are * similar to the underlying Reader. *

*

* When the method {@link #close()} is invoked all the bytes * remaining in the underlying Reader are copied to the * Writer(s). This behavior is different from this class and * {@code TeeInputStream} in Apache commons-io. *

*

* Bytes skipped are in any case copied to the Writer. Mark and * reset of the outer Reader doesn't affect the data copied to * the Writer(s), that remain similar to the Reader * passed in the constructor. *

*

* It also calculate some statistics on the read/write operations. * {@link #getWriteTime()} returns the time spent writing to the Writers, * {@link #getReadTime()} returns the time spent reading from the Reader and * {@link #getWriteSize()} returns the amount of data written * to a single Writer until now. *

*

* Sample usage: *

* *
 * 	 Reader source=... //some data to be read.
 *   StringWriter destination1= new StringWriter();
 *   StringWriter destination2= new StringWriter();
 *
 *   TeeReaderWriter tee = new TeeReaderWriter(source,destination1);
 *   org.apache.commons.io.IOUtils.copy(tee,destination2);
 *   tee.close();
 *   //at this point both destination1 and destination2 contains the same bytes
 *   //in destination1 were put by TeeReaderWriter while in
 *   //destination2 they were copied by IOUtils.
 *   StringBuffer buffer=destination1.getBuffer();
 * 
* * @see org.apache.commons.io.input.TeeReader * @author dvd.smnt * @since 1.2.7 * @version $Id: TeeReaderWriter.java 576 2015-03-28 00:03:33Z gcontini $ */ public class TeeReaderWriter extends Reader { /** * If true source and destination * streams are closed when {@link #close()} is invoked. */ protected final boolean closeStreams; protected final boolean[] copyEnabled; private long destinationPosition = 0; /** * The destination Writers where data is written. */ protected final Writer[] destinations; private long markPosition = 0; private long readTime = 0; /** * The source Reader where the data comes from. */ protected final Reader source; private long sourcePosition = 0; private final long[] writeTime; /** *

* Creates a TeeInputStreamWriter and saves its argument, the * input stream source and the output stream * destination for later use. *

*

* This constructor allow to specify multiple Writer to which * the data will be copied. *

* * @since 1.2.7 * @param source * The underlying Reader * @param closeStreams * if true the destination will be * closed when the {@link #close()} method is invoked. If * false the close method on the underlying * streams will not be called (it must be invoked externally). * @param destinations * Data read from source are also written to this * Writer. */ public TeeReaderWriter(final Reader source, final boolean closeStreams, final Writer... destinations) { this.source = source; if (destinations == null) { throw new IllegalArgumentException( "Destinations Writer can't be null"); } if (destinations.length == 0) { throw new IllegalArgumentException( "At least one destination Writer must be specified"); } for (final Writer destination : destinations) { if (destination == null) { throw new IllegalArgumentException( "One of the Writers in the array is null"); } } this.writeTime = new long[destinations.length]; this.destinations = destinations; this.closeStreams = closeStreams; this.copyEnabled = new boolean[destinations.length]; Arrays.fill(this.copyEnabled, true); } /** *

* Creates a TeeReaderWriter and saves its argument, the * input stream source and the Writer * destination for later use. *

*

* When the method {@link #close()} it is invoked the remaining content of * the source stream is copied to the * destination and the source and * destination streams are closed. *

* * @param source * The underlying Reader * @param destination * Data read from source are also written to this * Writer. */ public TeeReaderWriter(final Reader source, final Writer destination) { this(source, destination, true); } /** * Creates a TeeReaderWriter and saves its argument, the * input stream source and the output stream * destination for later use. * * @since 1.2.7 * @param source * The underlying Reader * @param destination * Data read from source are also written to this * Writer. * @param closeStreams * if true the destination will be * closed when the {@link #close()} method is invoked. If * false the close method on the underlying * streams will not be called (it must be invoked externally). */ public TeeReaderWriter(final Reader source, final Writer destination, final boolean closeStreams) { this(source, closeStreams, new Writer[] { destination }); } /** * {@inheritDoc} * *

* This method is called when the method {@link #close()} is invoked. It * copies all the data eventually remaining in the source * Reader passed in the constructor to the destination * Writer. *

*

* The standard behavior is to close both the underlying * Reader and Writer(s). When the class was * constructed with the parameter {@link #closeStreams} * set to false the underlying streams must be closed * externally. * @see #close() */ @Override public void close() throws IOException { IOException e1 = null; try { final char[] buffer = new char[EasyStreamConstants.SKIP_BUFFER_SIZE]; while (read(buffer, 0, buffer.length) > 0) { // empty block: just throw bytes away } } catch (final IOException e) { e1 = new IOException( "Incomplete data was written to the destination " + "Writer(s)."); e1.initCause(e); } if (this.closeStreams) { final long startr = System.currentTimeMillis(); this.source.close(); this.readTime += System.currentTimeMillis() - startr; for (int i = 0; i < this.destinations.length; i++) { final long start = System.currentTimeMillis(); this.destinations[i].close(); this.writeTime[i] += System.currentTimeMillis() - start; } } if (e1 != null) { throw e1; } } /** *

* Allow to switch off the copy to the underlying * OutputStreams. Setting the parameter to false will disable * the copy to all the underlying streams at once. *

*

* If you need more fine grained control you should use * {@link #enableCopy(boolean[])} . *

* * @since 1.2.9 * @param enable * whether to copy or not the bytes to the underlying stream. */ public final void enableCopy(final boolean enable) { Arrays.fill(this.copyEnabled, enable); } /** *

* Allow to switch off the copy to the underlying * OutputStreams, selectively enabling or disabling copy to * some specific stream. *

*

* The copy is enabled by default. Each element in the array correspond to * an OutputStream passed in the constructor. If the * correspondent element in the array passed as a parameter is set to * true the copy will be enabled. It can be invoked multiple * times. *

* * @since 1.2.9 * @param enable * whether to copy or not the bytes to the underlying * OutputStreams. */ public final void enableCopy(final boolean[] enable) { if (enable == null) { throw new IllegalArgumentException("Enable array can't be null"); } if (enable.length != this.copyEnabled.length) { throw new IllegalArgumentException("Enable array must be of " + "the same size of the Writer array passed " + "in the constructor. Array size [" + enable.length + "] streams [" + this.copyEnabled.length + "]"); } for (int i = 0; i < enable.length; i++) { this.copyEnabled[i] = enable[i]; } } /** *

* Returns the number of milliseconds spent reading from the * source Reader. *

* * @return number of milliseconds spent reading from the * source . * @since 1.2.7 */ public long getReadTime() { return this.readTime; } /** *

* Returns the number of bytes written until now to a single destination * Writer. *

*

* This number is not affected by any of the mark and reset that are made * on this {@linkplain TeeReaderWriter} and reflects only the number of * bytes written. *

* * @return number of bytes written until now to a single * destination. * @since 1.2.7 */ public long getWriteSize() { return this.destinationPosition; } /** *

* Return the time spent writing on the destination Writer(s) * in milliseconds. *

*

* The returned array has one element for each Writer passed * in the constructor. *

* * @return time spent writing on the destination Writers. */ public long[] getWriteTime() { return this.writeTime; } /** * {@inheritDoc} * *

* Marks the current position in this input stream. A subsequent call to * the reset method repositions this stream at the last * marked position so that subsequent reads re-read the same bytes. *

* @see #reset() * @see java.io.Reader#mark(int) * @since 1.2.7 */ @Override public void mark(final int readLimit) throws IOException { this.source.mark(readLimit); this.markPosition = this.sourcePosition; } /** {@inheritDoc} */ @Override public boolean markSupported() { return this.source.markSupported(); } /** {@inheritDoc} */ @Override public int read() throws IOException { final long startr = System.currentTimeMillis(); final int result = this.source.read(); this.readTime += System.currentTimeMillis() - startr; if (result >= 0) { this.sourcePosition++; if (this.sourcePosition > this.destinationPosition) { for (int i = 0; i < this.destinations.length; i++) { if (this.copyEnabled[i]) { final long start = System.currentTimeMillis(); this.destinations[i].write(result); getWriteTime()[i] += System.currentTimeMillis() - start; } } this.destinationPosition++; } } return result; } /** {@inheritDoc} */ @Override public int read(final char[] b, final int off, final int len) throws IOException { final long startr = System.currentTimeMillis(); final int result = this.source.read(b, off, len); this.readTime += System.currentTimeMillis() - startr; if (result > 0) { if (this.sourcePosition + result > this.destinationPosition) { final int newLen = (int) (this.sourcePosition + result - this.destinationPosition); final int newOff = off + (result - newLen); for (int i = 0; i < this.destinations.length; i++) { if (this.copyEnabled[i]) { final long start = System.currentTimeMillis(); this.destinations[i].write(b, newOff, newLen); getWriteTime()[i] += System.currentTimeMillis() - start; } } this.destinationPosition += newLen; } this.sourcePosition += result; } return result; } /** * {@inheritDoc} * *

* Repositions this stream to the position at the time the * mark method was last called on this input stream. *

*

* After reset() method is called the data is not copied * anymore to the destination Writer until the position where * reset was issued is reached again. This ensures the data * copied to the destination Writer reflects the data * contained in the source Reader (the one passed in the constructor). *

* @see #mark(int) * @see java.io.Reader#reset() * @exception IOException * If the source stream has an exception in calling * reset(). * @since 1.2.7 */ @Override public synchronized void reset() throws IOException { this.source.reset(); this.sourcePosition = this.markPosition; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy