View Javadoc

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