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