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