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