/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.utility.io;
import java.io.Writer;
/**
* A token stream that collects its output in a string buffer, which can
* then be used to construct a string. The normal write() methods will
* "escape" any embedded delimiters or escape characters.
* Use the writeDelimiter() methods to write a delimiter
* without it being "escaped". The escape character itself will
* always be "escaped" (i.e. there is no way to write out the escape
* character without it being doubled). Also, the escape character
* may not also be listed as a delimiter.
*/
public class TokenWriter
extends Writer
{
/** The delimiter characters, contained in a single String */
private String delimiters;
/** The escape character, used to mark embedded delimiters */
private char escapeCharacter;
/** The buffer that holds the String as it is built */
private StringBuffer buffer;
/** Cache the value of the highest delimiter character */
private char maxDelimiter;
/** Temporary buffer used to hold writes of delimiters */
private char[] delimiterBuffer;
/** Size of delimiterBuffer, must be >= 1 */
private final int delimiterBufferSize = 1024;
/** The default delimiters are
<space>
<tab>
<newline>
<carriage return>
<form feed>
*/
public static final String DEFAULT_DELIMITERS = " \t\n\r\f";
/** The default escape character is '\' <backslash> */
public static final char DEFAULT_ESCAPE_CHARACTER = '\\';
/**
* Create a new token writer, using the specified delimiters,
* escape character, and string buffer.
*
* @param delimiters a String containing the delimiters
* @param escapeCharacter a char representing the escape character
* @param buffer the StringBuffer to write on
*/
private TokenWriter(String delimiters, char escapeCharacter, StringBuffer buffer) {
super();
this.delimiters = delimiters;
this.escapeCharacter = escapeCharacter;
this.buffer = buffer;
this.lock = buffer; // synchronize on the buffer instead of the writer
this.initialize();
}
/**
* Create a new token writer, using the default delimiters,
* default escape character, and default initial string buffer size.
*/
public TokenWriter() {
this(DEFAULT_DELIMITERS, DEFAULT_ESCAPE_CHARACTER, new StringBuffer());
}
/**
* Create a new token writer, using the specified initial string buffer
* size and the default delimiters and default escape character.
*
* @param initialSize an int specifying the initial size of the buffer
*/
public TokenWriter(int initialSize) {
this(DEFAULT_DELIMITERS, DEFAULT_ESCAPE_CHARACTER, new StringBuffer(initialSize));
}
/**
* Create a new token writer, using the specified delimiters
* and the default escape character and default initial string buffer size.
*
* @param delimiters a String containing the delimiters
*/
public TokenWriter(String delimiters) {
this(delimiters, DEFAULT_ESCAPE_CHARACTER, new StringBuffer());
}
/**
* Create a new token writer, using the specified delimiters and
* escape character and the default initial string buffer size.
*
* @param delimiters a String containing the delimiters
* @param escapeCharacter a char representing the escape character
*/
public TokenWriter(String delimiters, char escapeCharacter) {
this(delimiters, escapeCharacter, new StringBuffer());
}
/**
* Create a new token writer, using the specified delimiters,
* escape character, and initial string buffer size.
*
* @param delimiters a String containing the delimiters
* @param escapeCharacter a char representing the escape character
* @param initialSize an int specifying the initial size of the buffer
*/
public TokenWriter(String delimiters, char escapeCharacter, int initialSize) {
this(delimiters, escapeCharacter, new StringBuffer(initialSize));
}
/**
* Calculate the max delimiter value and check for an
* invalid escape character.
*/
private void initialize() {
this.calculateMaxDelimiter();
if (this.charIsDelimiter(this.escapeCharacter)) {
throw new IllegalArgumentException("The \"escape\" character may not belong to the list of delimiters.");
}
}
/**
* Calculate the maximum value of a delimiter character.
* This will be used to short-circuit the search for a delimiter.
* @see #charIsDelimiter(int)
*/
private void calculateMaxDelimiter() {
if (this.delimiters == null) {
throw new NullPointerException();
}
this.maxDelimiter = 0;
for (int i = 0; i < this.delimiters.length(); i++) {
char c = this.delimiters.charAt(i);
if (this.maxDelimiter < c)
this.maxDelimiter = c;
}
}
/**
* Return whether the specified character is a delimiter.
*
* @param c character
*/
private boolean charIsDelimiter(int c) {
return (c <= this.maxDelimiter) && (this.delimiters.indexOf(c) >= 0);
}
/**
* Return whether the specified character is an escape character.
*
* @param c character
*/
private boolean charIsTheEscapeCharacter(int c) {
return c == this.escapeCharacter;
}
/**
* Return whether the specified character
* requires an "escape".
*
* @param c character
*/
private boolean charRequiresEscape(int c) {
return this.charIsTheEscapeCharacter(c) || this.charIsDelimiter(c);
}
/**
* Write a single character.
* "Escape" it if necessary.
*
* @param c character
*/
public void write(int c) {
synchronized (this.lock) {
if (this.charRequiresEscape(c))
this.buffer.append(this.escapeCharacter);
this.buffer.append((char) c);
}
}
/**
* Write a single delimiter character without "escaping" it.
*
* @param c character
*/
public void writeDelimiter(int c) {
synchronized (this.lock) {
if (!this.charIsDelimiter(c))
throw new IllegalArgumentException("Not a delimiter: " + c);
this.buffer.append((char) c);
}
}
/**
* Check the specified indices for validity within the specified array.
* Throw an IndexOutOfBoundsException if there are any problems.
* If the offset is equal to the length of the character array
* (which could be considered an illegal index) but
* the length is zero, nothing will happen....
*
* @param cbuffer Array of characters
* @param offset Offset from which to start writing characters
* @param length Number of characters to write
*/
private void checkIndices(char[] cbuffer, int offset, int length) {
if ((offset < 0) || (offset > cbuffer.length) || (length < 0) || ((offset + length) > cbuffer.length) || ((offset + length) < 0))
throw new IndexOutOfBoundsException();
}
/**
* Write a portion of an array of characters.
* "Escape" any embedded delimiter or escape characters.
*
* @param cbuffer Array of characters
* @param offset Offset from which to start writing characters
* @param length Number of characters to write
*/
public void write(char[] cbuffer, int offset, int length) {
synchronized (this.lock) {
this.checkIndices(cbuffer, offset, length);
if (length == 0) {
return;
}
int tooFar = offset + length;
for (int i = offset; i < tooFar; i++) {
this.write(cbuffer[i]);
}
}
}
/**
* Write a portion of an array of delimiter characters.
* Do <em>not</em> "escape" any of them.
*
* @param cbuffer Array of characters
* @param offset Offset from which to start writing characters
* @param length Number of characters to write
*/
public void writeDelimiter(char[] cbuffer, int offset, int length) {
synchronized (this.lock) {
this.checkIndices(cbuffer, offset, length);
if (length == 0) {
return;
}
int tooFar = offset + length;
for (int i = offset; i < tooFar; i++) {
this.writeDelimiter(cbuffer[i]);
}
}
}
/**
* Write a string of delimiter characters.
* Do <em>not</em> "escape" any them.
*
* @param string String to be written
*/
public void writeDelimiter(String string) {
this.writeDelimiter(string, 0, string.length());
}
/**
* Write a portion of a string of delimiter characters.
* Do <em>not</em> "escape" any them.
*
* @param string String to be written
* @param offset Offset from which to start writing characters
* @param length Number of characters to write
*/
public void writeDelimiter(String string, int offset, int length) {
synchronized (this.lock) {
char[] cbuffer;
if (length <= this.delimiterBufferSize) {
if (this.delimiterBuffer == null) {
this.delimiterBuffer = new char[this.delimiterBufferSize];
}
cbuffer = this.delimiterBuffer;
} else {
// don't permanently allocate large buffers
cbuffer = new char[length];
}
string.getChars(offset, (offset + length), cbuffer, 0);
this.writeDelimiter(cbuffer, 0, length);
}
}
/**
* Return the buffer's current value as a string.
*
* @return String representation of the current buffer value
*/
public String toString() {
return this.buffer.toString();
}
/**
* Return the string buffer itself.
*
* @return StringBuffer holding the current buffer value
*/
public StringBuffer getBuffer() {
return this.buffer;
}
/**
* Flush the stream.
*/
public void flush() {
// do nothing...
}
/**
* Close the stream.
* This method does not release the buffer, since its
* contents might still be required.
*/
public void close() {
// do nothing...
}
}