Provides true Closable semantics ordinarily missing in a {@link java.io.ByteArrayOutputStream}. : OutputStream « File « Java Tutorial






/* Copyright (c) 2001-2009, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */



import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/* $Id: ClosableByteArrayOutputStream.java 2946 2009-03-22 17:44:48Z fredt $ */

/**
 * @todo - finer-grained synchronization to reduce average
 * potential monitor contention
 */

/**
 * Provides true Closable semantics ordinarily missing in a
 * {@link java.io.ByteArrayOutputStream}. 
 *
 * Accumulates output in a byte array that automatically grows as needed.
 *
 * Data is retrieved using <tt>toByteArray()</tt>,
 * <tt>toByteArrayInputStream()</tt>, <tt>toString()</tt> and
 * <tt>toString(encoding)</tt>. 
 *
 * {@link #close() Closing} a <tt>ClosableByteArrayOutputStream</tt> prevents
 * further write operations, but all other operations may succeed until after
 * the first invocation of {@link #free() free()}.
 *
 * Freeing a <tt>ClosableByteArrayOutputStream</tt> closes the stream and
 * releases the internal buffer, preventing successful invocation of all
 * operations, with the exception of <tt>size()<tt>, <tt>close()</tt>,
 * <tt>isClosed()</tt>, <tt>free()</tt> and <tt>isFreed()</tt>. 
 *
 * This class is especially useful when an accumulating output stream must be
 * handed off to an extenal client under contract that the stream should
 * exhibit true Closable behaviour in response both to internally tracked
 * events and to client invocation of the <tt>OutputStream.close()</tt> method.
 *
 * @author boucherb@users
 * @version 1.9.0
 * @since 1.9.0
 */
public class ClosableByteArrayOutputStream extends OutputStream {

    /**
     * Data buffer.
     */
    protected byte[] buf;

    /**
     * # of valid bytes in buffer.
     */
    protected int count;

    /**
     * Whether this stream is closed.
     */
    protected boolean closed;

    /**
     * Whether this stream is freed.
     */
    protected boolean freed;

    /**
     * Creates a new output stream. 
     *
     * The buffer capacity is initially 32 bytes, though its size increases
     * if necessary.
     */
    public ClosableByteArrayOutputStream() {
        this(32);
    }

