net.hasor.utils.json.JSON Maven / Gradle / Ivy
// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package net.hasor.utils.json;
import net.hasor.utils.StringUtils;
import java.io.Externalizable;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* JSON Parser and Generator.
*
*
* This class provides some static methods to convert POJOs to and from JSON
* notation. The mapping from JSON to java is:
*
*
* object ==> Map
* array ==> Object[]
* number ==> Double or Long
* string ==> String
* null ==> null
* bool ==> Boolean
*
*
*
*
* The java to JSON mapping is:
*
*
* String --> string
* Number --> number
* Map --> object
* List --> array
* Array --> array
* null --> null
* Boolean--> boolean
* Object --> string (dubious!)
*
*
*
*
* The interface {@link net.hasor.utils.json.JSON.Convertible} may be implemented by classes that
* wish to externalize and initialize specific fields to and from JSON objects.
* Only directed acyclic graphs of objects are supported.
*
*
* The interface {@link net.hasor.utils.json.JSON.Generator} may be implemented by classes that know
* how to render themselves as JSON and the {@link #toString(Object)} method
* will use {@link net.hasor.utils.json.JSON.Generator#addJSON(Appendable)} to generate the JSON.
* The class {@link net.hasor.utils.json.JSON.Literal} may be used to hold pre-generated JSON object.
*
* The interface {@link net.hasor.utils.json.JSON.Convertor} may be implemented to provide static
* convertors for objects that may be registered with
* {@link #getConvertor(Class)}.
*
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class JSON {
protected final static Logger logger = Logger.getLogger(JSON.class.getName());
public final static JSON DEFAULT = new JSON();
private Map _convertors = new ConcurrentHashMap();
private int _stringBufferSize = 1024;
public JSON() {
}
/* ------------------------------------------------------------ */
/** @return the initial stringBuffer size to use when creating JSON strings (default 1024) */
public int getStringBufferSize() {
return _stringBufferSize;
}
/* ------------------------------------------------------------ */
/** @param stringBufferSize the initial stringBuffer size to use when creating JSON strings (default 1024) */
public void setStringBufferSize(int stringBufferSize) {
_stringBufferSize = stringBufferSize;
}
/* ------------------------------------------------------------ */
/**
* Register a {@link Convertor} for a class or interface.
* @param forClass The class or interface that the convertor applies to
* @param convertor the convertor
*/
public static void registerConvertor(Class forClass, Convertor convertor) {
DEFAULT.addConvertor(forClass, convertor);
}
/* ------------------------------------------------------------ */
public static JSON getDefault() {
return DEFAULT;
}
/* ------------------------------------------------------------ */
public static String toString(Object object) {
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
DEFAULT.append(buffer, object);
return buffer.toString();
}
/* ------------------------------------------------------------ */
public static String toString(Map object) {
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
DEFAULT.appendMap(buffer, object);
return buffer.toString();
}
/* ------------------------------------------------------------ */
public static String toString(Object[] array) {
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
DEFAULT.appendArray(buffer, array);
return buffer.toString();
}
/* ------------------------------------------------------------ */
/**
* @param s String containing JSON object or array.
* @return A Map, Object array or primitive array parsed from the JSON.
*/
public static Object parse(String s) {
return DEFAULT.parse(new StringSource(s), false);
}
/* ------------------------------------------------------------ */
/**
* @param s String containing JSON object or array.
* @param stripOuterComment If true, an outer comment around the JSON is ignored.
* @return A Map, Object array or primitive array parsed from the JSON.
*/
public static Object parse(String s, boolean stripOuterComment) {
return DEFAULT.parse(new StringSource(s), stripOuterComment);
}
/* ------------------------------------------------------------ */
/**
* @param in Reader containing JSON object or array.
* @return A Map, Object array or primitive array parsed from the JSON.
*/
public static Object parse(Reader in) throws IOException {
return DEFAULT.parse(new ReaderSource(in), false);
}
/* ------------------------------------------------------------ */
/**
* @param in Reader containing JSON object or array.
* @param stripOuterComment If true, an outer comment around the JSON is ignored.
* @return A Map, Object array or primitive array parsed from the JSON.
*/
public static Object parse(Reader in, boolean stripOuterComment) throws IOException {
return DEFAULT.parse(new ReaderSource(in), stripOuterComment);
}
/* ------------------------------------------------------------ */
/**
* Convert Object to JSON
* @param object The object to convert
* @return The JSON String
*/
public String toJSON(Object object) {
StringBuilder buffer = new StringBuilder(getStringBufferSize());
append(buffer, object);
return buffer.toString();
}
/* ------------------------------------------------------------ */
/**
* Convert JSON to Object
* @param json The json to convert
* @return The object
*/
public Object fromJSON(String json) {
Source source = new StringSource(json);
return parse(source);
}
/* ------------------------------------------------------------ */
/**
* Append object as JSON to string buffer.
* @param buffer the buffer to append to
* @param object the object to append
*/
public void append(Appendable buffer, Object object) {
try {
if (object == null)
buffer.append("null");
else if (object instanceof Convertible)
appendJSON(buffer, (Convertible) object);
else if (object instanceof Generator)
appendJSON(buffer, (Generator) object);
else if (object instanceof Map)
appendMap(buffer, (Map) object);
else if (object instanceof Collection)
appendArray(buffer, (Collection) object);
else if (object.getClass().isArray())
appendArray(buffer, object);
else if (object instanceof Number)
appendNumber(buffer, (Number) object);
else if (object instanceof Boolean)
appendBoolean(buffer, (Boolean) object);
else if (object instanceof Character)
appendString(buffer, object.toString());
else if (object instanceof String)
appendString(buffer, (String) object);
else {
Convertor convertor = getConvertor(object.getClass());
if (convertor != null)
appendJSON(buffer, convertor, object);
else
appendString(buffer, object.toString());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendNull(Appendable buffer) {
try {
buffer.append("null");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object) {
appendJSON(buffer, new Convertible() {
public void fromJSON(Map object) {
}
public void toJSON(Output out) {
convertor.toJSON(object, out);
}
});
}
/* ------------------------------------------------------------ */
public void appendJSON(final Appendable buffer, Convertible converter) {
ConvertableOutput out = new ConvertableOutput(buffer);
converter.toJSON(out);
out.complete();
}
/* ------------------------------------------------------------ */
public void appendJSON(Appendable buffer, Generator generator) {
generator.addJSON(buffer);
}
/* ------------------------------------------------------------ */
public void appendMap(Appendable buffer, Map, ?> map) {
try {
if (map == null) {
appendNull(buffer);
return;
}
buffer.append('{');
Iterator> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry, ?> entry = (Map.Entry, ?>) iter.next();
StringUtils.quote(buffer, entry.getKey().toString());
buffer.append(':');
append(buffer, entry.getValue());
if (iter.hasNext())
buffer.append(',');
}
buffer.append('}');
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendArray(Appendable buffer, Collection collection) {
try {
if (collection == null) {
appendNull(buffer);
return;
}
buffer.append('[');
Iterator iter = collection.iterator();
boolean first = true;
while (iter.hasNext()) {
if (!first)
buffer.append(',');
first = false;
append(buffer, iter.next());
}
buffer.append(']');
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendArray(Appendable buffer, Object array) {
try {
if (array == null) {
appendNull(buffer);
return;
}
buffer.append('[');
int length = Array.getLength(array);
for (int i = 0; i < length; i++) {
if (i != 0)
buffer.append(',');
append(buffer, Array.get(array, i));
}
buffer.append(']');
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendBoolean(Appendable buffer, Boolean b) {
try {
if (b == null) {
appendNull(buffer);
return;
}
buffer.append(b.booleanValue() ? "true" : "false");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendNumber(Appendable buffer, Number number) {
try {
if (number == null) {
appendNull(buffer);
return;
}
buffer.append(String.valueOf(number));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
public void appendString(Appendable buffer, String string) {
if (string == null) {
appendNull(buffer);
return;
}
StringUtils.quote(buffer, string);
}
// Parsing utilities
/* ------------------------------------------------------------ */
protected String toString(char[] buffer, int offset, int length) {
return new String(buffer, offset, length);
}
/* ------------------------------------------------------------ */
protected Map newMap() {
return new HashMap();
}
/* ------------------------------------------------------------ */
protected Object[] newArray(int size) {
return new Object[size];
}
/* ------------------------------------------------------------ */
protected JSON contextForArray() {
return this;
}
/* ------------------------------------------------------------ */
protected JSON contextFor(String field) {
return this;
}
/* ------------------------------------------------------------ */
protected Object convertTo(Class type, Map map) {
if (type == null) {
throw new NullPointerException("type is null.");
}
if (type != null && Convertible.class.isAssignableFrom(type)) {
try {
Convertible conv = (Convertible) type.newInstance();
conv.fromJSON(map);
return conv;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Convertor convertor = getConvertor(type);
if (convertor != null) {
return convertor.fromJSON(map);
}
return map;
}
/* ------------------------------------------------------------ */
/**
* Register a {@link Convertor} for a class or interface.
*
* @param forClass The class or interface that the convertor applies to
* @param convertor the convertor
*/
public void addConvertor(Class forClass, Convertor convertor) {
_convertors.put(forClass.getName(), convertor);
}
/* ------------------------------------------------------------ */
/**
* Lookup a convertor for a class.
*
* If no match is found for the class, then the interfaces for the class are
* tried. If still no match is found, then the super class and it's
* interfaces are tried recursively.
*
* @param forClass The class
* @return a {@link net.hasor.utils.json.JSON.Convertor} or null if none were found.
*/
protected Convertor getConvertor(Class forClass) {
if (forClass == null) {
return null;
}
Class cls = forClass;
Convertor convertor = _convertors.get(cls.getName());
if (convertor == null && this != DEFAULT)
convertor = DEFAULT.getConvertor(cls);
while (convertor == null && cls != null && cls != Object.class) {
Class[] ifs = cls.getInterfaces();
int i = 0;
while (convertor == null && ifs != null && i < ifs.length)
convertor = _convertors.get(ifs[i++].getName());
if (convertor == null) {
cls = cls.getSuperclass();
convertor = _convertors.get(cls.getName());
}
}
if (convertor == null)
return new JSONPojoConvertor(forClass);
return convertor;
}
/* ------------------------------------------------------------ */
/**
* Register a {@link net.hasor.utils.json.JSON.Convertor} for a named class or interface.
*
* @param name name of a class or an interface that the convertor applies to
* @param convertor the convertor
*/
public void addConvertorFor(String name, Convertor convertor) {
_convertors.put(name, convertor);
}
/* ------------------------------------------------------------ */
/**
* Lookup a convertor for a named class.
*
* @param name
* name of the class
* @return a {@link net.hasor.utils.json.JSON.Convertor} or null if none were found.
*/
public Convertor getConvertorFor(String name) {
String clsName = name;
Convertor convertor = _convertors.get(clsName);
if (convertor == null && this != DEFAULT)
convertor = DEFAULT.getConvertorFor(clsName);
return convertor;
}
/* ------------------------------------------------------------ */
public Object parse(Source source, boolean stripOuterComment) {
int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
if (!stripOuterComment)
return parse(source);
int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */
Object o = null;
while (source.hasNext()) {
char c = source.peek();
// handle // or /* comment
if (comment_state == 1) {
switch (c) {
case '/':
comment_state = -1;
break;
case '*':
comment_state = 2;
if (strip_state == 1) {
comment_state = 0;
strip_state = 2;
}
}
}
// handle /* */ comment
else if (comment_state > 1) {
switch (c) {
case '*':
comment_state = 3;
break;
case '/':
if (comment_state == 3) {
comment_state = 0;
if (strip_state == 2)
return o;
} else
comment_state = 2;
break;
default:
comment_state = 2;
}
}
// handle // comment
else if (comment_state < 0) {
switch (c) {
case '\r':
case '\n':
comment_state = 0;
default:
break;
}
}
// handle unknown
else {
if (!Character.isWhitespace(c)) {
if (c == '/')
comment_state = 1;
else if (c == '*')
comment_state = 3;
else if (o == null) {
o = parse(source);
continue;
}
}
}
source.next();
}
return o;
}
/* ------------------------------------------------------------ */
public Object parse(Source source) {
int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
while (source.hasNext()) {
char c = source.peek();
// handle // or /* comment
if (comment_state == 1) {
switch (c) {
case '/':
comment_state = -1;
break;
case '*':
comment_state = 2;
}
}
// handle /* */ comment
else if (comment_state > 1) {
switch (c) {
case '*':
comment_state = 3;
break;
case '/':
if (comment_state == 3)
comment_state = 0;
else
comment_state = 2;
break;
default:
comment_state = 2;
}
}
// handle // comment
else if (comment_state < 0) {
switch (c) {
case '\r':
case '\n':
comment_state = 0;
break;
default:
break;
}
}
// handle unknown
else {
switch (c) {
case '{':
return parseObject(source);
case '[':
return parseArray(source);
case '"':
return parseString(source);
case '-':
return parseNumber(source);
case 'n':
complete("null", source);
return null;
case 't':
complete("true", source);
return Boolean.TRUE;
case 'f':
complete("false", source);
return Boolean.FALSE;
case 'u':
complete("undefined", source);
return null;
case 'N':
complete("NaN", source);
return null;
case '/':
comment_state = 1;
break;
default:
if (Character.isDigit(c))
return parseNumber(source);
else if (Character.isWhitespace(c))
break;
return handleUnknown(source, c);
}
}
source.next();
}
return null;
}
/* ------------------------------------------------------------ */
protected Object handleUnknown(Source source, char c) {
throw new IllegalStateException("unknown char '" + c + "'(" + (int) c + ") in " + source);
}
/* ------------------------------------------------------------ */
protected Object parseObject(Source source) {
if (source.next() != '{')
throw new IllegalStateException();
Map map = newMap();
char next = seekTo("\"}", source);
while (source.hasNext()) {
if (next == '}') {
source.next();
break;
}
String name = parseString(source);
seekTo(':', source);
source.next();
Object value = contextFor(name).parse(source);
map.put(name, value);
seekTo(",}", source);
next = source.next();
if (next == '}')
break;
else
next = seekTo("\"}", source);
}
String classname = (String) map.get("class");
if (classname != null) {
try {
Class c = Loader.loadClass(JSON.class, classname);
return convertTo(c, map);
} catch (ClassNotFoundException e) {
logger.warning(e.getMessage());
}
}
return map;
}
/* ------------------------------------------------------------ */
protected Object parseArray(Source source) {
if (source.next() != '[')
throw new IllegalStateException();
int size = 0;
ArrayList list = null;
Object item = null;
boolean coma = true;
while (source.hasNext()) {
char c = source.peek();
switch (c) {
case ']':
source.next();
switch (size) {
case 0:
return newArray(0);
case 1:
Object array = newArray(1);
Array.set(array, 0, item);
return array;
default:
return list.toArray(newArray(list.size()));
}
case ',':
if (coma)
throw new IllegalStateException();
coma = true;
source.next();
break;
default:
if (Character.isWhitespace(c))
source.next();
else {
coma = false;
if (size++ == 0)
item = contextForArray().parse(source);
else if (list == null) {
list = new ArrayList();
list.add(item);
item = contextForArray().parse(source);
list.add(item);
item = null;
} else {
item = contextForArray().parse(source);
list.add(item);
item = null;
}
}
}
}
throw new IllegalStateException("unexpected end of array");
}
/* ------------------------------------------------------------ */
protected String parseString(Source source) {
if (source.next() != '"')
throw new IllegalStateException();
boolean escape = false;
StringBuilder b = null;
final char[] scratch = source.scratchBuffer();
if (scratch != null) {
int i = 0;
while (source.hasNext()) {
if (i >= scratch.length) {
// we have filled the scratch buffer, so we must
// use the StringBuffer for a large string
b = new StringBuilder(scratch.length * 2);
b.append(scratch, 0, i);
break;
}
char c = source.next();
if (escape) {
escape = false;
switch (c) {
case '"':
scratch[i++] = '"';
break;
case '\\':
scratch[i++] = '\\';
break;
case '/':
scratch[i++] = '/';
break;
case 'b':
scratch[i++] = '\b';
break;
case 'f':
scratch[i++] = '\f';
break;
case 'n':
scratch[i++] = '\n';
break;
case 'r':
scratch[i++] = '\r';
break;
case 't':
scratch[i++] = '\t';
break;
case 'u':
char uc = (char) ((TypeUtil.convertHexDigit((byte) source.next()) << 12) + (TypeUtil.convertHexDigit((byte) source.next()) << 8) + (TypeUtil.convertHexDigit((byte) source.next()) << 4) + (TypeUtil.convertHexDigit((byte) source.next())));
scratch[i++] = uc;
break;
default:
scratch[i++] = c;
}
} else if (c == '\\') {
escape = true;
continue;
} else if (c == '\"') {
// Return string that fits within scratch buffer
return toString(scratch, 0, i);
} else
scratch[i++] = c;
}
// Missing end quote, but return string anyway ?
if (b == null)
return toString(scratch, 0, i);
} else
b = new StringBuilder(getStringBufferSize());
// parse large string into string buffer
final StringBuilder builder = b;
while (source.hasNext()) {
char c = source.next();
if (escape) {
escape = false;
switch (c) {
case '"':
builder.append('"');
break;
case '\\':
builder.append('\\');
break;
case '/':
builder.append('/');
break;
case 'b':
builder.append('\b');
break;
case 'f':
builder.append('\f');
break;
case 'n':
builder.append('\n');
break;
case 'r':
builder.append('\r');
break;
case 't':
builder.append('\t');
break;
case 'u':
char uc = (char) ((TypeUtil.convertHexDigit((byte) source.next()) << 12) + (TypeUtil.convertHexDigit((byte) source.next()) << 8) + (TypeUtil.convertHexDigit((byte) source.next()) << 4) + (TypeUtil.convertHexDigit((byte) source.next())));
builder.append(uc);
break;
default:
builder.append(c);
}
} else if (c == '\\') {
escape = true;
continue;
} else if (c == '\"')
break;
else
builder.append(c);
}
return builder.toString();
}
/* ------------------------------------------------------------ */
public Number parseNumber(Source source) {
boolean minus = false;
long number = 0;
StringBuilder buffer = null;
longLoop:
while (source.hasNext()) {
char c = source.peek();
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
number = number * 10 + (c - '0');
source.next();
break;
case '-':
case '+':
if (number != 0)
throw new IllegalStateException("bad number");
minus = true;
source.next();
break;
case '.':
case 'e':
case 'E':
buffer = new StringBuilder(16);
if (minus)
buffer.append('-');
buffer.append(number);
buffer.append(c);
source.next();
break longLoop;
default:
break longLoop;
}
}
if (buffer == null)
return minus ? -1 * number : number;
doubleLoop:
while (source.hasNext()) {
char c = source.peek();
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
case '.':
case '+':
case 'e':
case 'E':
buffer.append(c);
source.next();
break;
default:
break doubleLoop;
}
}
return new Double(buffer.toString());
}
/* ------------------------------------------------------------ */
protected void seekTo(char seek, Source source) {
while (source.hasNext()) {
char c = source.peek();
if (c == seek)
return;
if (!Character.isWhitespace(c))
throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'");
source.next();
}
throw new IllegalStateException("Expected '" + seek + "'");
}
/* ------------------------------------------------------------ */
protected char seekTo(String seek, Source source) {
while (source.hasNext()) {
char c = source.peek();
if (seek.indexOf(c) >= 0) {
return c;
}
if (!Character.isWhitespace(c))
throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'");
source.next();
}
throw new IllegalStateException("Expected one of '" + seek + "'");
}
/* ------------------------------------------------------------ */
protected static void complete(String seek, Source source) {
int i = 0;
while (source.hasNext() && i < seek.length()) {
char c = source.next();
if (c != seek.charAt(i++))
throw new IllegalStateException("Unexpected '" + c + " while seeking \"" + seek + "\"");
}
if (i < seek.length())
throw new IllegalStateException("Expected \"" + seek + "\"");
}
private final class ConvertableOutput implements Output {
private final Appendable _buffer;
char c = '{';
private ConvertableOutput(Appendable buffer) {
_buffer = buffer;
}
public void complete() {
try {
if (c == '{')
_buffer.append("{}");
else if (c != 0)
_buffer.append("}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void add(Object obj) {
if (c == 0)
throw new IllegalStateException();
append(_buffer, obj);
c = 0;
}
public void addClass(Class type) {
try {
if (c == 0)
throw new IllegalStateException();
_buffer.append(c);
_buffer.append("\"class\":");
append(_buffer, type.getName());
c = ',';
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void add(String name, Object value) {
try {
if (c == 0)
throw new IllegalStateException();
_buffer.append(c);
StringUtils.quote(_buffer, name);
_buffer.append(':');
append(_buffer, value);
c = ',';
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void add(String name, double value) {
try {
if (c == 0)
throw new IllegalStateException();
_buffer.append(c);
StringUtils.quote(_buffer, name);
_buffer.append(':');
appendNumber(_buffer, new Double(value));
c = ',';
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void add(String name, long value) {
try {
if (c == 0)
throw new IllegalStateException();
_buffer.append(c);
StringUtils.quote(_buffer, name);
_buffer.append(':');
appendNumber(_buffer, value);
c = ',';
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void add(String name, boolean value) {
try {
if (c == 0)
throw new IllegalStateException();
_buffer.append(c);
StringUtils.quote(_buffer, name);
_buffer.append(':');
appendBoolean(_buffer, value ? Boolean.TRUE : Boolean.FALSE);
c = ',';
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/* ------------------------------------------------------------ */
public interface Source {
boolean hasNext();
char next();
char peek();
char[] scratchBuffer();
}
/* ------------------------------------------------------------ */
public static class StringSource implements Source {
private final String string;
private int index;
private char[] scratch;
public StringSource(String s) {
string = s;
}
public boolean hasNext() {
if (index < string.length())
return true;
scratch = null;
return false;
}
public char next() {
return string.charAt(index++);
}
public char peek() {
return string.charAt(index);
}
public String toString() {
return string.substring(0, index) + "|||" + string.substring(index);
}
public char[] scratchBuffer() {
if (scratch == null)
scratch = new char[string.length()];
return scratch;
}
}
/* ------------------------------------------------------------ */
public static class ReaderSource implements Source {
private Reader _reader;
private int _next = -1;
private char[] scratch;
public ReaderSource(Reader r) {
_reader = r;
}
public void setReader(Reader reader) {
_reader = reader;
_next = -1;
}
public boolean hasNext() {
getNext();
if (_next < 0) {
scratch = null;
return false;
}
return true;
}
public char next() {
getNext();
char c = (char) _next;
_next = -1;
return c;
}
public char peek() {
getNext();
return (char) _next;
}
private void getNext() {
if (_next < 0) {
try {
_next = _reader.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public char[] scratchBuffer() {
if (scratch == null)
scratch = new char[1024];
return scratch;
}
}
/* ------------------------------------------------------------ */
/**
* JSON Output class for use by {@link Convertible}.
*/
public interface Output {
public void addClass(Class c);
public void add(Object obj);
public void add(String name, Object value);
public void add(String name, double value);
public void add(String name, long value);
public void add(String name, boolean value);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/**
* JSON Convertible object. Object can implement this interface in a similar
* way to the {@link Externalizable} interface is used to allow classes to
* provide their own serialization mechanism.
*
* A JSON.Convertible object may be written to a JSONObject or initialized
* from a Map of field names to values.
*
* If the JSON is to be convertible back to an Object, then the method
* {@link Output#addClass(Class)} must be called from within toJSON()
*
*/
public interface Convertible {
public void toJSON(Output out);
public void fromJSON(Map object);
}
/* ------------------------------------------------------------ */
/**
* Static JSON Convertor.
*
* may be implemented to provide static convertors for objects that may be
* registered with
* {@link net.hasor.utils.json.JSON#registerConvertor(Class, net.hasor.utils.json.JSON.Convertor)}
* . These convertors are looked up by class, interface and super class by
* {@link net.hasor.utils.json.JSON#getConvertor(Class)}. Convertors should be used when the
* classes to be converted cannot implement {@link Convertible} or
* {@link Generator}.
*/
public interface Convertor {
public void toJSON(Object obj, Output out);
public Object fromJSON(Map object);
}
/* ------------------------------------------------------------ */
/**
* JSON Generator. A class that can add it's JSON representation directly to
* a StringBuffer. This is useful for object instances that are frequently
* converted and wish to avoid multiple Conversions
*/
public interface Generator {
public void addJSON(Appendable buffer);
}
/* ------------------------------------------------------------ */
/**
* A Literal JSON generator A utility instance of {@link net.hasor.utils.json.JSON.Generator}
* that holds a pre-generated string on JSON text.
*/
public static class Literal implements Generator {
private String _json;
/* ------------------------------------------------------------ */
/**
* Construct a literal JSON instance for use by {@link JSON#toString(Object)}.
* @param json A literal JSON string.
*/
public Literal(String json) {
if (logger.isLoggable(Level.FINE)) // TODO: Make this a configurable option on JSON instead!
parse(json);
_json = json;
}
public String toString() {
return _json;
}
public void addJSON(Appendable buffer) {
try {
buffer.append(_json);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}