| /* Copyright (c) 2001-2010, 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.CharArrayReader; |
| import java.io.CharArrayWriter; |
| import java.io.IOException; |
| import java.io.Writer; |
| |
| /** |
| * @todo - finer-grained synchronization to reduce average |
| * potential monitor contention |
| */ |
| |
| /** |
| * Provides Closable semantics ordinarily missing in a |
| * {@link java.io.CharArrayWriter}. <p> |
| * |
| * Accumulates output in a character array that automatically grows as needed.<p> |
| * |
| * Data is retrieved using <tt>toCharArray()</tt>, <tt>toCharArrayReader()</tt> |
| * and <tt>toString()</tt>. <p> |
| * |
| * {@link #close() Closing} a <tt>ClosableCharArrayWriter</tt> prevents |
| * further write operations, but all other operations will succeed until after |
| * the first invocation of {@link #free() free()}.<p> |
| * |
| * Freeing a <tt>ClosableCharArrayWriter</tt> closes the writer and |
| * releases its 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 writer must be |
| * handed off to an extenal client under contract that the writer should |
| * exhibit true Closable behaviour, both in response to internally tracked |
| * events and to client invocation of the <tt>Writer.close()</tt> method. |
| * |
| * @author boucherb@users |
| * @version 1.8.x |
| * @since 1.8.x |
| */ |
| public class ClosableCharArrayWriter extends Writer { |
| |
| /** |
| * Data buffer. |
| */ |
| protected char[] buf; |
| |
| /** |
| * # of valid characters in buffer. |
| */ |
| protected int count; |
| |
| /** |
| * Whether this writer is closed. |
| */ |
| protected boolean closed; |
| |
| /** |
| * Whether this writer is freed. |
| */ |
| protected boolean freed; |
| |
| /** |
| * Creates a new writer. <p> |
| * |
| * The buffer capacity is initially 32 characters, although its size |
| * automatically increases when necessary. |
| */ |
| public ClosableCharArrayWriter() { |
| this(32); |
| } |
| |
| /** |
| * Creates a new writer with a buffer capacity of the specified |
| * <tt>size</tt>, in characters. |
| * |
| * @param size the initial size. |
| * @exception IllegalArgumentException if <tt>size</tt> is negative. |
| */ |
| public ClosableCharArrayWriter(int size) throws IllegalArgumentException { |
| |
| if (size < 0) { |
| throw new IllegalArgumentException("Negative initial size: " |
| + size); // NOI18N |
| } |
| |
| buf = new char[size]; |
| } |
| |
| /** |
| * Writes the specified single character. |
| * |
| * @param c the single character to be written. |
| * @throws java.io.IOException if an I/O error occurs. |
| * In particular, an <tt>IOException</tt> may be thrown |
| * if this writer has been {@link #close() closed}. |
| */ |
| public synchronized void write(int c) throws IOException { |
| |
| checkClosed(); |
| |
| int newcount = count + 1; |
| |
| if (newcount > buf.length) { |
| buf = copyOf(buf, Math.max(buf.length << 1, newcount)); |
| } |
| |
| buf[count] = (char) c; |
| count = newcount; |
| } |
| |
| /** |
| * Writes the designated portion of the designated character array <p>. |
| * |
| * @param c the source character sequence. |
| * @param off the start offset in the source character sequence. |
| * @param len the number of characters to write. |
| * @throws java.io.IOException if an I/O error occurs. |
| * In particular, an <tt>IOException</tt> may be thrown |
| * if this writer has been {@link #close() closed}. |
| */ |
| public synchronized void write(char c[], int off, |
| int len) throws IOException { |
| |
| checkClosed(); |
| |
| if ((off < 0) || (off > c.length) || (len < 0) |
| || ((off + len) > c.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(c, off, buf, count, len); |
| |
| count = newcount; |
| } |
| |
| /** |
| * Efficiently writes the designated portion of the designated string. <p> |
| * |
| * The operation occurs as if by calling |
| * <tt>str.getChars(off, off + len, buf, count)</tt>. <p> |
| * |
| * @param str the string from which to write |
| * @param off the start offset in the string. |
| * @param len the number of characters to write. |
| * @throws java.io.IOException if an I/O error occurs. |
| * In particular, an <tt>IOException</tt> may be thrown |
| * if this writer has been {@link #close() closed}. |
| */ |
| public synchronized void write(String str, int off, |
| int len) throws IOException { |
| |
| checkClosed(); |
| |
| int strlen = str.length(); |
| |
| if ((off < 0) || (off > strlen) || (len < 0) || ((off + len) > strlen) |
| || ((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)); |
| } |
| |
| str.getChars(off, off + len, buf, count); |
| |
| count = newcount; |
| } |
| |
| /** |
| * 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 writer has been {@link #close() closed}. |
| */ |
| public void flush() throws IOException { |
| checkClosed(); |
| } |
| |
| /** |
| * Writes the complete contents of this writer's buffered data to the |
| * specified writer. <p> |
| * |
| * The operation occurs as if by calling <tt>out.write(buf, 0, count)</tt>. |
| * |
| * @param out the writer 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 writer has been {@link #free() freed}. |
| */ |
| public synchronized void writeTo(Writer out) throws IOException { |
| |
| checkFreed(); |
| |
| if (count > 0) { |
| out.write(buf, 0, count); |
| } |
| } |
| |
| /** |
| * Returns the current capacity of this writer's data buffer. |
| * |
| * @return the current capacity (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 writer has been {@link #free() freed}. |
| */ |
| public synchronized int capacity() throws IOException { |
| |
| checkFreed(); |
| |
| return buf.length; |
| } |
| |
| /** |
| * Resets the <tt>count</tt> field of this writer to zero, so that all |
| * currently accumulated output 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 writer's buffer capacity to its current size. <p> |
| * |
| * If the buffer is larger than necessary to hold its current sequence of |
| * characters, 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. |
| */ |
| public synchronized void trimToSize() throws IOException { |
| |
| checkFreed(); |
| |
| if (buf.length > count) { |
| buf = copyOf(buf, count); |
| } |
| } |
| |
| /** |
| * Creates a newly allocated character array. Its size is the current |
| * size of this writer and the valid contents of the buffer |
| * have been copied into it. |
| * |
| * @return the current contents of this writer, as a character array. |
| * @see #size() |
| * @throws java.io.IOException if an I/O error occurs. |
| * In particular, an <tt>IOException</tt> may be thrown |
| * if this writer has been {@link #free() freed}. |
| */ |
| public synchronized char[] toCharArray() throws IOException { |
| |
| checkFreed(); |
| |
| return copyOf(buf, count); |
| } |
| |
| /** |
| * Returns the current size of this writer's accumulated character data. |
| * |
| * @return the value of the <tt>count</tt> field, which is the number |
| * of valid characters accumulated in this writer. |
| * @see #count |
| * @throws java.io.IOException never |
| */ |
| public synchronized int size() throws IOException { |
| return count; |
| } |
| |
| /** |
| * Sets the size of this writer's accumulated character data. <p> |
| * |
| * @param newSize the new size of this writer's accumulated data |
| * @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 character data |
| * accumulated in this writer to a reader. <p> |
| * |
| * To ensure the integrity of the resulting reader, {@link #free() |
| * free} is invoked upon this writer as a side-effect. |
| * |
| * @return a reader representing this writer's accumulated |
| * character data |
| * @throws java.io.IOException if an I/O error occurs. |
| * In particular, an <tt>IOException</tt> may be thrown |
| * if this writer has been {@link #free() freed}. |
| */ |
| public synchronized CharArrayReader toCharArrayReader() |
| throws IOException { |
| |
| checkFreed(); |
| |
| CharArrayReader reader = new CharArrayReader(buf, 0, count); |
| |
| //System.out.println("toCharArrayReader::buf.length: " + buf.length); |
| free(); |
| |
| return reader; |
| } |
| |
| /** |
| * Converts this writer's accumulated data into a string. |
| * |
| * @return String constructed from this writer's accumulated data |
| * @throws RuntimeException may be thrown if this writer 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); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * @return <tt>true</tt> if this writer is closed, else <tt>false</tt> |
| */ |
| public synchronized boolean isClosed() { |
| return closed; |
| } |
| |
| /** |
| * 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 writer (default: never). |
| */ |
| public synchronized void free() throws IOException { |
| |
| closed = true; |
| freed = true; |
| buf = null; |
| count = 0; |
| } |
| |
| /** |
| * @return <tt>true</tt> if this writer is freed; else <tt>false</tt>. |
| */ |
| public synchronized boolean isFreed() { |
| return freed; |
| } |
| |
| /** |
| * @throws java.io.IOException if this writer is closed. |
| */ |
| protected synchronized void checkClosed() throws IOException { |
| |
| if (closed) { |
| throw new IOException("writer is closed."); // NOI18N |
| } |
| } |
| |
| /** |
| * @throws java.io.IOException if this writer is freed. |
| */ |
| protected synchronized void checkFreed() throws IOException { |
| |
| if (freed) { |
| throw new IOException("write buffer is freed."); // NOI18N |
| } |
| } |
| |
| /** |
| * 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 char[] copyOf(char[] original, int newLength) { |
| |
| char[] copy = new char[newLength]; |
| |
| System.arraycopy(original, 0, copy, 0, |
| Math.min(original.length, newLength)); |
| |
| return copy; |
| } |
| } |