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.util.ArrayList;
    23  import java.util.Collection;
    24  import java.util.Collections;
    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=Collections.synchronizedMap(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.getName(),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 cls=forClass;
    555         Convertor convertor=(Convertor)_convertors.get(cls.getName());
    556         if (convertor==null && this!=__default)
    557             convertor=__default.getConvertor(cls);
    558
    559         while (convertor==null&&cls!=null&&cls!=Object.class)
    560         {
    561             Class[] ifs=cls.getInterfaces();
    562             int i=0;
    563             while (convertor==null&&ifs!=null&&i<ifs.length)
    564                 convertor=(Convertor)_convertors.get(ifs[i++].getName());
    565             if (convertor==null)
    566             {
    567                 cls=cls.getSuperclass();
    568                 convertor=(Convertor)_convertors.get(cls.getName());
    569             }
    570         }
    571         return convertor;
    572     }
    573
    574     /**
    575      * Register a {@link Convertor} for a named class or interface.
    576      * @param name name of a class or an interface that the convertor applies to
    577      * @param convertor the convertor
    578      */
    579     public void addConvertorFor(String name, Convertor convertor)
    580     {
    581         _convertors.put(name,convertor);
    582     }
    583
    584     /**
    585      * Lookup a convertor for a named class.
    586      *
    587      * @param name name of the class
    588      * @return a {@link Convertor} or null if none were found.
    589      */
    590     public Convertor getConvertorFor(String name)
    591     {
    592         String clsName=name;
    593         Convertor convertor=(Convertor)_convertors.get(clsName);
    594         if (convertor==null && this!=__default)
    595             convertor=__default.getConvertorFor(clsName);
    596         return convertor;
    597     }
    598
    599     public Object parse(Source source, boolean stripOuterComment)
    600     {
    601         int comment_state=0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
    602         if (!stripOuterComment)
    603             return parse(source);
    604
    605         int strip_state=1; // 0=no strip, 1=wait for /*, 2= wait for */
    606
    607         Object o=null;
    608         while (source.hasNext())
    609         {
    610             char c=source.peek();
    611
    612             // handle // or /* comment
    613             if (comment_state==1)
    614             {
    615                 switch (c)
    616                 {
    617                     case '/':
    618                         comment_state=-1;
    619                         break;
    620                     case '*':
    621                         comment_state=2;
    622                         if (strip_state==1)
    623                         {
    624                             comment_state=0;
    625                             strip_state=2;
    626                         }
    627                 }
    628             }
    629             // handle /* */ comment
    630             else if (comment_state>1)
    631             {
    632                 switch (c)
    633                 {
    634                     case '*':
    635                         comment_state=3;
    636                         break;
    637                     case '/':
    638                         if (comment_state==3)
    639                         {
    640                             comment_state=0;
    641                             if (strip_state==2)
    642                                 return o;
    643                         }
    644                         else
    645                             comment_state=2;
    646                         break;
    647                     default:
    648                         comment_state=2;
    649                 }
    650             }
    651             // handle // comment
    652             else if (comment_state<0)
    653             {
    654                 switch (c)
    655                 {
    656                     case '\r':
    657                     case '\n':
    658                         comment_state=0;
    659                     default:
    660                         break;
    661                 }
    662             }
    663             // handle unknown
    664             else
    665             {
    666                 if (!Character.isWhitespace(c))
    667                 {
    668                     if (c=='/')
    669                         comment_state=1;
    670                     else if (c=='*')
    671                         comment_state=3;
    672                     else if (o==null)
    673                     {
    674                         o=parse(source);
    675                         continue;
    676                     }
    677                 }
    678             }
    679
    680             source.next();
    681         }
    682
    683         return o;
    684     }
    685
    686
    687     public Object parse(Source source)
    688     {
    689         int comment_state=0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
    690
    691         while (source.hasNext())
    692         {
    693             char c=source.peek();
    694
    695             // handle // or /* comment
    696             if (comment_state==1)
    697             {
    698                 switch (c)
    699                 {
    700                     case '/':
    701                         comment_state=-1;
    702                         break;
    703                     case '*':
    704                         comment_state=2;
    705                 }
    706             }
    707             // handle /* */ comment
    708             else if (comment_state>1)
    709             {
    710                 switch (c)
    711                 {
    712                     case '*':
    713                         comment_state=3;
    714                         break;
    715                     case '/':
    716                         if (comment_state==3)
    717                             comment_state=0;
    718                         else
    719                             comment_state=2;
    720                         break;
    721                     default:
    722                         comment_state=2;
    723                 }
    724             }
    725             // handle // comment
    726             else if (comment_state<0)
    727             {
    728                 switch (c)
    729                 {
    730                     case '\r':
    731                     case '\n':
    732                         comment_state=0;
    733                         break;
    734                     default:
    735                         break;
    736                 }
    737             }
    738             // handle unknown
    739             else
    740             {
    741                 switch (c)
    742                 {
    743                     case '{':
    744                         return parseObject(source);
    745                     case '[':
    746                         return parseArray(source);
    747                     case '"':
    748                         return parseString(source);
    749                     case '-':
    750                         return parseNumber(source);
    751
    752                     case 'n':
    753                         complete("null",source);
    754                         return null;
    755                     case 't':
    756                         complete("true",source);
    757                         return Boolean.TRUE;
    758                     case 'f':
    759                         complete("false",source);
    760                         return Boolean.FALSE;
    761                     case 'u':
    762                         complete("undefined",source);
    763                         return null;
    764                     case 'N':
    765                         complete("NaN",source);
    766                         return null;
    767
    768                     case '/':
    769                         comment_state=1;
    770                         break;
    771
    772                     default:
    773                         if (Character.isDigit(c))
    774                             return parseNumber(source);
    775                         else if (Character.isWhitespace(c))
    776                             break;
    777                         return handleUnknown(source, c);
    778                 }
    779             }
    780             source.next();
    781         }
    782
    783         return null;
    784     }
    785
    786     protected Object handleUnknown(Source source, char c)
    787     {
    788         throw new IllegalStateException("unknown char '"+c+"'("+(int)c+") in "+source);
    789     }
    790
    791     protected Object parseObject(Source source)
    792     {
    793         if (source.next()!='{')
    794             throw new IllegalStateException();
    795         Map map=newMap();
    796
    797         char next=seekTo("\"}",source);
    798
    799         while (source.hasNext())
    800         {
    801             if (next=='}')
    802             {
    803                 source.next();
    804                 break;
    805             }
    806
    807             String name=parseString(source);
    808             seekTo(':',source);
    809             source.next();
    810
    811             Object value=contextFor(name).parse(source);
    812             map.put(name,value);
    813
    814             seekTo(",}",source);
    815             next=source.next();
    816             if (next=='}')
    817                 break;
    818             else
    819                 next=seekTo("\"}",source);
    820         }
    821
    822         String classname=(String)map.get("class");
    823         if (classname!=null)
    824         {
    825             try
    826             {
    827                 Class c=Loader.loadClass(JSON.class,classname);
    828                 return convertTo(c,map);
    829             }
    830             catch (ClassNotFoundException e)
    831             {
    832                 e.printStackTrace();
    833             }
    834         }
    835         return map;
    836     }
    837
    838
    839     protected Object parseArray(Source source)
    840     {
    841         if (source.next()!='[')
    842             throw new IllegalStateException();
    843
    844         int size=0;
    845         ArrayList list=null;
    846         Object item=null;
    847         boolean coma=true;
    848
    849         while (source.hasNext())
    850         {
    851             char c=source.peek();
    852             switch (c)
    853             {
    854                 case ']':
    855                     source.next();
    856                     switch(size)
    857                     {
    858                         case 0:
    859                             return newArray(0);
    860                         case 1:
    861                             Object array = newArray(1);
    862                             Array.set(array,0,item);
    863                             return array;
    864                         default:
    865                             return list.toArray(newArray(list.size()));
    866                     }
    867
    868                 case ',':
    869                     if (coma)
    870                         throw new IllegalStateException();
    871                     coma=true;
    872                     source.next();
    873                     break;
    874
    875                 default:
    876                     if (Character.isWhitespace(c))
    877                         source.next();
    878                     else
    879                     {
    880                         coma=false;
    881                         if (size++==0)
    882                             item=contextForArray().parse(source);
    883                         else if (list==null)
    884                         {
    885                             list=new ArrayList();
    886                             list.add(item);
    887                             item=contextForArray().parse(source);
    888                             list.add(item);
    889                             item=null;
    890                         }
    891                         else
    892                         {
    893                             item=contextForArray().parse(source);
    894                             list.add(item);
    895                             item=null;
    896                         }
    897                     }
    898             }
    899
    900         }
    901
    902         throw new IllegalStateException("unexpected end of array");
    903     }
    904
    905
    906     protected String parseString(Source source)
    907     {
    908         if (source.next()!='"')
    909             throw new IllegalStateException();
    910
    911         boolean escape=false;
    912
    913         StringBuffer b=null;
    914         final char[] scratch=source.scratchBuffer();
    915
    916         if (scratch!=null)
    917         {
    918             int i=0;
    919             while (source.hasNext())
    920             {
    921                 if(i>=scratch.length)
    922                 {
    923                     // we have filled the scratch buffer, so we must
    924                     // use the StringBuffer for a large string
    925                     b=new StringBuffer(scratch.length*2);
    926                     b.append(scratch,0,i);
    927                     break;
    928                 }
    929
    930                 char c=source.next();
    931
    932                 if (escape)
    933                 {
    934                     escape=false;
    935                     switch (c)
    936                     {
    937                         case '"':
    938                             scratch[i++]='"';
    939                             break;
    940                         case '\\':
    941                             scratch[i++]='\\';
    942                             break;
    943                         case '/':
    944                             scratch[i++]='/';
    945                             break;
    946                         case 'b':
    947                             scratch[i++]='\b';
    948                             break;
    949                         case 'f':
    950                             scratch[i++]='\f';
    951                             break;
    952                         case 'n':
    953                             scratch[i++]='\n';
    954                             break;
    955                         case 'r':
    956                             scratch[i++]='\r';
    957                             break;
    958                         case 't':
    959                             scratch[i++]='\t';
    960                             break;
    961                         case 'u':
    962                             char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+
    963                                     (TypeUtil.convertHexDigit((byte)source.next())<<8)+
    964                                     (TypeUtil.convertHexDigit((byte)source.next())<<4)+
    965                                     (TypeUtil.convertHexDigit((byte)source.next())));
    966                             scratch[i++]=uc;
    967                             break;
    968                         default:
    969                             scratch[i++]=c;
    970                     }
    971                 }
    972                 else if (c=='\\')
    973                 {
    974                     escape=true;
    975                     continue;
    976                 }
    977                 else if (c=='\"')
    978                 {
    979                     // Return string that fits within scratch buffer
    980                     return toString(scratch,0,i);
    981                 }
    982                 else
    983                     scratch[i++]=c;
    984             }
    985
    986             // Missing end quote, but return string anyway ?
    987             if (b==null)
    988                 return toString(scratch,0,i);
    989         }
    990         else
    991             b=new StringBuffer(getStringBufferSize());
    992
    993
    994         // parse large string into string buffer
    995         synchronized (b)
    996         {
    997             while (source.hasNext())
    998             {
    999                 char c=source.next();
    1000
    1001                 if (escape)
    1002                 {
    1003                     escape=false;
    1004                     switch (c)
    1005                     {
    1006                         case '"':
    1007                             b.append('"');
    1008                             break;
    1009                         case '\\':
    1010                             b.append('\\');
    1011                             break;
    1012                         case '/':
    1013                             b.append('/');
    1014                             break;
    1015                         case 'b':
    1016                             b.append('\b');
    1017                             break;
    1018                         case 'f':
    1019                             b.append('\f');
    1020                             break;
    1021                         case 'n':
    1022                             b.append('\n');
    1023                             break;
    1024                         case 'r':
    1025                             b.append('\r');
    1026                             break;
    1027                         case 't':
    1028                             b.append('\t');
    1029                             break;
    1030                         case 'u':
    1031                             char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+
    1032                                     (TypeUtil.convertHexDigit((byte)source.next())<<8)+
    1033                                     (TypeUtil.convertHexDigit((byte)source.next())<<4)+
    1034                                     (TypeUtil.convertHexDigit((byte)source.next())));
    1035                             b.append(uc);
    1036                             break;
    1037                         default:
    1038                             b.append(c);
    1039                     }
    1040                 }
    1041                 else if (c=='\\')
    1042                 {
    1043                     escape=true;
    1044                     continue;
    1045                 }
    1046                 else if (c=='\"')
    1047                     break;
    1048                 else
    1049                     b.append(c);
    1050             }
    1051
    1052             return b.toString();
    1053         }
    1054     }
    1055
    1056     public Number parseNumber(Source source)
    1057     {
    1058         boolean minus=false;
    1059         long number=0;
    1060         StringBuffer buffer=null;
    1061
    1062         longLoop: while (source.hasNext())
    1063         {
    1064             char c=source.peek();
    1065             switch (c)
    1066             {
    1067                 case '0':
    1068                 case '1':
    1069                 case '2':
    1070                 case '3':
    1071                 case '4':
    1072                 case '5':
    1073                 case '6':
    1074                 case '7':
    1075                 case '8':
    1076                 case '9':
    1077                     number=number*10+(c-'0');
    1078                     source.next();
    1079                     break;
    1080
    1081                 case '-':
    1082                 case '+':
    1083                     if (number!=0)
    1084                         throw new IllegalStateException("bad number");
    1085                     minus=true;
    1086                     source.next();
    1087                     break;
    1088
    1089                 case '.':
    1090                 case 'e':
    1091                 case 'E':
    1092                     buffer=new StringBuffer(16);
    1093                     if(minus)
    1094                         buffer.append('-');
    1095                     buffer.append(number);
    1096                     buffer.append(c);
    1097                     source.next();
    1098                     break longLoop;
    1099
    1100                 default:
    1101                     break longLoop;
    1102             }
    1103         }
    1104
    1105         if (buffer==null)
    1106             return TypeUtil.newLong(minus?-1*number:number);
    1107
    1108         synchronized (buffer)
    1109         {
    1110             doubleLoop: while (source.hasNext())
    1111             {
    1112                 char c=source.peek();
    1113                 switch (c)
    1114                 {
    1115                     case '0':
    1116                     case '1':
    1117                     case '2':
    1118                     case '3':
    1119                     case '4':
    1120                     case '5':
    1121                     case '6':
    1122                     case '7':
    1123                     case '8':
    1124                     case '9':
    1125                     case '-':
    1126                     case '.':
    1127                     case '+':
    1128                     case 'e':
    1129                     case 'E':
    1130                         buffer.append(c);
    1131                         source.next();
    1132                         break;
    1133
    1134                     default:
    1135                         break doubleLoop;
    1136                 }
    1137             }
    1138             return new Double(buffer.toString());
    1139         }
    1140     }
    1141
    1142     protected void seekTo(char seek, Source source)
    1143     {
    1144         while (source.hasNext())
    1145         {
    1146             char c=source.peek();
    1147             if (c==seek)
    1148                 return;
    1149
    1150             if (!Character.isWhitespace(c))
    1151                 throw new IllegalStateException("Unexpected '"+c+" while seeking '"+seek+"'");
    1152             source.next();
    1153         }
    1154
    1155         throw new IllegalStateException("Expected '"+seek+"'");
    1156     }
    1157
    1158     protected char seekTo(String seek, Source source)
    1159     {
    1160         while (source.hasNext())
    1161         {
    1162             char c=source.peek();
    1163             if (seek.indexOf(c)>=0)
    1164             {
    1165                 return c;
    1166             }
    1167
    1168             if (!Character.isWhitespace(c))
    1169                 throw new IllegalStateException("Unexpected '"+c+"' while seeking one of '"+seek+"'");
    1170             source.next();
    1171         }
    1172
    1173         throw new IllegalStateException("Expected one of '"+seek+"'");
    1174     }
    1175
    1176     protected static void complete(String seek, Source source)
    1177     {
    1178         int i=0;
    1179         while (source.hasNext()&&i<seek.length())
    1180         {
    1181             char c=source.next();
    1182             if (c!=seek.charAt(i++))
    1183                 throw new IllegalStateException("Unexpected '"+c+" while seeking  \""+seek+"\"");
    1184         }
    1185
    1186         if (i<seek.length())
    1187             throw new IllegalStateException("Expected \""+seek+"\"");
    1188     }
    1189
    1190
    1191     public interface Source
    1192     {
    1193         boolean hasNext();
    1194
    1195         char next();
    1196
    1197         char peek();
    1198
    1199         char[] scratchBuffer();
    1200     }
    1201
    1202     public static class StringSource implements Source
    1203     {
    1204         private final String string;
    1205         private int index;
    1206         private char[] scratch;
    1207
    1208         public StringSource(String s)
    1209         {
    1210             string=s;
    1211         }
    1212
    1213         public boolean hasNext()
    1214         {
    1215             if (index<string.length())
    1216                 return true;
    1217             scratch=null;
    1218             return false;
    1219         }
    1220
    1221         public char next()
    1222         {
    1223             return string.charAt(index++);
    1224         }
    1225
    1226         public char peek()
    1227         {
    1228             return string.charAt(index);
    1229         }
    1230
    1231         public String toString()
    1232         {
    1233             return string.substring(0,index)+"|||"+string.substring(index);
    1234         }
    1235
    1236         public char[] scratchBuffer()
    1237         {
    1238             if (scratch==null)
    1239                 scratch=new char[string.length()];
    1240             return scratch;
    1241         }
    1242     }
    1243
    1244     public static class ReaderSource implements Source
    1245     {
    1246         private Reader _reader;
    1247         private int _next=-1;
    1248         private char[] scratch;
    1249
    1250         public ReaderSource(Reader r)
    1251         {
    1252             _reader=r;
    1253         }
    1254
    1255         public void setReader(Reader reader)
    1256         {
    1257             _reader=reader;
    1258             _next=-1;
    1259         }
    1260
    1261         public boolean hasNext()
    1262         {
    1263             getNext();
    1264             if (_next<0)
    1265             {
    1266                 scratch=null;
    1267                 return false;
    1268             }
    1269             return true;
    1270         }
    1271
    1272         public char next()
    1273         {
    1274             getNext();
    1275             char c=(char)_next;
    1276             _next=-1;
    1277             return c;
    1278         }
    1279
    1280         public char peek()
    1281         {
    1282             getNext();
    1283             return (char)_next;
    1284         }
    1285
    1286         private void getNext()
    1287         {
    1288             if (_next<0)
    1289             {
    1290                 try
    1291                 {
    1292                     _next=_reader.read();
    1293                 }
    1294                 catch (IOException e)
    1295                 {
    1296                     throw new RuntimeException(e);
    1297                 }
    1298             }
    1299         }
    1300
    1301         public char[] scratchBuffer()
    1302         {
    1303             if (scratch==null)
    1304                 scratch=new char[1024];
    1305             return scratch;
    1306         }
    1307
    1308     }
    1309
    1310     /* ------------------------------------------------------------ */
    1311     /**
    1312      * JSON Output class for use by {@link Convertible}.
    1313      */
    1314     public interface Output
    1315     {
    1316         public void addClass(Class c);
    1317
    1318         public void add(Object obj);
    1319
    1320         public void add(String name, Object value);
    1321
    1322         public void add(String name, double value);
    1323
    1324         public void add(String name, long value);
    1325
    1326         public void add(String name, boolean value);
    1327     }
    1328
    1329     /* ------------------------------------------------------------ */
    1330     /* ------------------------------------------------------------ */
    1331     /** JSON Convertible object.
    1332      * Object can implement this interface in a similar way to the
    1333      * {@link Externalizable} interface is used to allow classes to
    1334      * provide their own serialization mechanism.
    1335      * <p>
    1336      * A JSON.Convertible object may be written to a JSONObject
    1337      * or initialized from a Map of field names to values.
    1338      * <p>
    1339      * If the JSON is to be convertible back to an Object, then
    1340      * the method {@link Output#addClass(Class)} must be called from within toJSON()
    1341      *
    1342      */
    1343     public interface Convertible
    1344     {
    1345         public void toJSON(Output out);
    1346
    1347         public void fromJSON(Map object);
    1348     }
    1349
    1350     /* ------------------------------------------------------------ */
    1351     /** Static JSON Convertor.
    1352      * <p>
    1353      * may be implemented to provide static convertors for objects that may be registered
    1354      * with {@link JSON#registerConvertor(Class, org.mortbay.util.ajax.JSON.Convertor).
    1355      * These convertors are looked up by class, interface and
    1356      * super class by {@link JSON#getConvertor(Class)}.   Convertors should be used when the
    1357      * classes to be converted cannot implement {@link Convertible} or {@link Generator}.
    1358      */
    1359     public interface Convertor
    1360     {
    1361         public void toJSON(Object obj, Output out);
    1362
    1363         public Object fromJSON(Map object);
    1364     }
    1365
    1366     /* ------------------------------------------------------------ */
    1367     /** JSON Generator.
    1368      * A class that can add it's JSON representation directly to a StringBuffer.
    1369      * This is useful for object instances that are frequently converted and wish to
    1370      * avoid multiple Conversions
    1371      */
    1372     public interface Generator
    1373     {
    1374         public void addJSON(StringBuffer buffer);
    1375     }
    1376
    1377     /* ------------------------------------------------------------ */
    1378     /** A Literal JSON generator
    1379      * A utility instance of {@link JSON.Generator} that holds a pre-generated string on JSON text.
    1380      */
    1381     public static class Literal implements Generator
    1382     {
    1383         private String _json;
    1384
    1385         /* ------------------------------------------------------------ */
    1386         /** Construct a literal JSON instance for use by {@link JSON#toString(Object)}.
    1387          * If {@link Log#isDebugEnabled()} is true, the JSON will be parsed to check validity
    1388          * @param json A literal JSON string.
    1389          */
    1390         public Literal(String json)
    1391         {
    1392             if (Log.isDebugEnabled())
    1393                 parse(json);
    1394             _json=json;
    1395         }
    1396
    1397         public String toString()
    1398         {
    1399             return _json;
    1400         }
    1401
    1402         public void addJSON(StringBuffer buffer)
    1403         {
    1404             buffer.append(_json);
    1405         }
    1406     }
    1407 }