/*
* Copyright 2003-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Derived from Boon all rights granted to Groovy project for this fork.
*/
package groovy.json.internal;
import groovy.json.JsonException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @author Rick Hightower
*/
public class CharBuf extends Writer implements CharSequence {
protected int capacity = 16;
protected int location = 0;
protected char[] buffer;
public CharBuf(char[] buffer) {
__init__(buffer);
}
private void __init__(char[] buffer) {
this.buffer = buffer;
this.capacity = buffer.length;
}
public CharBuf(byte[] bytes) {
this.buffer = null;
try {
String str = new String(bytes, "UTF-8");
__init__(FastStringUtils.toCharArray(str));
} catch (UnsupportedEncodingException e) {
Exceptions.handle(e);
}
}
public static CharBuf createExact(final int capacity) {
return new CharBuf(capacity) {
public CharBuf add(char[] chars) {
Chr._idx(buffer, location, chars);
location += chars.length;
return this;
}
};
}
public static CharBuf create(int capacity) {
return new CharBuf(capacity);
}
public static CharBuf create(char[] buffer) {
return new CharBuf(buffer);
}
protected CharBuf(int capacity) {
this.capacity = capacity;
init();
}
protected CharBuf() {
init();
}
public void write(char[] cbuf, int off, int len) {
if (off == 0 && cbuf.length == len) {
this.add(cbuf);
} else {
char[] buffer = ArrayUtils.copyRange(cbuf, off, off + len);
this.add(buffer);
}
}
public void flush() throws IOException {
}
public void close() throws IOException {
}
public void init() {
buffer = new char[capacity];
}
public final CharBuf add(String str) {
add(FastStringUtils.toCharArray(str));
return this;
}
public final CharBuf addString(String str) {
add(FastStringUtils.toCharArray(str));
return this;
}
public final CharBuf add(int i) {
add(Integer.toString(i));
return this;
}
private Cache<Integer, char[]> icache;
public final CharBuf addInt(int i) {
switch (i) {
case 0:
addChar('0');
return this;
case 1:
addChar('1');
return this;
case -1:
addChar('-');
addChar('1');
return this;
}
addInt(Integer.valueOf(i));
return this;
}
public final CharBuf addInt(Integer key) {
if (icache == null) {
icache = new SimpleCache<Integer, char[]>(20, CacheType.LRU);
}
char[] chars = icache.get(key);
if (chars == null) {
String str = Integer.toString(key);
chars = FastStringUtils.toCharArray(str);
icache.put(key, chars);
}
addChars(chars);
return this;
}
final char[] trueChars = "true".toCharArray();
final char[] falseChars = "false".toCharArray();
public final CharBuf add(boolean b) {
addChars(b ? trueChars : falseChars);
return this;
}
public final CharBuf addBoolean(boolean b) {
add(Boolean.toString(b));
return this;
}
public final CharBuf add(byte i) {
add(Byte.toString(i));
return this;
}
public final CharBuf addByte(byte i) {
addInt(i);
return this;
}
public final CharBuf add(short i) {
add(Short.toString(i));
return this;
}
public final CharBuf addShort(short i) {
addInt(i);
return this;
}
public final CharBuf add(long l) {
add(Long.toString(l));
return this;
}
public final CharBuf add(double d) {
add(Double.toString(d));
return this;
}
private Cache<Double, char[]> dcache;
public final CharBuf addDouble(double d) {
addDouble(Double.valueOf(d));
return this;
}
public final CharBuf addDouble(Double key) {
if (dcache == null) {
dcache = new SimpleCache<Double, char[]>(20, CacheType.LRU);
}
char[] chars = dcache.get(key);
if (chars == null) {
String str = Double.toString(key);
chars = FastStringUtils.toCharArray(str);
dcache.put(key, chars);
}
add(chars);
return this;
}
public final CharBuf add(float d) {
add(Float.toString(d));
return this;
}
private Cache<Float, char[]> fcache;
public final CharBuf addFloat(float d) {
addFloat(Float.valueOf(d));
return this;
}
public final CharBuf addFloat(Float key) {
if (fcache == null) {
fcache = new SimpleCache<Float, char[]>(20, CacheType.LRU);
}
char[] chars = fcache.get(key);
if (chars == null) {
String str = Float.toString(key);
chars = FastStringUtils.toCharArray(str);
fcache.put(key, chars);
}
add(chars);
return this;
}
public final CharBuf addChar(byte i) {
add((char) i);
return this;
}
public final CharBuf addChar(int i) {
add((char) i);
return this;
}
public final CharBuf addChar(short i) {
add((char) i);
return this;
}
public final CharBuf addChar(final char ch) {
int _location = location;
char[] _buffer = buffer;
int _capacity = capacity;
if (1 + _location > _capacity) {
_buffer = Chr.grow(_buffer);
_capacity = _buffer.length;
}
_buffer[_location] = ch;
_location++;
location = _location;
buffer = _buffer;
capacity = _capacity;
return this;
}
public CharBuf addLine(String str) {
add(str.toCharArray());
add('\n');
return this;
}
public CharBuf addLine(CharSequence str) {
add(str.toString());
add('\n');
return this;
}
public CharBuf add(char[] chars) {
if (chars.length + location > capacity) {
buffer = Chr.grow(buffer, buffer.length * 2 + chars.length);
capacity = buffer.length;
}
Chr._idx(buffer, location, chars);
location += chars.length;
return this;
}
public final CharBuf addChars(char[] chars) {
if (chars.length + location > capacity) {
buffer = Chr.grow(buffer, buffer.length * 2 + chars.length);
capacity = buffer.length;
}
arraycopy(chars, 0, buffer, location, chars.length);
location += chars.length;
return this;
}
public final CharBuf addQuoted(char[] chars) {
int _location = location;
char[] _buffer = buffer;
int _capacity = capacity;
int sizeNeeded = chars.length + 2 + _location;
if (sizeNeeded > _capacity) {
_buffer = Chr.grow(_buffer, sizeNeeded * 2);
_capacity = _buffer.length;
}
_buffer[_location] = '"';
_location++;
arraycopy(chars, 0, _buffer, _location, chars.length);
_location += (chars.length);
_buffer[_location] = '"';
_location++;
location = _location;
buffer = _buffer;
capacity = _capacity;
return this;
}
public final CharBuf addJsonEscapedString(String jsonString) {
char[] charArray = FastStringUtils.toCharArray(jsonString);
return addJsonEscapedString(charArray);
}
private static boolean hasAnyJSONControlOrUnicodeChars(int c) {
/* Anything less than space is a control character. */
if (c < 30) {
return true;
/* 34 is double quote. */
} else if (c == 34) {
return true;
} else if (c == 92) {
return true;
} else if (c < ' ' || c > 126) {
return true;
}
return false;
}
private static boolean hasAnyJSONControlChars(final char[] charArray) {
int index = 0;
char c;
while (true) {
c = charArray[index];
if (hasAnyJSONControlOrUnicodeChars(c)) {
return true;
}
if (++index >= charArray.length) return false;
}
}
public final CharBuf addJsonEscapedString(final char[] charArray) {
if (charArray.length == 0) return this;
if (hasAnyJSONControlChars(charArray)) {
return doAddJsonEscapedString(charArray);
} else {
return this.addQuoted(charArray);
}
}
final byte[] encoded = new byte[2];
final byte[] charTo = new byte[2];
private final CharBuf doAddJsonEscapedString(char[] charArray) {
char[] _buffer = buffer;
int _location = this.location;
final byte[] _encoded = encoded;
final byte[] _charTo = charTo;
/* We are making a bet that not all chars will be unicode. */
int ensureThisMuch = charArray.length * 6 + 2;
int sizeNeeded = (ensureThisMuch) + _location;
if (sizeNeeded > capacity) {
int growBy = (_buffer.length * 2) < sizeNeeded ? sizeNeeded : (_buffer.length * 2);
_buffer = Chr.grow(buffer, growBy);
capacity = _buffer.length;
}
_buffer[_location] = '"';
_location++;
int index = 0;
while (true) {
char c = charArray[index];
if (hasAnyJSONControlOrUnicodeChars(c)) {
/* We are covering our bet with a safety net.
otherwise we would have to have 5x buffer
allocated for control chars */
if (_location + 5 > _buffer.length) {
_buffer = Chr.grow(_buffer, 20);
}
switch (c) {
case '\"':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = '"';
_location++;
break;
case '\\':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = '\\';
_location++;
break;
//There is not requirement to escape solidus so we will not.
// case '/':
// _buffer[_location] = '\\';
// _location ++;
// _buffer[_location] = '/';
// _location ++;
// break;
case '\b':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = 'b';
_location++;
break;
case '\f':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = 'f';
_location++;
break;
case '\n':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = 'n';
_location++;
break;
case '\r':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = 'r';
_location++;
break;
case '\t':
_buffer[_location] = '\\';
_location++;
_buffer[_location] = 't';
_location++;
break;
default:
_buffer[_location] = '\\';
_location++;
_buffer[_location] = 'u';
_location++;
if (c <= 255) {
_buffer[_location] = '0';
_location++;
_buffer[_location] = '0';
_location++;
ByteScanner.encodeByteIntoTwoAsciiCharBytes(c, _encoded);
for (int b : _encoded) {
_buffer[_location] = (char) b;
_location++;
}
} else {
Byt.charTo(_charTo, c);
for (int charByte : _charTo) {
ByteScanner.encodeByteIntoTwoAsciiCharBytes(charByte, _encoded);
for (int b : _encoded) {
_buffer[_location] = (char) b;
_location++;
}
}
}
}
} else {
_buffer[_location] = c;
_location++;
}
if (++index >= charArray.length) break;
}
_buffer[_location] = '"';
_location++;
buffer = _buffer;
location = _location;
return this;
}
public final CharBuf addJsonFieldName(String str) {
return addJsonFieldName(FastStringUtils.toCharArray(str));
}
public final CharBuf addJsonFieldName(char[] chars) {
int _location = location;
char[] _buffer = buffer;
int _capacity = capacity;
try {
int sizeNeeded = chars.length + 3 + _location;
if (sizeNeeded > _capacity) {
_buffer = Chr.grow(_buffer, sizeNeeded * 2);
_capacity = _buffer.length;
}
_buffer[_location] = '"';
_location++;
arraycopy(chars, 0, _buffer, _location, chars.length);
_location += (chars.length);
_buffer[_location] = '"';
_location++;
_buffer[_location] = ':';
_location++;
location = _location;
buffer = _buffer;
capacity = _capacity;
return this;
} catch (Exception ex) {
return Exceptions.handle(CharBuf.class, Exceptions.sputs(toDebugString(), new String(chars), "_location", _location), ex);
}
}
public final CharBuf addQuoted(String str) {
final char[] chars = FastStringUtils.toCharArray(str);
addQuoted(chars);
return this;
}
public CharBuf add(char[] chars, final int length) {
if (length + location < capacity) {
Chr._idx(buffer, location, chars, length);
} else {
buffer = Chr.grow(buffer, buffer.length * 2 + length);
Chr._idx(buffer, location, chars);
capacity = buffer.length;
}
location += length;
return this;
}
public CharBuf add(byte[] chars) {
if (chars.length + location < capacity) {
Chr._idx(buffer, location, chars);
} else {
buffer = Chr.grow(buffer, buffer.length * 2 + chars.length);
Chr._idx(buffer, location, chars);
capacity = buffer.length;
}
location += chars.length;
return this;
}
private static final void sysstemarraycopy(final char[] src, final int srcPos, final char[] dest, final int destPos, final int length) {
System.arraycopy(src, srcPos, dest, destPos, length);
}
private static final void arraycopy(final char[] src, final int srcPos, final char[] dest, final int destPos, final int length) {
sysstemarraycopy(src, srcPos, dest, destPos, length);
}
public CharBuf add(byte[] bytes, int start, int end) {
int charsLength = end - start;
if (charsLength + location > capacity) {
buffer = Chr.grow(buffer, buffer.length * 2 + charsLength);
}
Chr._idx(buffer, location, bytes, start, end);
capacity = buffer.length;
location += charsLength;
return this;
}
public final CharBuf add(char ch) {
if (1 + location < capacity) {
buffer[location] = ch;
} else {
buffer = Chr.grow(buffer);
buffer[location] = ch;
capacity = buffer.length;
}
location += 1;
return this;
}
public int length() {
return len();
}
public char charAt(int index) {
return buffer[index];
}
public CharSequence subSequence(int start, int end) {
return new String(buffer, start, end - start);
}
public String toString() {
return new String(buffer, 0, location);
}
public String toDebugString() {
return "CharBuf{" +
"capacity=" + capacity +
", location=" + location +
'}';
}
public String toStringAndRecycle() {
String str = new String(buffer, 0, location);
location = 0;
return str;
}
public int len() {
return location;
}
public char[] toCharArray() {
return this.buffer;
}
public void _len(int location) {
this.location = location;
}
public char[] readForRecycle() {
this.location = 0;
return this.buffer;
}
public void recycle() {
this.location = 0;
}
public double doubleValue() {
return CharScanner.parseDouble(this.buffer, 0, location);
}
public float floatValue() {
return CharScanner.parseFloat(this.buffer, 0, location);
}
public int intValue() {
return CharScanner.parseIntFromTo(buffer, 0, location);
}
public long longValue() {
return CharScanner.parseLongFromTo(buffer, 0, location);
}
public byte byteValue() {
return (byte) intValue();
}
public short shortValue() {
return (short) intValue();
}
public Number toIntegerWrapper() {
if (CharScanner.isInteger(buffer, 0, location)) {
return intValue();
} else {
return longValue();
}
}
static final char[] nullChars = "null".toCharArray();
public final void addNull() {
this.add(nullChars);
}
public void removeLastChar() {
location--;
}
private Cache<BigDecimal, char[]> bigDCache;
public CharBuf addBigDecimal(BigDecimal key) {
if (bigDCache == null) {
bigDCache = new SimpleCache<BigDecimal, char[]>(20, CacheType.LRU);
}
char[] chars = bigDCache.get(key);
if (chars == null) {
String str = key.toString();
chars = FastStringUtils.toCharArray(str);
bigDCache.put(key, chars);
}
add(chars);
return this;
}
private Cache<BigInteger, char[]> bigICache;
public CharBuf addBigInteger(BigInteger key) {
if (bigICache == null) {
bigICache = new SimpleCache<BigInteger, char[]>(20, CacheType.LRU);
}
char[] chars = bigICache.get(key);
if (chars == null) {
String str = key.toString();
chars = FastStringUtils.toCharArray(str);
bigICache.put(key, chars);
}
add(chars);
return this;
}
private Cache<Long, char[]> lcache;
public final CharBuf addLong(long l) {
addLong(Long.valueOf(l));
return this;
}
public final CharBuf addLong(Long key) {
if (lcache == null) {
lcache = new SimpleCache<Long, char[]>(20, CacheType.LRU);
}
char[] chars = lcache.get(key);
if (chars == null) {
String str = Long.toString(key);
chars = FastStringUtils.toCharArray(str);
lcache.put(key, chars);
}
add(chars);
return this;
}
public final CharBuf decodeJsonString(char[] chars) {
return decodeJsonString(chars, 0, chars.length);
}
public final CharBuf decodeJsonString(char[] chars, int start, int to) {
int len = to - start;
char[] buffer = this.buffer;
int location = this.location;
if (len > capacity) {
buffer = Chr.grow(buffer, buffer.length * 2 + len);
capacity = buffer.length;
}
for (int index = start; index < to; index++) {
char c = chars[index];
if (c == '\\') {
if (index < to) {
index++;
c = chars[index];
switch (c) {
case 'n':
buffer[location++] = '\n';
break;
case '/':
buffer[location++] = '/';
break;
case '"':
buffer[location++] = '"';
break;
case 'f':
buffer[location++] = '\f';
break;
case 't':
buffer[location++] = '\t';
break;
case '\\':
buffer[location++] = '\\';
break;
case 'b':
buffer[location++] = '\b';
break;
case 'r':
buffer[location++] = '\r';
break;
case 'u':
if (index + 4 < to) {
String hex = new String(chars, index + 1, 4);
char unicode = (char) Integer.parseInt(hex, 16);
buffer[location++] = unicode;
index += 4;
}
break;
default:
throw new JsonException("Unable to decode string");
}
}
} else {
buffer[location++] = c;
}
}
this.buffer = buffer;
this.location = location;
return this;
}
}