View Javadoc

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