1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2014 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 JSON()98 public JSON() 99 { 100 } 101 102 /** 103 * @return the initial stringBuffer size to use when creating JSON strings 104 * (default 1024) 105 */ getStringBufferSize()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 */ setStringBufferSize(int stringBufferSize)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 */ registerConvertor(Class forClass, Convertor convertor)129 public static void registerConvertor(Class forClass, Convertor convertor) 130 { 131 DEFAULT.addConvertor(forClass,convertor); 132 } 133 getDefault()134 public static JSON getDefault() 135 { 136 return DEFAULT; 137 } 138 139 @Deprecated setDefault(JSON json)140 public static void setDefault(JSON json) 141 { 142 } 143 toString(Object object)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 toString(Map object)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 toString(Object[] array)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 */ parse(String s)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 */ parse(String s, boolean stripOuterComment)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 */ parse(Reader in)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 */ parse(Reader in, boolean stripOuterComment)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 parse(InputStream in)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 parse(InputStream in, boolean stripOuterComment)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 */ toJSON(Object object)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 */ fromJSON(String json)256 public Object fromJSON(String json) 257 { 258 Source source = new StringSource(json); 259 return parse(source); 260 } 261 262 @Deprecated append(StringBuffer buffer, Object object)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 */ append(Appendable buffer, Object object)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 appendNull(StringBuffer buffer)342 public void appendNull(StringBuffer buffer) 343 { 344 appendNull((Appendable)buffer); 345 } 346 appendNull(Appendable buffer)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 appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)360 public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object) 361 { 362 appendJSON((Appendable)buffer,convertor,object); 363 } 364 appendJSON(final Appendable buffer, final Convertor convertor, final Object object)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 appendJSON(final StringBuffer buffer, Convertible converter)381 public void appendJSON(final StringBuffer buffer, Convertible converter) 382 { 383 appendJSON((Appendable)buffer,converter); 384 } 385 appendJSON(final Appendable buffer, Convertible converter)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 appendJSON(StringBuffer buffer, Generator generator)394 public void appendJSON(StringBuffer buffer, Generator generator) 395 { 396 generator.addJSON(buffer); 397 } 398 appendJSON(Appendable buffer, Generator generator)399 public void appendJSON(Appendable buffer, Generator generator) 400 { 401 generator.addJSON(buffer); 402 } 403 404 @Deprecated appendMap(StringBuffer buffer, Map<?,?> map)405 public void appendMap(StringBuffer buffer, Map<?,?> map) 406 { 407 appendMap((Appendable)buffer,map); 408 } 409 appendMap(Appendable buffer, Map<?,?> map)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 appendArray(StringBuffer buffer, Collection collection)441 public void appendArray(StringBuffer buffer, Collection collection) 442 { 443 appendArray((Appendable)buffer,collection); 444 } 445 appendArray(Appendable buffer, Collection collection)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 appendArray(StringBuffer buffer, Object array)477 public void appendArray(StringBuffer buffer, Object array) 478 { 479 appendArray((Appendable)buffer,array); 480 } 481 appendArray(Appendable buffer, Object array)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 appendBoolean(StringBuffer buffer, Boolean b)511 public void appendBoolean(StringBuffer buffer, Boolean b) 512 { 513 appendBoolean((Appendable)buffer,b); 514 } 515 appendBoolean(Appendable buffer, Boolean b)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 appendNumber(StringBuffer buffer, Number number)534 public void appendNumber(StringBuffer buffer, Number number) 535 { 536 appendNumber((Appendable)buffer,number); 537 } 538 appendNumber(Appendable buffer, Number number)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 appendString(StringBuffer buffer, String string)557 public void appendString(StringBuffer buffer, String string) 558 { 559 appendString((Appendable)buffer,string); 560 } 561 appendString(Appendable buffer, String string)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 toString(char[] buffer, int offset, int length)575 protected String toString(char[] buffer, int offset, int length) 576 { 577 return new String(buffer,offset,length); 578 } 579 newMap()580 protected Map<String, Object> newMap() 581 { 582 return new HashMap<String, Object>(); 583 } 584 newArray(int size)585 protected Object[] newArray(int size) 586 { 587 return new Object[size]; 588 } 589 contextForArray()590 protected JSON contextForArray() 591 { 592 return this; 593 } 594 contextFor(String field)595 protected JSON contextFor(String field) 596 { 597 return this; 598 } 599 convertTo(Class type, Map map)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 */ addConvertor(Class forClass, Convertor convertor)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 */ getConvertor(Class forClass)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 */ addConvertorFor(String name, Convertor convertor)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 */ getConvertorFor(String name)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 parse(Source source, boolean stripOuterComment)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 parse(Source source)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 handleUnknown(Source source, char c)884 protected Object handleUnknown(Source source, char c) 885 { 886 throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source); 887 } 888 parseObject(Source source)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 parseArray(Source source)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 parseString(Source source)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 parseNumber(Source source)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 seekTo(char seek, Source source)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 seekTo(String seek, Source source)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 complete(String seek, Source source)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 ConvertableOutput(Appendable buffer)1295 private ConvertableOutput(Appendable buffer) 1296 { 1297 _buffer = buffer; 1298 } 1299 complete()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 add(Object obj)1315 public void add(Object obj) 1316 { 1317 if (c == 0) 1318 throw new IllegalStateException(); 1319 append(_buffer,obj); 1320 c = 0; 1321 } 1322 addClass(Class type)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 add(String name, Object value)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 add(String name, double value)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 add(String name, long value)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 add(String name, boolean value)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 { hasNext()1415 boolean hasNext(); 1416 next()1417 char next(); 1418 peek()1419 char peek(); 1420 scratchBuffer()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 StringSource(String s)1430 public StringSource(String s) 1431 { 1432 string = s; 1433 } 1434 hasNext()1435 public boolean hasNext() 1436 { 1437 if (index < string.length()) 1438 return true; 1439 scratch = null; 1440 return false; 1441 } 1442 next()1443 public char next() 1444 { 1445 return string.charAt(index++); 1446 } 1447 peek()1448 public char peek() 1449 { 1450 return string.charAt(index); 1451 } 1452 1453 @Override toString()1454 public String toString() 1455 { 1456 return string.substring(0,index) + "|||" + string.substring(index); 1457 } 1458 scratchBuffer()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 ReaderSource(Reader r)1473 public ReaderSource(Reader r) 1474 { 1475 _reader = r; 1476 } 1477 setReader(Reader reader)1478 public void setReader(Reader reader) 1479 { 1480 _reader = reader; 1481 _next = -1; 1482 } 1483 hasNext()1484 public boolean hasNext() 1485 { 1486 getNext(); 1487 if (_next < 0) 1488 { 1489 scratch = null; 1490 return false; 1491 } 1492 return true; 1493 } 1494 next()1495 public char next() 1496 { 1497 getNext(); 1498 char c = (char)_next; 1499 _next = -1; 1500 return c; 1501 } 1502 peek()1503 public char peek() 1504 { 1505 getNext(); 1506 return (char)_next; 1507 } 1508 getNext()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 scratchBuffer()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 { addClass(Class c)1538 public void addClass(Class c); 1539 add(Object obj)1540 public void add(Object obj); 1541 add(String name, Object value)1542 public void add(String name, Object value); 1543 add(String name, double value)1544 public void add(String name, double value); 1545 add(String name, long value)1546 public void add(String name, long value); 1547 add(String name, boolean value)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 { toJSON(Output out)1566 public void toJSON(Output out); 1567 fromJSON(Map object)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 { toJSON(Object obj, Output out)1584 public void toJSON(Object obj, Output out); 1585 fromJSON(Map object)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 { addJSON(Appendable buffer)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 */ Literal(String json)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 toString()1623 public String toString() 1624 { 1625 return _json; 1626 } 1627 addJSON(Appendable buffer)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 } 1641