Mercurial Hosting > luan
diff src/org/eclipse/jetty/util/ajax/JSON.java @ 802:3428c60d7cfc
replace jetty jars with source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 07 Sep 2016 21:15:48 -0600 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSON.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1640 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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 org.eclipse.jetty.util.ajax; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * JSON Parser and Generator. + * <p /> + * This class provides some static methods to convert POJOs to and from JSON + * notation. The mapping from JSON to java is: + * + * <pre> + * object ==> Map + * array ==> Object[] + * number ==> Double or Long + * string ==> String + * null ==> null + * bool ==> Boolean + * </pre> + + * The java to JSON mapping is: + * + * <pre> + * String --> string + * Number --> number + * Map --> object + * List --> array + * Array --> array + * null --> null + * Boolean--> boolean + * Object --> string (dubious!) + * </pre> + * + * The interface {@link 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. + * <p /> + * The interface {@link JSON.Generator} may be implemented by classes that know + * how to render themselves as JSON and the {@link #toString(Object)} method + * will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON. + * The class {@link JSON.Literal} may be used to hold pre-generated JSON object. + * <p /> + * The interface {@link JSON.Convertor} may be implemented to provide static + * converters for objects that may be registered with + * {@link #registerConvertor(Class, Convertor)}. + * These converters are looked up by class, interface and super class by + * {@link #getConvertor(Class)}. + * <p /> + * If a JSON object has a "class" field, then a java class for that name is + * loaded and the method {@link #convertTo(Class,Map)} is used to find a + * {@link JSON.Convertor} for that class. + * <p /> + * If a JSON object has a "x-class" field then a direct lookup for a + * {@link JSON.Convertor} for that class name is done (without loading the class). + */ +public class JSON +{ + static final Logger LOG = Log.getLogger(JSON.class); + public final static JSON DEFAULT = new JSON(); + + private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>(); + 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; + } + + @Deprecated + public static void setDefault(JSON json) + { + } + + 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); + } + + /** + * @deprecated use {@link #parse(Reader)} + * @param in + * Reader containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + @Deprecated + public static Object parse(InputStream in) throws IOException + { + return DEFAULT.parse(new StringSource(IO.toString(in)),false); + } + + /** + * @deprecated use {@link #parse(Reader, boolean)} + * @param in + * Stream 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. + */ + @Deprecated + public static Object parse(InputStream in, boolean stripOuterComment) throws IOException + { + return DEFAULT.parse(new StringSource(IO.toString(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); + } + + @Deprecated + public void append(StringBuffer buffer, Object object) + { + append((Appendable)buffer,object); + } + + /** + * 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"); + } + // Most likely first + else if (object instanceof Map) + { + appendMap(buffer,(Map)object); + } + else if (object instanceof String) + { + appendString(buffer,(String)object); + } + else if (object instanceof Number) + { + appendNumber(buffer,(Number)object); + } + else if (object instanceof Boolean) + { + appendBoolean(buffer,(Boolean)object); + } + else if (object.getClass().isArray()) + { + appendArray(buffer,object); + } + else if (object instanceof Character) + { + appendString(buffer,object.toString()); + } + else if (object instanceof Convertible) + { + appendJSON(buffer,(Convertible)object); + } + else if (object instanceof Generator) + { + appendJSON(buffer,(Generator)object); + } + else + { + // Check Convertor before Collection to support JSONCollectionConvertor + Convertor convertor = getConvertor(object.getClass()); + if (convertor != null) + { + appendJSON(buffer,convertor,object); + } + else if (object instanceof Collection) + { + appendArray(buffer,(Collection)object); + } + else + { + appendString(buffer,object.toString()); + } + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendNull(StringBuffer buffer) + { + appendNull((Appendable)buffer); + } + + public void appendNull(Appendable buffer) + { + try + { + buffer.append("null"); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object) + { + appendJSON((Appendable)buffer,convertor,object); + } + + 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); + } + }); + } + + @Deprecated + public void appendJSON(final StringBuffer buffer, Convertible converter) + { + appendJSON((Appendable)buffer,converter); + } + + public void appendJSON(final Appendable buffer, Convertible converter) + { + ConvertableOutput out=new ConvertableOutput(buffer); + converter.toJSON(out); + out.complete(); + } + + @Deprecated + public void appendJSON(StringBuffer buffer, Generator generator) + { + generator.addJSON(buffer); + } + + public void appendJSON(Appendable buffer, Generator generator) + { + generator.addJSON(buffer); + } + + @Deprecated + public void appendMap(StringBuffer buffer, Map<?,?> map) + { + appendMap((Appendable)buffer,map); + } + + 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(); + QuotedStringTokenizer.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); + } + } + + @Deprecated + public void appendArray(StringBuffer buffer, Collection collection) + { + appendArray((Appendable)buffer,collection); + } + + 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); + } + } + + @Deprecated + public void appendArray(StringBuffer buffer, Object array) + { + appendArray((Appendable)buffer,array); + } + + 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); + } + } + + @Deprecated + public void appendBoolean(StringBuffer buffer, Boolean b) + { + appendBoolean((Appendable)buffer,b); + } + + public void appendBoolean(Appendable buffer, Boolean b) + { + try + { + if (b == null) + { + appendNull(buffer); + return; + } + buffer.append(b?"true":"false"); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendNumber(StringBuffer buffer, Number number) + { + appendNumber((Appendable)buffer,number); + } + + 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); + } + } + + @Deprecated + public void appendString(StringBuffer buffer, String string) + { + appendString((Appendable)buffer,string); + } + + public void appendString(Appendable buffer, String string) + { + if (string == null) + { + appendNull(buffer); + return; + } + + QuotedStringTokenizer.quote(buffer,string); + } + + // Parsing utilities + + protected String toString(char[] buffer, int offset, int length) + { + return new String(buffer,offset,length); + } + + protected Map<String, Object> newMap() + { + return new HashMap<String, Object>(); + } + + 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 && 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. + * <p> + * 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 JSON.Convertor} or null if none were found. + */ + protected Convertor getConvertor(Class forClass) + { + Class cls = forClass; + Convertor convertor = _convertors.get(cls.getName()); + if (convertor == null && this != DEFAULT) + convertor = DEFAULT.getConvertor(cls); + + while (convertor == 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()); + } + } + return convertor; + } + + /** + * Register a {@link 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 JSON.Convertor} or null if none were found. + */ + public Convertor getConvertorFor(String name) + { + Convertor convertor = _convertors.get(name); + if (convertor == null && this != DEFAULT) + convertor = DEFAULT.getConvertorFor(name); + 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<String, Object> 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 xclassname = (String)map.get("x-class"); + if (xclassname != null) + { + Convertor c = getConvertorFor(xclassname); + if (c != null) + return c.fromJSON(map); + LOG.warn("No Convertor for x-class '{}'", xclassname); + } + + String classname = (String)map.get("class"); + if (classname != null) + { + try + { + Class c = Loader.loadClass(JSON.class,classname); + return convertTo(c,map); + } + catch (ClassNotFoundException e) + { + LOG.warn("No Class for '{}'", classname); + } + } + + 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; + } + 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; + } + 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); + QuotedStringTokenizer.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); + QuotedStringTokenizer.quote(_buffer,name); + _buffer.append(':'); + appendNumber(_buffer, 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); + QuotedStringTokenizer.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); + QuotedStringTokenizer.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); + } + + @Override + 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. + * <p> + * A JSON.Convertible object may be written to a JSONObject or initialized + * from a Map of field names to values. + * <p> + * 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. + * <p> + * may be implemented to provide static convertors for objects that may be + * registered with + * {@link JSON#registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)} + * . These convertors are looked up by class, interface and super class by + * {@link 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 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)}. If {@link Log#isDebugEnabled()} is + * true, the JSON will be parsed to check validity + * + * @param json + * A literal JSON string. + */ + public Literal(String json) + { + if (LOG.isDebugEnabled()) // TODO: Make this a configurable option on JSON instead! + parse(json); + _json = json; + } + + @Override + public String toString() + { + return _json; + } + + public void addJSON(Appendable buffer) + { + try + { + buffer.append(_json); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + } +}