Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   //
   //  ========================================================================
   //  Copyright (c) 1995-2012 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.Reader;
  import java.util.HashMap;
  import java.util.Map;
  
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 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 JSON.Generator may be implemented by classes that know how to render themselves as JSON and the toString(java.lang.Object) method will use JSON.Generator.addJSON(java.lang.Appendable) to generate the JSON. The class JSON.Literal may be used to hold pre-generated JSON object.

The interface JSON.Convertor may be implemented to provide static convertors for objects that may be registered with registerConvertor(java.lang.Class,org.eclipse.jetty.util.ajax.JSON.Convertor) . These convertors are looked up by class, interface and super class by getConvertor(java.lang.Class).

  
  public class JSON
  {
      static final Logger LOG = Log.getLogger(JSON.class);
      public final static JSON DEFAULT = new JSON();
  
      private Map<StringConvertor_convertors = new ConcurrentHashMap<StringConvertor>();
      private int _stringBufferSize = 1024;
  
     public JSON()
     {
     }
 
     /* ------------------------------------------------------------ */
    

Returns:
the initial stringBuffer size to use when creating JSON strings (default 1024)
 
     public int getStringBufferSize()
     {
         return ;
     }
 
     /* ------------------------------------------------------------ */
    

Parameters:
stringBufferSize the initial stringBuffer size to use when creating JSON strings (default 1024)
 
     public void setStringBufferSize(int stringBufferSize)
     {
          = stringBufferSize;
     }
 
     /* ------------------------------------------------------------ */
    
Register a JSON.Convertor for a class or interface.

Parameters:
forClass The class or interface that the convertor applies to
convertor the convertor
 
     public static void registerConvertor(Class forClassConvertor convertor)
     {
         .addConvertor(forClass,convertor);
     }
 
     /* ------------------------------------------------------------ */
     public static JSON getDefault()
     {
         return ;
     }
 
     /* ------------------------------------------------------------ */
     @Deprecated
     public static void setDefault(JSON json)
     {
     }
 
     /* ------------------------------------------------------------ */
     public static String toString(Object object)
     {
         StringBuilder buffer = new StringBuilder(.getStringBufferSize());
         .append(buffer,object);
         return buffer.toString();
     }
 
     /* ------------------------------------------------------------ */
     public static String toString(Map object)
     {
         StringBuilder buffer = new StringBuilder(.getStringBufferSize());
         .appendMap(buffer,object);
         return buffer.toString();
     }
 
     /* ------------------------------------------------------------ */
     public static String toString(Object[] array)
     {
         StringBuilder buffer = new StringBuilder(.getStringBufferSize());
         .appendArray(buffer,array);
         return buffer.toString();
     }
 
     /* ------------------------------------------------------------ */
    

Parameters:
s String containing JSON object or array.
Returns:
A Map, Object array or primitive array parsed from the JSON.
 
     public static Object parse(String s)
     {
         return .parse(new StringSource(s),false);
     }
 
     /* ------------------------------------------------------------ */
    

Parameters:
s String containing JSON object or array.
stripOuterComment If true, an outer comment around the JSON is ignored.
Returns:
A Map, Object array or primitive array parsed from the JSON.
 
     public static Object parse(String sboolean stripOuterComment)
     {
         return .parse(new StringSource(s),stripOuterComment);
     }
 
     /* ------------------------------------------------------------ */
    

Parameters:
in Reader containing JSON object or array.
Returns:
A Map, Object array or primitive array parsed from the JSON.
 
     public static Object parse(Reader inthrows IOException
     {
         return .parse(new ReaderSource(in),false);
     }
 
     /* ------------------------------------------------------------ */
    

Parameters:
in Reader containing JSON object or array.
stripOuterComment If true, an outer comment around the JSON is ignored.
Returns:
A Map, Object array or primitive array parsed from the JSON.
 
     public static Object parse(Reader inboolean stripOuterCommentthrows IOException
     {
         return .parse(new ReaderSource(in),stripOuterComment);
     }
 
     /* ------------------------------------------------------------ */
    

Deprecated:
use parse(java.io.Reader)
Parameters:
in Reader containing JSON object or array.
Returns:
A Map, Object array or primitive array parsed from the JSON.
 
     @Deprecated
     public static Object parse(InputStream inthrows IOException
     {
         return .parse(new StringSource(IO.toString(in)),false);
     }
 
     /* ------------------------------------------------------------ */
    

Deprecated:
use parse(java.io.Reader,boolean)
Parameters:
in Stream containing JSON object or array.
stripOuterComment If true, an outer comment around the JSON is ignored.
Returns:
A Map, Object array or primitive array parsed from the JSON.
 
     @Deprecated
     public static Object parse(InputStream inboolean stripOuterCommentthrows IOException
     {
         return .parse(new StringSource(IO.toString(in)),stripOuterComment);
     }
 
     /* ------------------------------------------------------------ */
    
Convert Object to JSON

Parameters:
object The object to convert
Returns:
The JSON String
 
     public String toJSON(Object object)
     {
         StringBuilder buffer = new StringBuilder(getStringBufferSize());
         append(buffer,object);
         return buffer.toString();
     }
 
     /* ------------------------------------------------------------ */
    
Convert JSON to Object

Parameters:
json The json to convert
Returns:
The object
 
     public Object fromJSON(String json)
     {
         Source source = new StringSource(json);
         return parse(source);
     }
 
     @Deprecated
     public void append(StringBuffer bufferObject object)
     {
         append((Appendable)buffer,object);
     }
 
     /* ------------------------------------------------------------ */
    
Append object as JSON to string buffer.

Parameters:
buffer the buffer to append to
object the object to append
 
     public void append(Appendable bufferObject 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);
         }
     }
 
     /* ------------------------------------------------------------ */
     @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 bufferfinal Convertor convertorfinal Object object)
     {
         appendJSON((Appendable)buffer,convertor,object);
     }
 
     /* ------------------------------------------------------------ */
     public void appendJSON(final Appendable bufferfinal Convertor convertorfinal 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 bufferConvertible converter)
     {
         appendJSON((Appendable)buffer,converter);
     }
 
     /* ------------------------------------------------------------ */
     public void appendJSON(final Appendable bufferConvertible converter)
     {
         ConvertableOutput out=new ConvertableOutput(buffer);
         converter.toJSON(out);
         out.complete();
     }
 
     /* ------------------------------------------------------------ */
     @Deprecated
     public void appendJSON(StringBuffer bufferGenerator generator)
     {
         generator.addJSON(buffer);
     }
 
     /* ------------------------------------------------------------ */
     public void appendJSON(Appendable bufferGenerator generator)
     {
         generator.addJSON(buffer);
     }
 
     /* ------------------------------------------------------------ */
     @Deprecated
     public void appendMap(StringBuffer bufferMap<?,?> map)
     {
         appendMap((Appendable)buffer,map);
     }
 
     /* ------------------------------------------------------------ */
     public void appendMap(Appendable bufferMap<?,?> 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 bufferCollection collection)
     {
     	appendArray((Appendable)buffer,collection);
     }
 
     /* ------------------------------------------------------------ */
     public void appendArray(Appendable bufferCollection 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 bufferObject array)
     {
 	appendArray((Appendable)buffer,array);
     }
 
     /* ------------------------------------------------------------ */
     public void appendArray(Appendable bufferObject array)
     {
         try
         {
             if (array == null)
             {
                 appendNull(buffer);
                 return;
             }
 
             buffer.append('[');
             int length = Array.getLength(array);
 
             for (int i = 0; i < lengthi++)
             {
                 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 bufferBoolean b)
     {
         appendBoolean((Appendable)buffer,b);
     }
 
     /* ------------------------------------------------------------ */
     public void appendBoolean(Appendable bufferBoolean b)
     {
         try
         {
             if (b == null)
             {
                 appendNull(buffer);
                 return;
             }
             buffer.append(b.booleanValue()?"true":"false");
         }
         catch (IOException e)
         {
             throw new RuntimeException(e);
         }
     }
 
     /* ------------------------------------------------------------ */
     @Deprecated
     public void appendNumber(StringBuffer bufferNumber number)
     {
 	appendNumber((Appendable)buffer,number);
     }
 
     /* ------------------------------------------------------------ */
     public void appendNumber(Appendable bufferNumber 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 bufferString string)
     {
     	appendString((Appendable)buffer,string);
     }
 
     /* ------------------------------------------------------------ */
     public void appendString(Appendable bufferString string)
     {
         if (string == null)
         {
             appendNull(buffer);
             return;
         }
 
         QuotedStringTokenizer.quote(buffer,string);
     }
 
     // Parsing utilities
 
     /* ------------------------------------------------------------ */
     protected String toString(char[] bufferint offsetint length)
     {
         return new String(buffer,offset,length);
     }
 
     /* ------------------------------------------------------------ */
     protected Map<StringObjectnewMap()
     {
         return new HashMap<StringObject>();
     }
 
     /* ------------------------------------------------------------ */
     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 typeMap 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 JSON.Convertor for a class or interface.

Parameters:
forClass The class or interface that the convertor applies to
convertor the convertor
 
     public void addConvertor(Class forClassConvertor convertor)
     {
         .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.

Parameters:
forClass The class
Returns:
a JSON.Convertor or null if none were found.
 
     protected Convertor getConvertor(Class forClass)
     {
         Class cls = forClass;
         Convertor convertor = .get(cls.getName());
         if (convertor == null && this != )
             convertor = .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 = .get(ifs[i++].getName());
             if (convertor == null)
             {
                 cls = cls.getSuperclass();
                 convertor = .get(cls.getName());
             }
         }
         return convertor;
     }
 
     /* ------------------------------------------------------------ */
    
Register a JSON.Convertor for a named class or interface.

Parameters:
name name of a class or an interface that the convertor applies to
convertor the convertor
 
     public void addConvertorFor(String nameConvertor convertor)
     {
         .put(name,convertor);
     }
 
     /* ------------------------------------------------------------ */
    
Lookup a convertor for a named class.

Parameters:
name name of the class
Returns:
a JSON.Convertor or null if none were found.
 
     public Convertor getConvertorFor(String name)
     {
         String clsName = name;
         Convertor convertor = .get(clsName);
         if (convertor == null && this != )
             convertor = .getConvertorFor(clsName);
         return convertor;
     }
 
     /* ------------------------------------------------------------ */
     public Object parse(Source sourceboolean 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 .;
                     case 'f':
                         complete("false",source);
                         return .;
                     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 sourcechar c)
     {
         throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
     }
 
     /* ------------------------------------------------------------ */
     protected Object parseObject(Source source)
     {
         if (source.next() != '{')
             throw new IllegalStateException();
         Map<StringObjectmap = 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)
             {
                 .warn(e);
             }
         }
         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 seekSource 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 seekSource 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();
        }