com.gc.iotools.stream.os.TeeOutputStream.java Source code

Java tutorial

Introduction

Here is the source code for com.gc.iotools.stream.os.TeeOutputStream.java

Source

package com.gc.iotools.stream.os;

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

/**
 * <p>
 * Copies the data that is written to this class to the array of
 * <code>OutputStream</code> passed in the constructor, allowing to write to
 * multiple <code>OutputStream</code> at once. It also collect statistics on the
 * operations done (time spent writing to the internal streams, amount of data
 * written).
 * </p>
 * <p>
 * Usage:
 * </p>
 * 
 * <pre>
 *     InputStream source=... //some data to be read.
 *   ByteArrayOutputStream destination1= new ByteArrayOutputStream();
 *   ByteArrayOutputStream destination2= new ByteArrayOutputStream();
 * 
 *   TeeOutputStream tee =  new TeeOutputStream(destination1,destination2);
 *   org.apache.commons.io.IOUtils.copy(source,tee);
 *   tee.close();
 *   //at this point both destination1 and destination2 contains the same bytes.
 * </pre>
 * 
 * @author dvd.smnt
 * @since 1.2.4
 * @version $Id: TeeOutputStream.java 527 2014-02-24 19:29:50Z
 *          gabriele.contini@gmail.com $
 */
public class TeeOutputStream extends OutputStream {

    /**
     * <code>True</code> when {@link #close()} is invoked. Prevents data from
     * being written to the destination <code>OutputStream(s)</code> after
     * {@link #close()} has been invoked.
     */
    protected boolean closeCalled = false;
    private final boolean[] copyEnabled;
    /**
     * The destination <code>OutputStream(s)</code> where data is written.
     */
    protected final OutputStream[] destinations;

    private long size = 0;
    private final long[] writeTime;

    /**
     * <p>
     * Creates a <code>TeeOutputStream</code> and saves its arguments, the
     * <code>destinations</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.4
     * @param destinations
     *            Data written to this <code>OutputStream</code> are copied to
     *            all the <code>destinations</code>.
     */
    public TeeOutputStream(final OutputStream... destinations) {
        checkDestinations(destinations);
        this.writeTime = new long[destinations.length];
        this.destinations = destinations;
        this.copyEnabled = new boolean[destinations.length];
        Arrays.fill(this.copyEnabled, true);
    }

    private void checkDestinations(final OutputStream... dests) {
        if (dests == null) {
            throw new IllegalArgumentException("Destinations OutputStream can't be null");
        }
        if (dests.length == 0) {
            throw new IllegalArgumentException("At least one destination OutputStream must be specified");
        }
        for (final OutputStream destination : dests) {
            if (destination == null) {
                throw new IllegalArgumentException("One of the outputstreams in the array is null");
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    public void close() throws IOException {
        if (!this.closeCalled) {
            this.closeCalled = true;
            for (int i = 0; i < this.destinations.length; i++) {
                final OutputStream stream = this.destinations[i];
                final long start = System.currentTimeMillis();
                stream.close();
                this.writeTime[i] += System.currentTimeMillis() - start;
            }
        }
    }

    /**
     * <p>
     * Allow to switch off the copy to the underlying streams. The copy is
     * enabled by default. 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.8
     * @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 streams, selectively
     * enabling or disabling copy on 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 stream.
     */
    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];
        }
    }

    /** {@inheritDoc} */
    @Override
    public void flush() throws IOException {
        if (!this.closeCalled) {
            for (int i = 0; i < this.destinations.length; i++) {
                final OutputStream stream = this.destinations[i];
                final long start = System.currentTimeMillis();
                stream.flush();
                this.writeTime[i] += System.currentTimeMillis() - start;
            }
        }
    }

    /**
     * <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>
     * This method returns the size in bytes of the data written to this
     * OutputStream. It can be used to collect statistics on the write
     * operations.
     * </p>
     * 
     * @return size in bytes of the data written to the
     *         <code>OutputStreams</code>.
     */
    public final long getSize() {
        return this.size;
    }

    /**
     * <p>
     * Return the time spent writing to 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 void write(final byte[] b) throws IOException {
        if (b == null) {
            throw new NullPointerException("Array of bytes can't be null");
        }
        if (!this.closeCalled) {
            for (int i = 0; i < this.destinations.length; i++) {
                if (this.copyEnabled[i]) {
                    final OutputStream stream = this.destinations[i];
                    final long start = System.currentTimeMillis();
                    stream.write(b);
                    this.writeTime[i] += System.currentTimeMillis() - start;
                }
            }
            this.size += b.length;
        } else {
            throw new IOException("Stream already closed.");
        }
    }

    /** {@inheritDoc} */
    @Override
    public void write(final byte[] b, final int off, final int len) throws IOException {
        if (b == null) {
            throw new NullPointerException("Array of bytes can't be null");
        }
        if (!this.closeCalled) {
            for (int i = 0; i < this.destinations.length; i++) {
                if (this.copyEnabled[i]) {
                    final OutputStream stream = this.destinations[i];
                    final long start = System.currentTimeMillis();
                    stream.write(b, off, len);
                    this.writeTime[i] += System.currentTimeMillis() - start;
                }
            }
            this.size += len;
        } else {
            throw new IOException("Stream already closed.");
        }
    }

    /** {@inheritDoc} */
    @Override
    public void write(final int b) throws IOException {
        if (!this.closeCalled) {
            for (int i = 0; i < this.destinations.length; i++) {
                if (this.copyEnabled[i]) {
                    final OutputStream stream = this.destinations[i];
                    final long start = System.currentTimeMillis();
                    stream.write(b);
                    this.size++;
                    this.writeTime[i] += System.currentTimeMillis() - start;
                }
            }
            this.size++;
        } else {
            throw new IOException("Attempt to write to a closed stream.");
        }
    }
}