View Javadoc

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