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