    /**
     * Creates a new output stream with a buffer capacity of the specified
     * <tt>size</tt>, in bytes.
     *
     * @param size the initial size.
     * @exception IllegalArgumentException if size is negative.
     */
    public ClosableByteArrayOutputStream(int size)
    throws IllegalArgumentException {

        if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);    // NOI18N
        }

        buf = new byte[size];
    }

    /**
     * Writes the specified single byte.
     *
     * @param b the single byte to be written.
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #close() closed}.
     */
    public synchronized void write(int b) throws IOException {

        checkClosed();

        int newcount = count + 1;

        if (newcount > buf.length) {
            buf = copyOf(buf, Math.max(buf.length << 1, newcount));
        }

        buf[count] = (byte) b;
        count      = newcount;
    }

    /**
     * Writes the specified portion of the designated octet sequence. 
     *
     * @param b the data.
     * @param off the start offset in the data.
     * @param len the number of bytes to write.
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #close() closed}.
     */
    public synchronized void write(byte b[], int off,
                                   int len) throws IOException {

        checkClosed();

        if ((off < 0) || (off > b.length) || (len < 0)
                || ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }

        int newcount = count + len;

        if (newcount > buf.length) {
            buf = copyOf(buf, Math.max(buf.length << 1, newcount));
        }

        System.arraycopy(b, off, buf, count, len);

        count = newcount;
    }

    /**
     * By default, does nothing. 
     *
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #close() closed}.
     */
    public void flush() throws IOException {
        checkClosed();
    }

    /**
     * Writes the complete contents of this stream's accumulated data to the
     * specified output stream. 
     *
     * The operation occurs as if by calling <tt>out.write(buf, 0, count)</tt>.
     *
     * @param out the output stream to which to write the data.
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #free() freed}.
     */
    public synchronized void writeTo(OutputStream out) throws IOException {
        checkFreed();
        out.write(buf, 0, count);
    }

    /**
     * Returns the current capacity of this stream's data buffer.
     *
     * @return  the length of the internal data array
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #free() freed}.
     */
    public synchronized int capacity() throws IOException {

        checkFreed();

        return buf.length;
    }

    /**
     * Resets the <tt>count</tt> field of this output stream to zero, so that
     * all currently accumulated data is effectively discarded. 
     *
     * Further write operations will reuse the allocated buffer space. 
     *
     * @see #count
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #close() closed}.
     */
    public synchronized void reset() throws IOException {

        checkClosed();

        count = 0;
    }

    /**
     * Attempts to reduce this stream's capacity to its current size. 
     *
     * If the data buffer is larger than necessary to hold its current sequence
     * of bytes, then it may be resized to become more space efficient.
     * Calling this method may, but is not required to, affect the value
     * returned by a subsequent call to the {@link #capacity()} method. 
     *
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #free() freed}.
     */
    public synchronized void trimToSize() throws IOException {

        checkFreed();

        if (buf.length > count) {
            buf = copyOf(buf, count);
        }
    }

    /**
     * Retrieves a copy of this stream's accumated data, as a byte array.
     *
     * @return a copy of this stream's accumated data, as a byte array.
     * @see #size()
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #free() freed}.
     */
    public synchronized byte[] toByteArray() throws IOException {

        checkFreed();

        return copyOf(buf, count);
    }

    /**
     * Returns the current size of this stream's accumated data.
     *
     * @return the value of the <tt>count</tt> field, which is the number
     *      of valid bytes in this output stream.
     * @see #count
     * @throws java.io.IOException never
     */
    public synchronized int size() throws IOException {
        return count;
    }

    /**
     * Sets the size of this stream's accumulated data. 
     *
     * @param   newSize the new size
     * @throws  ArrayIndexOutOfBoundsException if new size is negative
     */
    public synchronized void setSize(int newSize) {

        if (newSize < 0) {
            throw new ArrayIndexOutOfBoundsException(newSize);
        } else if (newSize > buf.length) {
            buf = copyOf(buf, Math.max(buf.length << 1, newSize));
        }

        count = newSize;
    }

    /**
     * Performs an effecient (zero-copy) conversion of the data accumulated in
     * this output stream to an input stream. 
     *
     * To ensure the future integrity of the resulting input stream, {@link
     * #free() free} is invoked upon this output stream as a side-effect.
     *
     * @return an input stream representing this output stream's accumulated
     *      data
     * @throws java.io.IOException if an I/O error occurs.
     *      In particular, an <tt>IOException</tt> may be thrown
     *      if this output stream has been {@link #free() freed}.
     */
    public synchronized ByteArrayInputStream toByteArrayInputStream()
    throws IOException {

        checkFreed();

        ByteArrayInputStream inputStream = new ByteArrayInputStream(buf, 0,
            count);

        free();

        return inputStream;
    }

    /**
     * Converts this stream's accumuated data into a string, translating bytes
     * into characters according to the platform's default character encoding.
     *
     * @return String translated from this stream's accumuated data.
     * @throws RuntimeException may be thrown if this output stream has been
     *      {@link #free() freed}.
     */
    public synchronized String toString() {

        try {
            checkFreed();
        } catch (IOException ex) {
            throw new RuntimeException(ex.toString());
        }

        return new String(buf, 0, count);
    }

    /**
     * Converts this stream's accumuated data into a string, translating bytes
     * into characters according to the specified character encoding.
     *
     * @return String translated from the buffer's contents.
     * @param enc a character-encoding name.
     * @throws java.io.IOException may be thrown if this output stream has been
     *      {@link #free() freed}.
     * @throws UnsupportedEncodingException If the named encoding is not
     *      supported.
     */
    public synchronized String toString(String enc)
    throws IOException, UnsupportedEncodingException {

        checkFreed();

        return new String(buf, 0, count, enc);
    }

    /**
     * Closes this object for further writing. 
     *
     * Other operations may continue to succeed until after the first invocation
     * of {@link #free() free()}. 
     *
     * @throws java.io.IOException if an I/O error occurs (default: never)
     */
    public synchronized void close() throws IOException {
        closed = true;
    }

    /**
     * Retrieves whether this stream is closed. 
     * @return <tt>true</tt> if this stream is closed, else <tt>false</tt>
     */
    public synchronized boolean isClosed() {
        return closed;
    }

    /**
     * Closes this object and releases the underlying buffer for
     * garbage collection. 
     *
     * @throws java.io.IOException if an I/O error occurs while closing
     *      this stream (default: never).
     */
    public synchronized void free() throws IOException {

        closed = true;
        freed  = true;
        buf    = null;
        count  = 0;
    }

    /**
     * Retrieves whether this stream is freed. 
     *
     * @return <tt>true</tt> if this stream is freed; else <tt>false</tt>.
     */
    public synchronized boolean isFreed() {
        return freed;
    }

    /**
     * Tests whether this stream is closed. 
     *
     * @throws java.io.IOException if this stream is closed.
     */
    protected synchronized void checkClosed() throws IOException {

        if (closed) {

            throw new IOException("stream is closed.");    // NOI18N
        }
    }

    /**
     * Tests whether this stream is freed. 
     *
     * @throws java.io.IOException if this stream is freed.
     */
    protected synchronized void checkFreed() throws IOException {

        if (freed) {
            throw new IOException("stream buffer is freed.");    // NOI18N
        }
    }

    /**
     * Retrieves a copy of <tt>original</tt> with the given
     * <tt>newLength</tt>. 
     *
     * @param original the object to copy
     * @param newLength the length of the copy
     * @return copy of <tt>original</tt> with the given <tt>newLength</tt>
     */
    protected byte[] copyOf(byte[] original, int newLength) {

        byte[] copy = new byte[newLength];

        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));

        return copy;
    }
}








11.13.OutputStream
11.13.1.Byte Counting OutputStream
11.13.2.Memory Byte Array OutputStream
11.13.3.A null output stream. All data written to this stream is ignored.
11.13.4.Provides true Closable semantics ordinarily missing in a {@link java.io.ByteArrayOutputStream}.
11.13.5.Write the entire contents of the supplied string to the given stream. This method always flushes and closes the stream when finished.
11.13.6.Transfers all bytes that can be read from one stream to another stream.