View Javadoc

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