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 }