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