/* Copyright (c) 2001-2019, 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.
 */


package org.hsqldb.lib;

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

/* $Id: ClosableByteArrayOutputStream.java 5969 2019-04-27 14:59:54Z fredt $ */

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

Provides true Closable semantics ordinarily missing in a ByteArrayOutputStream.

Accumulates output in a byte array that automatically grows as needed.

Data is retrieved using toByteArray(), toByteArrayInputStream(), toString() and toString(encoding).

Closing a ClosableByteArrayOutputStream prevents further write operations, but all other operations may succeed until after the first invocation of free().

Freeing a ClosableByteArrayOutputStream closes the stream and releases the internal buffer, preventing successful invocation of all operations, with the exception of size(), close(), isClosed(), free() and isFreed().

This class is especially useful when an accumulating output stream must be handed off to an external client under contract that the stream should exhibit true Closable behaviour in response both to internally tracked events and to client invocation of the OutputStream.close() method.

Author:Campbell Burnet (campbell-burnet@users dot sourceforge.net)
Version:1.9.0
Since:1.9.0
/** * Provides true Closable semantics ordinarily missing in a * {@link java.io.ByteArrayOutputStream}. <p> * * Accumulates output in a byte array that automatically grows as needed.<p> * * Data is retrieved using <tt>toByteArray()</tt>, * <tt>toByteArrayInputStream()</tt>, <tt>toString()</tt> and * <tt>toString(encoding)</tt>. <p> * * {@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()}.<p> * * 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>. <p> * * This class is especially useful when an accumulating output stream must be * handed off to an external 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 Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @version 1.9.0 * @since 1.9.0 */
public class ClosableByteArrayOutputStream extends OutputStream {
Data buffer.
/** * Data buffer. */
protected byte[] buf;
# of valid bytes in buffer.
/** * # of valid bytes in buffer. */
protected int count;
Whether this stream is closed.
/** * Whether this stream is closed. */
protected boolean closed;
Whether this stream is freed.
/** * 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.

/** * Creates a new output stream. <p> * * 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 size, in bytes.
Params:
  • size – the initial size.
Throws:
/** * 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.
Params:
  • b – the single byte to be written.
Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been closed.
/** * 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.

Params:
  • b – the data.
  • off – the start offset in the data.
  • len – the number of bytes to write.
Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been closed.
/** * Writes the specified portion of the designated octet sequence. <p> * * @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:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been closed.
/** * By default, does nothing. <p> * * @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 out.write(buf, 0, count).

Params:
  • out – the output stream to which to write the data.
Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been freed.
/** * Writes the complete contents of this stream's accumulated data to the * specified output stream. <p> * * 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.
Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been freed.
Returns: the length of the internal data array
/** * 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 count 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.

Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been closed.
See Also:
  • count
/** * Resets the <tt>count</tt> field of this output stream to zero, so that * all currently accumulated data is effectively discarded. <p> * * Further write operations will reuse the allocated buffer space. <p> * * @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 capacity() method.

Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been freed.
/** * Attempts to reduce this stream's capacity to its current size. <p> * * 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. <p> * * @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 accumulated data, as a byte array.
Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been freed.
See Also:
Returns:a copy of this stream's accumulated data, as a byte array.
/** * Retrieves a copy of this stream's accumulated data, as a byte array. * * @return a copy of this stream's accumulated 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 accumulated data.
Throws:
See Also:
Returns:the value of the count field, which is the number of valid bytes in this output stream.
/** * Returns the current size of this stream's accumulated 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.

Params:
  • newSize – the new size
Throws:
/** * Sets the size of this stream's accumulated data. <p> * * @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 efficient (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, free is invoked upon this output stream as a side-effect.

Throws:
  • IOException – if an I/O error occurs. In particular, an IOException may be thrown if this output stream has been freed.
Returns:an input stream representing this output stream's accumulated data
/** * Performs an efficient (zero-copy) conversion of the data accumulated in * this output stream to an input stream. <p> * * 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 accumulated data into a string, translating bytes into characters according to the platform's default character encoding.
Throws:
Returns:String translated from this stream's accumulated data.
/** * Converts this stream's accumulated data into a string, translating bytes * into characters according to the platform's default character encoding. * * @return String translated from this stream's accumulated 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 accumulated data into a string, translating bytes into characters according to the specified character encoding.
Params:
  • enc – a character-encoding name.
Throws:
Returns:String translated from the buffer's contents.
/** * Converts this stream's accumulated 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 free().

Throws:
  • IOException – if an I/O error occurs (default: never)
/** * Closes this object for further writing. <p> * * Other operations may continue to succeed until after the first invocation * of {@link #free() free()}. <p> * * @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.

Returns:true if this stream is closed, else false
/** * Retrieves whether this stream is closed. <p> * @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:
  • IOException – if an I/O error occurs while closing this stream (default: never).
/** * Closes this object and releases the underlying buffer for * garbage collection. <p> * * @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.

Returns:true if this stream is freed; else false.
/** * Retrieves whether this stream is freed. <p> * * @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:
  • IOException – if this stream is closed.
/** * Tests whether this stream is closed. <p> * * @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:
  • IOException – if this stream is freed.
/** * Tests whether this stream is freed. <p> * * @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 original with the given newLength.

Params:
  • original – the object to copy
  • newLength – the length of the copy
Returns:copy of original with the given newLength
/** * Retrieves a copy of <tt>original</tt> with the given * <tt>newLength</tt>. <p> * * @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; } }