/* Copyright (c) 2001-2018, 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.
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 Campbell Burnet (campbell-burnet@users dot sourceforge.net)
* @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 efficient (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;
}
}