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);
+            }
+        }
+    }
+}