View Javadoc

    1   //========================================================================
    2   //Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
    3   //------------------------------------------------------------------------
    4   //Licensed under the Apache License, Version 2.0 (the "License");
    5   //you may not use this file except in compliance with the License.
    6   //You may obtain a copy of the License at
    7   //http://www.apache.org/licenses/LICENSE-2.0
    8   //Unless required by applicable law or agreed to in writing, software
    9   //distributed under the License is distributed on an "AS IS" BASIS,
    10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  //See the License for the specific language governing permissions and
    12  //limitations under the License.
    13  //========================================================================
    14
    15  package org.mortbay.util.ajax;
    16
    17  import java.io.Externalizable;
    18  import java.io.IOException;
    19  import java.io.InputStream;
    20  import java.io.Reader;
    21  import java.lang.reflect.Array;
    22  import java.lang.reflect.Constructor;
    23  import java.util.ArrayList;
    24  import java.util.Collection;
    25  import java.util.HashMap;
    26  import java.util.Iterator;
    27  import java.util.Map;
    28
    29  import org.mortbay.log.Log;
    30  import org.mortbay.util.IO;
    31  import org.mortbay.util.Loader;
    32  import org.mortbay.util.QuotedStringTokenizer;
    33  import org.mortbay.util.TypeUtil;
    34
    35  /** JSON Parser and Generator.
    36   *
    37   * <p>This class provides some static methods to convert POJOs to and from JSON
    38   * notation.  The mapping from JSON to java is:<pre>
    39   *   object ==> Map
    40   *   array  ==> Object[]
    41   *   number ==> Double or Long
    42   *   string ==> String
    43   *   null   ==> null
    44   *   bool   ==> Boolean
    45   * </pre>
    46   * </p><p>
    47   * The java to JSON mapping is:<pre>
    48   *   String --> string
    49   *   Number --> number
    50   *   Map    --> object
    51   *   List   --> array
    52   *   Array  --> array
    53   *   null   --> null
    54   *   Boolean--> boolean
    55   *   Object --> string (dubious!)
    56   * </pre>
    57   * </p><p>
    58   * The interface {@link JSON.Convertible} may be implemented by classes that wish to externalize and
    59   * initialize specific fields to and from JSON objects.  Only directed acyclic graphs of objects are supported.
    60   * </p>
    61   * <p>
    62   * The interface {@link JSON.Generator} may be implemented by classes that know how to render themselves as JSON and
    63   * the {@link #toString(Object)} method will use {@link JSON.Generator#addJSON(StringBuffer)} to generate the JSON.
    64   * The class {@link JSON.Literal} may be used to hold pre-gnerated JSON object.
    65   * <p>
    66   * The interface {@link Convertor} may be implemented to provide static convertors for objects that may be registered
    67   * with {@link #registerConvertor(Class, org.mortbay.util.ajax.JSON.Convertor)}. These convertors are looked up by class, interface and
    68   * super class by {@link #getConvertor(Class)}.
    69   * </p>
    70   * @author gregw
    71   *
    72   */
    73  public class JSON
    74  {
    75      private static JSON __default = new JSON();
    76
    77      private Map _convertors=new HashMap();
    78      private int _stringBufferSize=256;
    79
    80
    81      public JSON()
    82      {
    83      }
    84
    85      /* ------------------------------------------------------------ */
    86      /**
    87       * @return the initial stringBuffer size to use when creating JSON strings (default 256)
    88       */
    89      public int getStringBufferSize()
    90      {
    91          return _stringBufferSize;
    92      }
    93
    94
    95
    96      /* ------------------------------------------------------------ */
    97      /**
    98       * @param stringBufferSize the initial stringBuffer size to use when creating JSON strings (default 256)
    99       */
    100     public void setStringBufferSize(int stringBufferSize)
    101     {
    102         _stringBufferSize=stringBufferSize;
    103     }
    104
    105
    106
    107
    108     /**
    109      * Register a {@link Convertor} for a class or interface.
    110      * @param forClass The class or interface that the convertor applies to
    111      * @param convertor the convertor
    112      */
    113     public static void registerConvertor(Class forClass, Convertor convertor)
    114     {
    115         __default.addConvertor(forClass,convertor);
    116     }
    117
    118     public static JSON getDefault()
    119     {
    120         return __default;
    121     }
    122
    123     public static void setDefault(JSON json)
    124     {
    125         __default=json;
    126     }
    127
    128     public static String toString(Object object)
    129     {
    130         StringBuffer buffer=new StringBuffer(__default.getStringBufferSize());
    131         synchronized (buffer)
    132         {
    133             __default.append(buffer,object);
    134             return buffer.toString();
    135         }
    136     }
    137
    138     public static String toString(Map object)
    139     {
    140         StringBuffer buffer=new StringBuffer(__default.getStringBufferSize());
    141         synchronized (buffer)
    142         {
    143             __default.appendMap(buffer,object);
    144             return buffer.toString();
    145         }
    146     }
    147
    148     public static String toString(Object[] array)
    149     {
    150         StringBuffer buffer=new StringBuffer(__default.getStringBufferSize());
    151         synchronized (buffer)
    152         {
    153             __default.appendArray(buffer,array);
    154             return buffer.toString();
    155         }
    156     }
    157
    158     /**
    159      * @param s String containing JSON object or array.
    160      * @return A Map, Object array or primitive array parsed from the JSON.
    161      */
    162     public static Object parse(String s)
    163     {
    164         return __default.parse(new StringSource(s),false);
    165     }
    166
    167     /**
    168      * @param s String containing JSON object or array.
    169      * @param stripOuterComment If true, an outer comment around the JSON is ignored.
    170      * @return A Map, Object array or primitive array parsed from the JSON.
    171      */
    172     public static Object parse(String s, boolean stripOuterComment)
    173     {
    174         return __default.parse(new StringSource(s),stripOuterComment);
    175     }
    176
    177     /**
    178      * @param in Reader containing JSON object or array.
    179      * @return A Map, Object array or primitive array parsed from the JSON.
    180      */
    181     public static Object parse(Reader in) throws IOException
    182     {
    183         return __default.parse(new ReaderSource(in),false);
    184     }
    185
    186     /**
    187      * @param s Stream containing JSON object or array.
    188      * @param stripOuterComment If true, an outer comment around the JSON is ignored.
    189      * @return A Map, Object array or primitive array parsed from the JSON.
    190      */
    191     public static Object parse(Reader in, boolean stripOuterComment) throws IOException
    192     {
    193         return __default.parse(new ReaderSource(in),stripOuterComment);
    194     }
    195
    196     /**
    197      * @deprecated use {@link #parse(Reader)}
    198      * @param in Reader containing JSON object or array.
    199      * @return A Map, Object array or primitive array parsed from the JSON.
    200      */
    201     public static Object parse(InputStream in) throws IOException
    202     {
    203         return __default.parse(new StringSource(IO.toString(in)),false);
    204     }
    205
    206     /**
    207      * @deprecated use {@link #parse(Reader, boolean)}
    208      * @param s Stream containing JSON object or array.
    209      * @param stripOuterComment If true, an outer comment around the JSON is ignored.
    210      * @return A Map, Object array or primitive array parsed from the JSON.
    211      */
    212     public static Object parse(InputStream in, boolean stripOuterComment) throws IOException
    213     {
    214         return __default.parse(new StringSource(IO.toString(in)),stripOuterComment);
    215     }
    216
    217     /* ------------------------------------------------------------ */
    218     /** Convert Object to JSON
    219      * @param object The object to convert
    220      * @return The JSON String
    221      */
    222     public String toJSON(Object object)
    223     {
    224         StringBuffer buffer=new StringBuffer(getStringBufferSize());
    225         synchronized (buffer)
    226         {
    227             append(buffer,object);
    228             return buffer.toString();
    229         }
    230     }
    231
    232     /* ------------------------------------------------------------ */
    233     /** Convert JSON to Object
    234      * @param json The json to convert
    235      * @return The object
    236      */
    237     public Object fromJSON(String json)
    238     {
    239         Source source = new StringSource(json);
    240         return parse(source);
    241     }
    242
    243     /**
    244      * Append object as JSON to string buffer.
    245      * @param buffer
    246      * @param object
    247      */
    248     public void append(StringBuffer buffer, Object object)
    249     {
    250         if (object==null)
    251             buffer.append("null");
    252         else if (object instanceof Convertible)
    253             appendJSON(buffer,(Convertible)object);
    254         else if (object instanceof Generator)
    255             appendJSON(buffer,(Generator)object);
    256         else if (object instanceof Map)
    257             appendMap(buffer,(Map)object);
    258         else if (object instanceof Collection)
    259             appendArray(buffer,(Collection)object);
    260         else if (object.getClass().isArray())
    261             appendArray(buffer,object);
    262         else if (object instanceof Number)
    263             appendNumber(buffer,(Number)object);
    264         else if (object instanceof Boolean)
    265             appendBoolean(buffer,(Boolean)object);
    266         else if (object instanceof String)
    267             appendString(buffer,(String)object);
    268         else
    269         {
    270             Convertor convertor=getConvertor(object.getClass());
    271             if (convertor!=null)
    272                 appendJSON(buffer,convertor,object);
    273             else
    274                 appendString(buffer,object.toString());
    275         }
    276     }
    277
    278     public void appendNull(StringBuffer buffer)
    279     {
    280         buffer.append("null");
    281     }
    282
    283     public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
    284     {
    285         appendJSON(buffer,new Convertible()
    286         {
    287             public void fromJSON(Map object)
    288             {
    289             }
    290
    291             public void toJSON(Output out)
    292             {
    293                 convertor.toJSON(object,out);
    294             }
    295         });
    296     }
    297
    298     public void appendJSON(final StringBuffer buffer, Convertible converter)
    299     {
    300         final char[] c=
    301         { '{' };
    302         converter.toJSON(new Output()
    303         {
    304             public void add(Object obj)
    305             {
    306                 if (c[0]==0)
    307                     throw new IllegalStateException();
    308                 append(buffer,obj);
    309                 c[0]=0;
    310             }
    311
    312             public void addClass(Class type)
    313             {
    314                 if (c[0]==0)
    315                     throw new IllegalStateException();
    316                 buffer.append(c);
    317                 buffer.append("\"class\":");
    318                 append(buffer,type.getName());
    319                 c[0]=',';
    320             }
    321
    322             public void add(String name, Object value)
    323             {
    324                 if (c[0]==0)
    325                     throw new IllegalStateException();
    326                 buffer.append(c);
    327                 QuotedStringTokenizer.quote(buffer,name);
    328                 buffer.append(':');
    329                 append(buffer,value);
    330                 c[0]=',';
    331             }
    332
    333             public void add(String name, double value)
    334             {
    335                 if (c[0]==0)
    336                     throw new IllegalStateException();
    337                 buffer.append(c);
    338                 QuotedStringTokenizer.quote(buffer,name);
    339                 buffer.append(':');
    340                 appendNumber(buffer,new Double(value));
    341                 c[0]=',';
    342             }
    343
    344             public void add(String name, long value)
    345             {
    346                 if (c[0]==0)
    347                     throw new IllegalStateException();
    348                 buffer.append(c);
    349                 QuotedStringTokenizer.quote(buffer,name);
    350                 buffer.append(':');
    351                 appendNumber(buffer,TypeUtil.newLong(value));
    352                 c[0]=',';
    353             }
    354
    355             public void add(String name, boolean value)
    356             {
    357                 if (c[0]==0)
    358                     throw new IllegalStateException();
    359                 buffer.append(c);
    360                 QuotedStringTokenizer.quote(buffer,name);
    361                 buffer.append(':');
    362                 appendBoolean(buffer,value?Boolean.TRUE:Boolean.FALSE);
    363                 c[0]=',';
    364             }
    365         });
    366
    367         if (c[0]=='{')
    368             buffer.append("{}");
    369         else if (c[0]!=0)
    370             buffer.append("}");
    371     }
    372
    373     public void appendJSON(StringBuffer buffer, Generator generator)
    374     {
    375         generator.addJSON(buffer);
    376     }
    377
    378     public void appendMap(StringBuffer buffer, Map object)
    379     {
    380         if (object==null)
    381         {
    382             appendNull(buffer);
    383             return;
    384         }
    385
    386         buffer.append('{');
    387         Iterator iter=object.entrySet().iterator();
    388         while (iter.hasNext())
    389         {
    390             Map.Entry entry=(Map.Entry)iter.next();
    391             QuotedStringTokenizer.quote(buffer,entry.getKey().toString());
    392             buffer.append(':');
    393             append(buffer,entry.getValue());
    394             if (iter.hasNext())
    395                 buffer.append(',');
    396         }
    397
    398         buffer.append('}');
    399     }
    400
    401     public void appendArray(StringBuffer buffer, Collection collection)
    402     {
    403         if (collection==null)
    404         {
    405             appendNull(buffer);
    406             return;
    407         }
    408
    409         buffer.append('[');
    410         Iterator iter=collection.iterator();
    411         boolean first=true;
    412         while (iter.hasNext())
    413         {
    414             if (!first)
    415                 buffer.append(',');
    416
    417             first=false;
    418             append(buffer,iter.next());
    419         }
    420
    421         buffer.append(']');
    422     }
    423
    424     public void appendArray(StringBuffer buffer, Object array)
    425     {
    426         if (array==null)
    427         {
    428             appendNull(buffer);
    429             return;
    430         }
    431
    432         buffer.append('[');
    433         int length=Array.getLength(array);
    434
    435         for (int i=0; i<length; i++)
    436         {
    437             if (i!=0)
    438                 buffer.append(',');
    439             append(buffer,Array.get(array,i));
    440         }
    441
    442         buffer.append(']');
    443     }
    444
    445     public void appendBoolean(StringBuffer buffer, Boolean b)
    446     {
    447         if (b==null)
    448         {
    449             appendNull(buffer);
    450             return;
    451         }
    452         buffer.append(b.booleanValue()?"true":"false");
    453     }
    454
    455     public void appendNumber(StringBuffer buffer, Number number)
    456     {
    457         if (number==null)
    458         {
    459             appendNull(buffer);
    460             return;
    461         }
    462         buffer.append(number);
    463     }
    464
    465     public void appendString(StringBuffer buffer, String string)
    466     {
    467         if (string==null)
    468         {
    469             appendNull(buffer);
    470             return;
    471         }
    472
    473         QuotedStringTokenizer.quote(buffer,string);
    474     }
    475
    476
    477
    478
    479
    480
    481
    482     // Parsing utilities
    483
    484     protected String toString(char[] buffer,int offset,int length)
    485     {
    486         return new String(buffer,offset,length);
    487     }
    488
    489     protected Map newMap()
    490     {
    491         return new HashMap();
    492     }
    493
    494     protected Object[] newArray(int size)
    495     {
    496         return new Object[size];
    497     }
    498
    499     protected JSON contextForArray()
    500     {
    501         return this;
    502     }
    503
    504     protected JSON contextFor(String field)
    505     {
    506         return this;
    507     }
    508
    509     protected Object convertTo(Class type,Map map)
    510     {
    511         if (type!=null&&Convertible.class.isAssignableFrom(type))
    512         {
    513             try
    514             {
    515                 Convertible conv=(Convertible)type.newInstance();
    516                 conv.fromJSON(map);
    517                 return conv;
    518             }
    519             catch (Exception e)
    520             {
    521                 throw new RuntimeException(e);
    522             }
    523         }
    524
    525         Convertor convertor=getConvertor(type);
    526         if (convertor!=null)
    527         {
    528             return convertor.fromJSON(map);
    529         }
    530         return map;
    531     }
    532
    533
    534     /**
    535      * Register a {@link Convertor} for a class or interface.
    536      * @param forClass The class or interface that the convertor applies to
    537      * @param convertor the convertor
    538      */
    539     public void addConvertor(Class forClass, Convertor convertor)
    540     {
    541         _convertors.put(forClass,convertor);
    542     }
    543
    544     /**
    545      * Lookup a convertor for a class.
    546      * <p>
    547      * If no match is found for the class, then the interfaces for the class are tried. If still no
    548      * match is found, then the super class and it's interfaces are tried recursively.
    549      * @param forClass The class
    550      * @return a {@link Convertor} or null if none were found.
    551      */
    552     protected Convertor getConvertor(Class forClass)
    553     {
    554         Class c=forClass;
    555         Convertor convertor=(Convertor)_convertors.get(c);
    556         if (convertor==null && this!=__default)
    557             convertor=__default.getConvertor(forClass);
    558
    559         while (convertor==null&&c!=null&&c!=Object.class)
    560         {
    561             Class[] ifs=c.getInterfaces();
    562             int i=0;
    563             while (convertor==null&&ifs!=null&&i<ifs.length)
    564                 convertor=(Convertor)_convertors.get(ifs[i++]);
    565             if (convertor==null)
    566             {
    567                 c=c.getSuperclass();
    568                 convertor=(Convertor)_convertors.get(c);
    569             }
    570         }
    571         return convertor;
    572     }
    573
    574
    575
    576     public Object parse(Source source, boolean stripOuterComment)
    577     {
    578         int comment_state=0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
    579         if (!stripOuterComment)
    580             return parse(source);
    581
    582         int strip_state=1; // 0=no strip, 1=wait for /*, 2= wait for */
    583
    584         Object o=null;
    585         while (source.hasNext())
    586         {
    587             char c=source.peek();
    588
    589             // handle // or /* comment
    590             if (comment_state==1)
    591             {
    592                 switch (c)
    593                 {
    594                     case '/':
    595                         comment_state=-1;
    596                         break;
    597                     case '*':
    598                         comment_state=2;
    599                         if (strip_state==1)
    600                         {
    601                             comment_state=0;
    602                             strip_state=2;
    603                         }
    604                 }
    605             }
    606             // handle /* */ comment
    607             else if (comment_state>1)
    608             {
    609                 switch (c)
    610                 {
    611                     case '*':
    612                         comment_state=3;
    613                         break;
    614                     case '/':
    615                         if (comment_state==3)
    616                         {
    617                             comment_state=0;
    618                             if (strip_state==2)
    619                                 return o;
    620                         }
    621                         else
    622                             comment_state=2;
    623                         break;
    624                     default:
    625                         comment_state=2;
    626                 }
    627             }
    628             // handle // comment
    629             else if (comment_state<0)
    630             {
    631                 switch (c)
    632                 {
    633                     case '\r':
    634                     case '\n':
    635                         comment_state=0;
    636                     default:
    637                         break;
    638                 }
    639             }
    640             // handle unknown
    641             else
    642             {
    643                 if (!Character.isWhitespace(c))
    644                 {
    645                     if (c=='/')
    646                         comment_state=1;
    647                     else if (c=='*')
    648                         comment_state=3;
    649                     else if (o==null)
    650                     {
    651                         o=parse(source);
    652                         continue;
    653                     }
    654                 }
    655             }
    656
    657             source.next();
    658         }
    659
    660         return o;
    661     }
    662
    663
    664     public Object parse(Source source)
    665     {
    666         int comment_state=0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
    667
    668         while (source.hasNext())
    669         {
    670             char c=source.peek();
    671
    672             // handle // or /* comment
    673             if (comment_state==1)
    674             {
    675                 switch (c)
    676                 {
    677                     case '/':
    678                         comment_state=-1;
    679                         break;
    680                     case '*':
    681                         comment_state=2;
    682                 }
    683             }
    684             // handle /* */ comment
    685             else if (comment_state>1)
    686             {
    687                 switch (c)
    688                 {
    689                     case '*':
    690                         comment_state=3;
    691                         break;
    692                     case '/':
    693                         if (comment_state==3)
    694                             comment_state=0;
    695                         else
    696                             comment_state=2;
    697                         break;
    698                     default:
    699                         comment_state=2;
    700                 }
    701             }
    702             // handle // comment
    703             else if (comment_state<0)
    704             {
    705                 switch (c)
    706                 {
    707                     case '\r':
    708                     case '\n':
    709                         comment_state=0;
    710                         break;
    711                     default:
    712                         break;
    713                 }
    714             }
    715             // handle unknown
    716             else
    717             {
    718                 switch (c)
    719                 {
    720                     case '{':
    721                         return parseObject(source);
    722                     case '[':
    723                         return parseArray(source);
    724                     case '"':
    725                         return parseString(source);
    726                     case '-':
    727                         return parseNumber(source);
    728
    729                     case 'n':
    730                         complete("null",source);
    731                         return null;
    732                     case 't':
    733                         complete("true",source);
    734                         return Boolean.TRUE;
    735                     case 'f':
    736                         complete("false",source);
    737                         return Boolean.FALSE;
    738                     case 'u':
    739                         complete("undefined",source);
    740                         return null;
    741
    742                     case '/':
    743                         comment_state=1;
    744                         break;
    745
    746                     default:
    747                         if (Character.isDigit(c))
    748                             return parseNumber(source);
    749                         else if (Character.isWhitespace(c))
    750                             break;
    751                         throw new IllegalStateException("unknown char '"+c+"'("+(int)c+") in "+source);
    752                 }
    753             }
    754             source.next();
    755         }
    756
    757         return null;
    758     }
    759
    760     protected Object parseObject(Source source)
    761     {
    762         if (source.next()!='{')
    763             throw new IllegalStateException();
    764         Map map=newMap();
    765
    766         char next=seekTo("\"}",source);
    767
    768         while (source.hasNext())
    769         {
    770             if (next=='}')
    771             {
    772                 source.next();
    773                 break;
    774             }
    775
    776             String name=parseString(source);
    777             seekTo(':',source);
    778             source.next();
    779
    780             Object value=contextFor(name).parse(source);
    781             map.put(name,value);
    782
    783             seekTo(",}",source);
    784             next=source.next();
    785             if (next=='}')
    786                 break;
    787             else
    788                 next=seekTo("\"}",source);
    789         }
    790
    791         String classname=(String)map.get("class");
    792         if (classname!=null)
    793         {
    794             try
    795             {
    796                 Class c=Loader.loadClass(JSON.class,classname);
    797                 return convertTo(c,map);
    798             }
    799             catch (ClassNotFoundException e)
    800             {
    801                 e.printStackTrace();
    802             }
    803         }
    804         return map;
    805     }
    806
    807
    808     private Object parseArray(Source source)
    809     {
    810         if (source.next()!='[')
    811             throw new IllegalStateException();
    812
    813         int size=0;
    814         ArrayList list=null;
    815         Object item=null;
    816         boolean coma=true;
    817
    818         while (source.hasNext())
    819         {
    820             char c=source.peek();
    821             switch (c)
    822             {
    823                 case ']':
    824                     source.next();
    825                     switch(size)
    826                     {
    827                         case 0:
    828                             return newArray(0);
    829                         case 1:
    830                             Object array = newArray(1);
    831                             Array.set(array,0,item);
    832                             return array;
    833                         default:
    834                             return list.toArray(newArray(list.size()));
    835                     }
    836
    837                 case ',':
    838                     if (coma)
    839                         throw new IllegalStateException();
    840                     coma=true;
    841                     source.next();
    842                     break;
    843
    844                 default:
    845                     if (Character.isWhitespace(c))
    846                         source.next();
    847                     else
    848                     {
    849                         coma=false;
    850                         if (size++==0)
    851                             item=contextForArray().parse(source);
    852                         else if (list==null)
    853                         {
    854                             list=new ArrayList();
    855                             list.add(item);
    856                             item=contextForArray().parse(source);
    857                             list.add(item);
    858                             item=null;
    859                         }
    860                         else
    861                         {
    862                             item=contextForArray().parse(source);
    863                             list.add(item);
    864                             item=null;
    865                         }
    866                     }
    867             }
    868
    869         }
    870
    871         throw new IllegalStateException("unexpected end of array");
    872     }
    873
    874
    875     private String parseString(Source source)
    876     {
    877         if (source.next()!='"')
    878             throw new IllegalStateException();
    879
    880         boolean escape=false;
    881
    882         StringBuffer b=null;
    883         final char[] scratch=source.scratchBuffer();
    884
    885         if (scratch!=null)
    886         {
    887             int i=0;
    888             while (source.hasNext())
    889             {
    890                 if(i>=scratch.length)
    891                 {
    892                     // we have filled the scratch buffer, so we must
    893                     // use the StringBuffer for a large string
    894                     b=new StringBuffer(scratch.length*2);
    895                     b.append(scratch,0,i);
    896                     break;
    897                 }
    898
    899                 char c=source.next();
    900
    901                 if (escape)
    902                 {
    903                     escape=false;
    904                     switch (c)
    905                     {
    906                         case '"':
    907                             scratch[i++]='"';
    908                             break;
    909                         case '\\':
    910                             scratch[i++]='\\';
    911                             break;
    912                         case '/':
    913                             scratch[i++]='/';
    914                             break;
    915                         case 'b':
    916                             scratch[i++]='\b';
    917                             break;
    918                         case 'f':
    919                             scratch[i++]='\f';
    920                             break;
    921                         case 'n':
    922                             scratch[i++]='\n';
    923                             break;
    924                         case 'r':
    925                             scratch[i++]='\r';
    926                             break;
    927                         case 't':
    928                             scratch[i++]='\t';
    929                             break;
    930                         case 'u':
    931                             char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+
    932                                     (TypeUtil.convertHexDigit((byte)source.next())<<8)+
    933                                     (TypeUtil.convertHexDigit((byte)source.next())<<4)+
    934                                     (TypeUtil.convertHexDigit((byte)source.next())));
    935                             scratch[i++]=uc;
    936                             break;
    937                         default:
    938                             scratch[i++]=c;
    939                     }
    940                 }
    941                 else if (c=='\\')
    942                 {
    943                     escape=true;
    944                     continue;
    945                 }
    946                 else if (c=='\"')
    947                 {
    948                     // Return string that fits within scratch buffer
    949                     return toString(scratch,0,i);
    950                 }
    951                 else
    952                     scratch[i++]=c;
    953             }
    954
    955             // Missing end quote, but return string anyway ?
    956             if (b==null)
    957                 return toString(scratch,0,i);
    958         }
    959         else
    960             b=new StringBuffer(getStringBufferSize());
    961
    962
    963         // parse large string into string buffer
    964         synchronized (b)
    965         {
    966             while (source.hasNext())
    967             {
    968                 char c=source.next();
    969
    970                 if (escape)
    971                 {
    972                     escape=false;
    973                     switch (c)
    974                     {
    975                         case '"':
    976                             b.append('"');
    977                             break;
    978                         case '\\':
    979                             b.append('\\');
    980                             break;
    981                         case '/':
    982                             b.append('/');
    983                             break;
    984                         case 'b':
    985                             b.append('\b');
    986                             break;
    987                         case 'f':
    988                             b.append('\f');
    989                             break;
    990                         case 'n':
    991                             b.append('\n');
    992                             break;
    993                         case 'r':
    994                             b.append('\r');
    995                             break;
    996                         case 't':
    997                             b.append('\t');
    998                             break;
    999                         case 'u':
    1000                             char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+
    1001                                     (TypeUtil.convertHexDigit((byte)source.next())<<8)+
    1002                                     (TypeUtil.convertHexDigit((byte)source.next())<<4)+
    1003                                     (TypeUtil.convertHexDigit((byte)source.next())));
    1004                             b.append(uc);
    1005                             break;
    1006                         default:
    1007                             b.append(c);
    1008                     }
    1009                 }
    1010                 else if (c=='\\')
    1011                 {
    1012                     escape=true;
    1013                     continue;
    1014                 }
    1015                 else if (c=='\"')
    1016                     break;
    1017                 else
    1018                     b.append(c);
    1019             }
    1020
    1021             return b.toString();
    1022         }
    1023     }
    1024
    1025     public Number parseNumber(Source source)
    1026     {
    1027         boolean minus=false;
    1028         long number=0;
    1029         StringBuffer buffer=null;
    1030
    1031         longLoop: while (source.hasNext())
    1032         {
    1033             char c=source.peek();
    1034             switch (c)
    1035             {
    1036                 case '0':
    1037                 case '1':
    1038                 case '2':
    1039                 case '3':
    1040                 case '4':
    1041                 case '5':
    1042                 case '6':
    1043                 case '7':
    1044                 case '8':
    1045                 case '9':
    1046                     number=number*10+(c-'0');
    1047                     source.next();
    1048                     break;
    1049
    1050                 case '-':
    1051                 case '+':
    1052                     if (number!=0)
    1053                         throw new IllegalStateException("bad number");
    1054                     minus=true;
    1055                     source.next();
    1056                     break;
    1057
    1058                 case '.':
    1059                 case 'e':
    1060                 case 'E':
    1061                     buffer=new StringBuffer(16);
    1062                     if(minus)
    1063                         buffer.append('-');
    1064                     buffer.append(number);
    1065                     buffer.append(c);
    1066                     source.next();
    1067                     break longLoop;
    1068
    1069                 default:
    1070                     break longLoop;
    1071             }
    1072         }
    1073
    1074         if (buffer==null)
    1075             return TypeUtil.newLong(minus?-1*number:number);
    1076
    1077         synchronized (buffer)
    1078         {
    1079             doubleLoop: while (source.hasNext())
    1080             {
    1081                 char c=source.peek();
    1082                 switch (c)
    1083                 {
    1084                     case '0':
    1085                     case '1':
    1086                     case '2':
    1087                     case '3':
    1088                     case '4':
    1089                     case '5':
    1090                     case '6':
    1091                     case '7':
    1092                     case '8':
    1093                     case '9':
    1094                     case '-':
    1095                     case '.':
    1096                     case '+':
    1097                     case 'e':
    1098                     case 'E':
    1099                         buffer.append(c);
    1100                         source.next();
    1101                         break;
    1102
    1103                     default:
    1104                         break doubleLoop;
    1105                 }
    1106             }
    1107             return new Double(buffer.toString());
    1108         }
    1109     }
    1110
    1111     protected void seekTo(char seek, Source source)
    1112     {
    1113         while (source.hasNext())
    1114         {
    1115             char c=source.peek();
    1116             if (c==seek)
    1117                 return;
    1118
    1119             if (!Character.isWhitespace(c))
    1120                 throw new IllegalStateException("Unexpected '"+c+" while seeking '"+seek+"'");
    1121             source.next();
    1122         }
    1123
    1124         throw new IllegalStateException("Expected '"+seek+"'");
    1125     }
    1126
    1127     protected char seekTo(String seek, Source source)
    1128     {
    1129         while (source.hasNext())
    1130         {
    1131             char c=source.peek();
    1132             if (seek.indexOf(c)>=0)
    1133             {
    1134                 return c;
    1135             }
    1136
    1137             if (!Character.isWhitespace(c))
    1138                 throw new IllegalStateException("Unexpected '"+c+"' while seeking one of '"+seek+"'");
    1139             source.next();
    1140         }
    1141
    1142         throw new IllegalStateException("Expected one of '"+seek+"'");
    1143     }
    1144
    1145     protected static void complete(String seek, Source source)
    1146     {
    1147         int i=0;
    1148         while (source.hasNext()&&i<seek.length())
    1149         {
    1150             char c=source.next();
    1151             if (c!=seek.charAt(i++))
    1152                 throw new IllegalStateException("Unexpected '"+c+" while seeking  \""+seek+"\"");
    1153         }
    1154
    1155         if (i<seek.length())
    1156             throw new IllegalStateException("Expected \""+seek+"\"");
    1157     }
    1158
    1159
    1160     public interface Source
    1161     {
    1162         boolean hasNext();
    1163
    1164         char next();
    1165
    1166         char peek();
    1167
    1168         char[] scratchBuffer();
    1169     }
    1170
    1171     public static class StringSource implements Source
    1172     {
    1173         private final String string;
    1174         private int index;
    1175         private char[] scratch;
    1176
    1177         public StringSource(String s)
    1178         {
    1179             string=s;
    1180         }
    1181
    1182         public boolean hasNext()
    1183         {
    1184             if (index<string.length())
    1185                 return true;
    1186             scratch=null;
    1187             return false;
    1188         }
    1189
    1190         public char next()
    1191         {
    1192             return string.charAt(index++);
    1193         }
    1194
    1195         public char peek()
    1196         {
    1197             return string.charAt(index);
    1198         }
    1199
    1200         public String toString()
    1201         {
    1202             return string.substring(0,index)+"|||"+string.substring(index);
    1203         }
    1204
    1205         public char[] scratchBuffer()
    1206         {
    1207             if (scratch==null)
    1208                 scratch=new char[string.length()];
    1209             return scratch;
    1210         }
    1211     }
    1212
    1213     public static class ReaderSource implements Source
    1214     {
    1215         private Reader _reader;
    1216         private int _next=-1;
    1217         private char[] scratch;
    1218
    1219         public ReaderSource(Reader r)
    1220         {
    1221             _reader=r;
    1222         }
    1223
    1224         public void setReader(Reader reader)
    1225         {
    1226             _reader=reader;
    1227             _next=-1;
    1228         }
    1229
    1230         public boolean hasNext()
    1231         {
    1232             getNext();
    1233             if (_next<0)
    1234             {
    1235                 scratch=null;
    1236                 return false;
    1237             }
    1238             return true;
    1239         }
    1240
    1241         public char next()
    1242         {
    1243             getNext();
    1244             char c=(char)_next;
    1245             _next=-1;
    1246             return c;
    1247         }
    1248
    1249         public char peek()
    1250         {
    1251             getNext();
    1252             return (char)_next;
    1253         }
    1254
    1255         private void getNext()
    1256         {
    1257             if (_next<0)
    1258             {
    1259                 try
    1260                 {
    1261                     _next=_reader.read();
    1262                 }
    1263                 catch (IOException e)
    1264                 {
    1265                     throw new RuntimeException(e);
    1266                 }
    1267             }
    1268         }
    1269
    1270         public char[] scratchBuffer()
    1271         {
    1272             if (scratch==null)
    1273                 scratch=new char[1024];
    1274             return scratch;
    1275         }
    1276
    1277     }
    1278
    1279     /* ------------------------------------------------------------ */
    1280     /**
    1281      * JSON Output class for use by {@link Convertible}.
    1282      */
    1283     public interface Output
    1284     {
    1285         public void addClass(Class c);
    1286
    1287         public void add(Object obj);
    1288
    1289         public void add(String name, Object value);
    1290
    1291         public void add(String name, double value);
    1292
    1293         public void add(String name, long value);
    1294
    1295         public void add(String name, boolean value);
    1296     }
    1297
    1298     /* ------------------------------------------------------------ */
    1299     /* ------------------------------------------------------------ */
    1300     /** JSON Convertible object.
    1301      * Object can implement this interface in a similar way to the
    1302      * {@link Externalizable} interface is used to allow classes to
    1303      * provide their own serialization mechanism.
    1304      * <p>
    1305      * A JSON.Convertible object may be written to a JSONObject
    1306      * or initialized from a Map of field names to values.
    1307      * <p>
    1308      * If the JSON is to be convertible back to an Object, then
    1309      * the method {@link Output#addClass(Class)} must be called from within toJSON()
    1310      *
    1311      */
    1312     public interface Convertible
    1313     {
    1314         public void toJSON(Output out);
    1315
    1316         public void fromJSON(Map object);
    1317     }
    1318
    1319     /* ------------------------------------------------------------ */
    1320     /** Static JSON Convertor.
    1321      * <p>
    1322      * may be implemented to provide static convertors for objects that may be registered
    1323      * with {@link JSON#registerConvertor(Class, org.mortbay.util.ajax.JSON.Convertor).
    1324      * These convertors are looked up by class, interface and
    1325      * super class by {@link JSON#getConvertor(Class)}.   Convertors should be used when the
    1326      * classes to be converted cannot implement {@link Convertible} or {@link Generator}.
    1327      */
    1328     public interface Convertor
    1329     {
    1330         public void toJSON(Object obj, Output out);
    1331
    1332         public Object fromJSON(Map object);
    1333     }
    1334
    1335     /* ------------------------------------------------------------ */
    1336     /** JSON Generator.
    1337      * A class that can add it's JSON representation directly to a StringBuffer.
    1338      * This is useful for object instances that are frequently converted and wish to
    1339      * avoid multiple Conversions
    1340      */
    1341     public interface Generator
    1342     {
    1343         public void addJSON(StringBuffer buffer);
    1344     }
    1345
    1346     /* ------------------------------------------------------------ */
    1347     /** A Literal JSON generator
    1348      * A utility instance of {@link JSON.Generator} that holds a pre-generated string on JSON text.
    1349      */
    1350     public static class Literal implements Generator
    1351     {
    1352         private String _json;
    1353
    1354         /* ------------------------------------------------------------ */
    1355         /** Construct a literal JSON instance for use by {@link JSON#toString(Object)}.
    1356          * If {@link Log#isDebugEnabled()} is true, the JSON will be parsed to check validity
    1357          * @param json A literal JSON string.
    1358          */
    1359         public Literal(String json)
    1360         {
    1361             if (Log.isDebugEnabled())
    1362                 parse(json);
    1363             _json=json;
    1364         }
    1365
    1366         public String toString()
    1367         {
    1368             return _json;
    1369         }
    1370
    1371         public void addJSON(StringBuffer buffer)
    1372         {
    1373             buffer.append(_json);
    1374         }
    1375     }
    1376 }