// // ======================================================================== // 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. *
* 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 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 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. * * 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)}. * * 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. * * 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
* 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
* 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 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);
}
}
}
}