Java tutorial
package com.gc.iotools.stream.is; /* * Copyright (c) 2008, 2014 Gabriele Contini. This source code is released * under the BSD License. */ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import com.gc.iotools.stream.base.AbstractInputStreamWrapper; import com.gc.iotools.stream.base.EasyStreamConstants; /** * <p> * Copies the data from the underlying <code>InputStream</code> to the * <code>OutputStream(s)</code> passed in the constructor. The data copied are * similar to the underlying <code>InputStream</code>. * </p> * <p> * When the method <code>{@link #close()}</code> is invoked all the bytes * remaining in the underlying <code>InputStream</code> are copied to the * <code>OutputStream(s)</code>. This behavior is different from this class * and {@code TeeInputStream} in Apache commons-io. * </p> * <p> * Bytes skipped are in any case copied to the <code>OutputStream</code>. Mark * and reset of the outer <code>InputStream</code> doesn't affect the data * copied to the <code>OutputStream(s)</code>, that remain similar to the * <code>InputStream</code> passed in the constructor. * </p> * <p> * It also calculate some statistics on the read/write operations. * {@link #getWriteTime()} returns the time spent writing to the * OutputStreams, {@link #getReadTime()} returns the time spent reading from * the InputStream and {@link TeeInputStreamOutputStream#getWriteSize()} * returns the amount of data written to a single <code>OutputStream</code> * until now. * </p> * <p> * Sample usage: * </p> * * <pre> * InputStream source=... //some data to be read. * ByteArrayOutputStream destination1= new ByteArrayOutputStream(); * ByteArrayOutputStream destination2= new ByteArrayOutputStream(); * * TeeInputStreamOutputStream tee= * new TeeInputStreamOutputStream(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 TeeInputStreamOutputStream while in * //destination2 they were copied by IOUtils. * byte[] bytes=destination1.getBytes(); * </pre> * * @see org.apache.commons.io.input.TeeInputStream * @author dvd.smnt * @since 1.0.6 * @version $Id$ */ public class TeeInputStreamOutputStream extends AbstractInputStreamWrapper { /** * If <code>true</code> <code>source</code> and <code>destination</code> * streams are closed when {@link #close()} is invoked. */ protected final boolean closeStreams; protected final boolean[] copyEnabled; private long destinationPosition = 0; /** * The destination <code>OutputStream</code> where data is written. */ protected final OutputStream[] destinations; private long markPosition = 0; private long readTime = 0; private long sourcePosition = 0; private final long[] writeTime; /** * <p> * Creates a <code>TeeInputStreamOutputStream</code> and saves its * argument, the input stream <code>source</code> and the output stream * <code>destination</code> for later use. * </p> * <p> * This constructor allow to specify multiple <code>OutputStream</code> to * which the data will be copied. * </p> * * @since 1.2.3 * @param source * The underlying <code>InputStream</code> * @param closeStreams * if <code>true</code> the <code>destination</code> will be * closed when the {@link #close()} method is invoked. If * <code>false</code> the close method on the underlying * streams will not be called (it must be invoked externally). * @param destinations * Data read from <code>source</code> are also written to this * <code>OutputStream</code>. */ public TeeInputStreamOutputStream(final InputStream source, final boolean closeStreams, final OutputStream... destinations) { super(source); if (destinations == null) { throw new IllegalArgumentException("Destinations OutputStream can't be null"); } if (destinations.length == 0) { throw new IllegalArgumentException("At least one destination OutputStream must be specified"); } for (final OutputStream destination : destinations) { if (destination == null) { throw new IllegalArgumentException("One of the outputstreams 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); } /** * <p> * Creates a <code>TeeOutputStream</code> and saves its argument, the * input stream <code>source</code> and the <code>OutputStream</code> * <code>destination</code> for later use. * </p> * <p> * When the method {@link #close()} it is invoked the remaining content of * the <code>source</code> stream is copied to the * <code>destination</code> and the <code>source</code> and * <code>destination</code> streams are closed. * </p> * * @param source * The underlying <code>InputStream</code> * @param destination * Data read from <code>source</code> are also written to this * <code>OutputStream</code>. */ public TeeInputStreamOutputStream(final InputStream source, final OutputStream destination) { this(source, destination, true); } /** * Creates a <code>TeeOutputStream</code> and saves its argument, the * input stream <code>source</code> and the output stream * <code>destination</code> for later use. * * @since 1.2.0 * @param source * The underlying <code>InputStream</code> * @param destination * Data read from <code>source</code> are also written to this * <code>OutputStream</code>. * @param closeStreams * if <code>true</code> the <code>destination</code> will be * closed when the {@link #close()} method is invoked. If * <code>false</code> the close method on the underlying * streams will not be called (it must be invoked externally). */ public TeeInputStreamOutputStream(final InputStream source, final OutputStream destination, final boolean closeStreams) { this(source, closeStreams, new OutputStream[] { destination }); } /** {@inheritDoc} */ @Override public int available() throws IOException { return this.source.available(); } /** * {@inheritDoc} * * <p> * This method is called when the method {@link #close()} is invoked. It * copies all the data eventually remaining in the source * <code>InputStream</code> passed in the constructor to the destination * <code>OutputStream</code>. * </p> * <p> * The standard behavior is to close both the underlying * <code>InputStream</code> and <code>OutputStream(s)</code>. When the * class was constructed with the parameter * {@link TeeInputStreamOutputStream#closeCalled closeCalled} set to false * the underlying streams must be closed externally. * @see #close() */ @Override public void closeOnce() throws IOException { IOException e1 = null; try { final byte[] buffer = new byte[EasyStreamConstants.SKIP_BUFFER_SIZE]; while (innerRead(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 " + "OutputStream(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; } } /** * <p> * Allow to switch off the copy to the underlying * <code>OutputStream</code>s. Setting the parameter to false will disable * the copy to all the underlying streams at once. * </p> * <p> * If you need more fine grained control you should use * {@link #enableCopy(boolean[])} . * </p> * * @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); } /** * <p> * Allow to switch off the copy to the underlying * <code>OutputStream</code>s, selectively enabling or disabling copy to * some specific stream. * </p> * <p> * The copy is enabled by default. Each element in the array correspond to * an <code>OutputStream</code> passed in the constructor. If the * correspondent element in the array passed as a parameter is set to * <code>true</code> the copy will be enabled. It can be invoked multiple * times. * </p> * * @since 1.2.9 * @param enable * whether to copy or not the bytes to the underlying * <code>OutputStream</code>s. */ 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 OutputStream 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]; } } /** * <p> * Returns the <code>OutputStream</code>(s) passed in the constructor. * </p> * * @since 1.2.9 * @return Array of OutputStream passed in the constructor. */ public final OutputStream[] getDestinationStreams() { return this.destinations; } /** * <p> * Returns the number of milliseconds spent reading from the * <code>source</code> <code>InputStream</code>. * </p> * * @return number of milliseconds spent reading from the * <code>source</code> . * @since 1.2.5 */ public long getReadTime() { return this.readTime; } /** * <p> * Returns the number of bytes written until now to a single destination * <code>OutputStream</code>. * </p> * <p> * This number is not affected by any of the mark and reset that are made * on this {@linkplain TeeInputStreamOutputStream} and reflects only the * number of bytes written. * </p> * * @return number of bytes written until now to a single * <code>destination</code>. * @since 1.2.5 */ public long getWriteSize() { return this.destinationPosition; } /** * <p> * Return the time spent writing on the destination * <code>OutputStream(s)</code> in milliseconds. * </p> * <p> * The returned array has one element for each <code>OutputStream</code> * passed in the constructor. * </p> * * @return time spent writing on the destination * <code>OutputStreams</code>. */ public long[] getWriteTime() { return this.writeTime; } /** {@inheritDoc} */ @Override public int innerRead(final byte[] 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} * * <p> * Marks the current position in this input stream. A subsequent call to * the <code>reset</code> method repositions this stream at the last * marked position so that subsequent reads re-read the same bytes. * </p> * @see #reset() * @see java.io.InputStream#mark(int) * @since 1.2.0 */ @Override public void mark(final int readLimit) { 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} * * <p> * Repositions this stream to the position at the time the * <code>mark</code> method was last called on this input stream. * </p> * <p> * After <code>reset()</code> method is called the data is not copied * anymore to the destination <code>OutputStream</code> until the position * where <code>reset</code> was issued is reached again. This ensures the * data copied to the destination <code>OutputStream</code> reflects the * data contained in the source InputStream (the one passed in the * constructor). * </p> * @see #mark(int) * @see java.io.InputStream#reset() * @exception IOException * If the source stream has an exception in calling * reset(). * @since 1.2.0 */ @Override public synchronized void reset() throws IOException { this.source.reset(); this.sourcePosition = this.markPosition; } }