View Javadoc

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