View Javadoc

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