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                         return handleUnknown(source, c);
752                 }
753             }
754             source.next();
755         }
756 
757         return null;
758     }
759     
760     protected Object handleUnknown(Source source, char c)
761     {
762         throw new IllegalStateException("unknown char '"+c+"'("+(int)c+") in "+source);
763     }
764 
765     protected Object parseObject(Source source)
766     {
767         if (source.next()!='{')
768             throw new IllegalStateException();
769         Map map=newMap();
770 
771         char next=seekTo("\"}",source);
772 
773         while (source.hasNext())
774         {
775             if (next=='}')
776             {
777                 source.next();
778                 break;
779             }
780 
781             String name=parseString(source);
782             seekTo(':',source);
783             source.next();
784 
785             Object value=contextFor(name).parse(source);
786             map.put(name,value);
787 
788             seekTo(",}",source);
789             next=source.next();
790             if (next=='}')
791                 break;
792             else
793                 next=seekTo("\"}",source);
794         }
795 
796         String classname=(String)map.get("class");
797         if (classname!=null)
798         {
799             try
800             {
801                 Class c=Loader.loadClass(JSON.class,classname);
802                 return convertTo(c,map);
803             }
804             catch (ClassNotFoundException e)
805             {
806                 e.printStackTrace();
807             }
808         }
809         return map;
810     }
811     
812 
813     protected Object parseArray(Source source)
814     {
815         if (source.next()!='[')
816             throw new IllegalStateException();
817 
818         int size=0;
819         ArrayList list=null;
820         Object item=null;
821         boolean coma=true;
822 
823         while (source.hasNext())
824         {
825             char c=source.peek();
826             switch (c)
827             {
828                 case ']':
829                     source.next();
830                     switch(size)
831                     {
832                         case 0:
833                             return newArray(0);
834                         case 1:
835                             Object array = newArray(1);
836                             Array.set(array,0,item);
837                             return array;
838                         default:
839                             return list.toArray(newArray(list.size()));
840                     }
841 
842                 case ',':
843                     if (coma)
844                         throw new IllegalStateException();
845                     coma=true;
846                     source.next();
847                     break;
848 
849                 default:
850                     if (Character.isWhitespace(c))
851                         source.next();
852                     else
853                     {
854                         coma=false;
855                         if (size++==0)
856                             item=contextForArray().parse(source);
857                         else if (list==null)
858                         {
859                             list=new ArrayList();
860                             list.add(item);
861                             item=contextForArray().parse(source);
862                             list.add(item);
863                             item=null;
864                         }
865                         else
866                         {
867                             item=contextForArray().parse(source);
868                             list.add(item);
869                             item=null;
870                         }
871                     }
872             }
873 
874         }
875 
876         throw new IllegalStateException("unexpected end of array");
877     }
878     
879 
880     protected String parseString(Source source)
881     {
882         if (source.next()!='"')
883             throw new IllegalStateException();
884 
885         boolean escape=false;
886 
887         StringBuffer b=null;
888         final char[] scratch=source.scratchBuffer();
889         
890         if (scratch!=null)
891         {
892             int i=0;
893             while (source.hasNext())
894             {
895                 if(i>=scratch.length)
896                 {
897                     // we have filled the scratch buffer, so we must
898                     // use the StringBuffer for a large string
899                     b=new StringBuffer(scratch.length*2);
900                     b.append(scratch,0,i);
901                     break;
902                 }
903 
904                 char c=source.next();
905 
906                 if (escape)
907                 {
908                     escape=false;
909                     switch (c)
910                     {
911                         case '"':
912                             scratch[i++]='"';
913                             break;
914                         case '\\':
915                             scratch[i++]='\\';
916                             break;
917                         case '/':
918                             scratch[i++]='/';
919                             break;
920                         case 'b':
921                             scratch[i++]='\b';
922                             break;
923                         case 'f':
924                             scratch[i++]='\f';
925                             break;
926                         case 'n':
927                             scratch[i++]='\n';
928                             break;
929                         case 'r':
930                             scratch[i++]='\r';
931                             break;
932                         case 't':
933                             scratch[i++]='\t';
934                             break;
935                         case 'u':
936                             char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+
937                                     (TypeUtil.convertHexDigit((byte)source.next())<<8)+
938                                     (TypeUtil.convertHexDigit((byte)source.next())<<4)+
939                                     (TypeUtil.convertHexDigit((byte)source.next())));
940                             scratch[i++]=uc;
941                             break;
942                         default:
943                             scratch[i++]=c;
944                     }
945                 }
946                 else if (c=='\\')
947                 {
948                     escape=true;
949                     continue;
950                 }
951                 else if (c=='\"')
952                 {
953                     // Return string that fits within scratch buffer
954                     return toString(scratch,0,i);
955                 }
956                 else
957                     scratch[i++]=c;
958             }
959             
960             // Missing end quote, but return string anyway ?
961             if (b==null)
962                 return toString(scratch,0,i);
963         }
964         else
965             b=new StringBuffer(getStringBufferSize());
966         
967         
968         // parse large string into string buffer
969         synchronized (b)
970         {
971             while (source.hasNext())
972             {
973                 char c=source.next();
974 
975                 if (escape)
976                 {
977                     escape=false;
978                     switch (c)
979                     {
980                         case '"':
981                             b.append('"');
982                             break;
983                         case '\\':
984                             b.append('\\');
985                             break;
986                         case '/':
987                             b.append('/');
988                             break;
989                         case 'b':
990                             b.append('\b');
991                             break;
992                         case 'f':
993                             b.append('\f');
994                             break;
995                         case 'n':
996                             b.append('\n');
997                             break;
998                         case 'r':
999                             b.append('\r');
1000                             break;
1001                         case 't':
1002                             b.append('\t');
1003                             break;
1004                         case 'u':
1005                             char uc=(char)((TypeUtil.convertHexDigit((byte)source.next())<<12)+
1006                                     (TypeUtil.convertHexDigit((byte)source.next())<<8)+
1007                                     (TypeUtil.convertHexDigit((byte)source.next())<<4)+
1008                                     (TypeUtil.convertHexDigit((byte)source.next())));
1009                             b.append(uc);
1010                             break;
1011                         default:
1012                             b.append(c);
1013                     }
1014                 }
1015                 else if (c=='\\')
1016                 {
1017                     escape=true;
1018                     continue;
1019                 }
1020                 else if (c=='\"')
1021                     break;
1022                 else
1023                     b.append(c);
1024             }
1025 
1026             return b.toString();
1027         }
1028     }
1029 
1030     public Number parseNumber(Source source)
1031     {
1032         boolean minus=false;
1033         long number=0;
1034         StringBuffer buffer=null;
1035 
1036         longLoop: while (source.hasNext())
1037         {
1038             char c=source.peek();
1039             switch (c)
1040             {
1041                 case '0':
1042                 case '1':
1043                 case '2':
1044                 case '3':
1045                 case '4':
1046                 case '5':
1047                 case '6':
1048                 case '7':
1049                 case '8':
1050                 case '9':
1051                     number=number*10+(c-'0');
1052                     source.next();
1053                     break;
1054 
1055                 case '-':
1056                 case '+':
1057                     if (number!=0)
1058                         throw new IllegalStateException("bad number");
1059                     minus=true;
1060                     source.next();
1061                     break;
1062 
1063                 case '.':
1064                 case 'e':
1065                 case 'E':
1066                     buffer=new StringBuffer(16);
1067                     if(minus) 
1068                         buffer.append('-');
1069                     buffer.append(number);
1070                     buffer.append(c);
1071                     source.next();
1072                     break longLoop;
1073 
1074                 default:
1075                     break longLoop;
1076             }
1077         }
1078 
1079         if (buffer==null)
1080             return TypeUtil.newLong(minus?-1*number:number);
1081 
1082         synchronized (buffer)
1083         {
1084             doubleLoop: while (source.hasNext())
1085             {
1086                 char c=source.peek();
1087                 switch (c)
1088                 {
1089                     case '0':
1090                     case '1':
1091                     case '2':
1092                     case '3':
1093                     case '4':
1094                     case '5':
1095                     case '6':
1096                     case '7':
1097                     case '8':
1098                     case '9':
1099                     case '-':
1100                     case '.':
1101                     case '+':
1102                     case 'e':
1103                     case 'E':
1104                         buffer.append(c);
1105                         source.next();
1106                         break;
1107 
1108                     default:
1109                         break doubleLoop;
1110                 }
1111             }
1112             return new Double(buffer.toString());
1113         }
1114     }
1115 
1116     protected void seekTo(char seek, Source source)
1117     {
1118         while (source.hasNext())
1119         {
1120             char c=source.peek();
1121             if (c==seek)
1122                 return;
1123 
1124             if (!Character.isWhitespace(c))
1125                 throw new IllegalStateException("Unexpected '"+c+" while seeking '"+seek+"'");
1126             source.next();
1127         }
1128 
1129         throw new IllegalStateException("Expected '"+seek+"'");
1130     }
1131 
1132     protected char seekTo(String seek, Source source)
1133     {
1134         while (source.hasNext())
1135         {
1136             char c=source.peek();
1137             if (seek.indexOf(c)>=0)
1138             {
1139                 return c;
1140             }
1141 
1142             if (!Character.isWhitespace(c))
1143                 throw new IllegalStateException("Unexpected '"+c+"' while seeking one of '"+seek+"'");
1144             source.next();
1145         }
1146 
1147         throw new IllegalStateException("Expected one of '"+seek+"'");
1148     }
1149 
1150     protected static void complete(String seek, Source source)
1151     {
1152         int i=0;
1153         while (source.hasNext()&&i<seek.length())
1154         {
1155             char c=source.next();
1156             if (c!=seek.charAt(i++))
1157                 throw new IllegalStateException("Unexpected '"+c+" while seeking  \""+seek+"\"");
1158         }
1159 
1160         if (i<seek.length())
1161             throw new IllegalStateException("Expected \""+seek+"\"");
1162     }
1163 
1164     
1165     public interface Source
1166     {
1167         boolean hasNext();
1168 
1169         char next();
1170 
1171         char peek();
1172         
1173         char[] scratchBuffer();
1174     }
1175 
1176     public static class StringSource implements Source
1177     {
1178         private final String string;
1179         private int index;
1180         private char[] scratch;
1181 
1182         public StringSource(String s)
1183         {
1184             string=s;
1185         }
1186 
1187         public boolean hasNext()
1188         {
1189             if (index<string.length())
1190                 return true;
1191             scratch=null;
1192             return false;
1193         }
1194 
1195         public char next()
1196         {
1197             return string.charAt(index++);
1198         }
1199 
1200         public char peek()
1201         {
1202             return string.charAt(index);
1203         }
1204         
1205         public String toString()
1206         {
1207             return string.substring(0,index)+"|||"+string.substring(index);
1208         }
1209 
1210         public char[] scratchBuffer()
1211         {
1212             if (scratch==null)
1213                 scratch=new char[string.length()];
1214             return scratch;
1215         }
1216     }
1217 
1218     public static class ReaderSource implements Source
1219     {
1220         private Reader _reader;
1221         private int _next=-1;
1222         private char[] scratch;
1223 
1224         public ReaderSource(Reader r)
1225         {
1226             _reader=r;
1227         }
1228         
1229         public void setReader(Reader reader)
1230         {
1231             _reader=reader;
1232             _next=-1;
1233         }
1234 
1235         public boolean hasNext()
1236         {
1237             getNext();
1238             if (_next<0)
1239             {
1240                 scratch=null;
1241                 return false;
1242             }
1243             return true;
1244         }
1245 
1246         public char next()
1247         {
1248             getNext();
1249             char c=(char)_next;
1250             _next=-1;
1251             return c;
1252         }
1253 
1254         public char peek()
1255         {
1256             getNext();
1257             return (char)_next;
1258         }
1259 
1260         private void getNext()
1261         {
1262             if (_next<0)
1263             {
1264                 try
1265                 {
1266                     _next=_reader.read();
1267                 }
1268                 catch (IOException e)
1269                 {
1270                     throw new RuntimeException(e);
1271                 }
1272             }
1273         }
1274 
1275         public char[] scratchBuffer()
1276         {
1277             if (scratch==null)
1278                 scratch=new char[1024];
1279             return scratch;
1280         }
1281 
1282     }
1283 
1284     /* ------------------------------------------------------------ */
1285     /** 
1286      * JSON Output class for use by {@link Convertible}.
1287      */
1288     public interface Output
1289     {
1290         public void addClass(Class c);
1291 
1292         public void add(Object obj);
1293 
1294         public void add(String name, Object value);
1295 
1296         public void add(String name, double value);
1297 
1298         public void add(String name, long value);
1299 
1300         public void add(String name, boolean value);
1301     }
1302 
1303     /* ------------------------------------------------------------ */
1304     /* ------------------------------------------------------------ */
1305     /** JSON Convertible object.
1306      * Object can implement this interface in a similar way to the 
1307      * {@link Externalizable} interface is used to allow classes to
1308      * provide their own serialization mechanism.
1309      * <p>
1310      * A JSON.Convertible object may be written to a JSONObject 
1311      * or initialized from a Map of field names to values.
1312      * <p>
1313      * If the JSON is to be convertible back to an Object, then
1314      * the method {@link Output#addClass(Class)} must be called from within toJSON()
1315      *
1316      */
1317     public interface Convertible
1318     {
1319         public void toJSON(Output out);
1320 
1321         public void fromJSON(Map object);
1322     }
1323 
1324     /* ------------------------------------------------------------ */
1325     /** Static JSON Convertor.
1326      * <p>
1327      * may be implemented to provide static convertors for objects that may be registered 
1328      * with {@link JSON#registerConvertor(Class, org.mortbay.util.ajax.JSON.Convertor). 
1329      * These convertors are looked up by class, interface and
1330      * super class by {@link JSON#getConvertor(Class)}.   Convertors should be used when the
1331      * classes to be converted cannot implement {@link Convertible} or {@link Generator}.
1332      */
1333     public interface Convertor
1334     {
1335         public void toJSON(Object obj, Output out);
1336 
1337         public Object fromJSON(Map object);
1338     }
1339 
1340     /* ------------------------------------------------------------ */
1341     /** JSON Generator.
1342      * A class that can add it's JSON representation directly to a StringBuffer.
1343      * This is useful for object instances that are frequently converted and wish to 
1344      * avoid multiple Conversions
1345      */
1346     public interface Generator
1347     {
1348         public void addJSON(StringBuffer buffer);
1349     }
1350 
1351     /* ------------------------------------------------------------ */
1352     /** A Literal JSON generator
1353      * A utility instance of {@link JSON.Generator} that holds a pre-generated string on JSON text.
1354      */
1355     public static class Literal implements Generator
1356     {
1357         private String _json;
1358 
1359         /* ------------------------------------------------------------ */
1360         /** Construct a literal JSON instance for use by {@link JSON#toString(Object)}.
1361          * If {@link Log#isDebugEnabled()} is true, the JSON will be parsed to check validity
1362          * @param json A literal JSON string. 
1363          */
1364         public Literal(String json)
1365         {
1366             if (Log.isDebugEnabled())
1367                 parse(json);
1368             _json=json;
1369         }
1370 
1371         public String toString()
1372         {
1373             return _json;
1374         }
1375 
1376         public void addJSON(StringBuffer buffer)
1377         {
1378             buffer.append(_json);
1379         }
1380     }
1381 }