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