001 package org.apache.tapestry.json; 002 003 /* 004 Copyright (c) 2002 JSON.org 005 006 Permission is hereby granted, free of charge, to any person obtaining a copy 007 of this software and associated documentation files (the "Software"), to deal 008 in the Software without restriction, including without limitation the rights 009 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 copies of the Software, and to permit persons to whom the Software is 011 furnished to do so, subject to the following conditions: 012 013 The above copyright notice and this permission notice shall be included in all 014 copies or substantial portions of the Software. 015 016 The Software shall be used for Good, not Evil. 017 018 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 019 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 020 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 021 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 022 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 023 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 024 SOFTWARE. 025 */ 026 027 import java.text.ParseException; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.NoSuchElementException; 031 032 /** 033 * A JSONArray is an ordered sequence of values. Its external form is a string 034 * wrapped in square brackets with commas between the values. The internal form 035 * is an object having get() and opt() methods for accessing the values by 036 * index, and put() methods for adding or replacing values. The values can be 037 * any of these types: Boolean, JSONArray, JSONObject, Number, String, or the 038 * JSONObject.NULL object. 039 * <p> 040 * The constructor can convert a JSON external form string into an internal form 041 * Java object. The toString() method creates an external form string. 042 * <p> 043 * A get() method returns a value if one can be found, and throws an exception 044 * if one cannot be found. An opt() method returns a default value instead of 045 * throwing an exception, and so is useful for obtaining optional values. 046 * <p> 047 * The generic get() and opt() methods return an object which you can cast or 048 * query for type. There are also typed get() and opt() methods that do typing 049 * checking and type coersion for you. 050 * <p> 051 * The texts produced by the toString() methods are very strict. The 052 * constructors are more forgiving in the texts they will accept. 053 * <ul> 054 * <li>An extra <code>,</code> <small>(comma)</small> may appear just 055 * before the closing bracket.</li> 056 * <li>The null value will be inserted when there is <code>,</code> <small>(comma)</small> 057 * elision.</li> 058 * <li>Strings may be quoted with <code>'</code> <small>(single quote)</small>.</li> 059 * <li>Strings do not need to be quoted at all if they do not begin with a 060 * quote or single quote, and if they do not contain leading or trailing spaces, 061 * and if they do not contain any of these characters: 062 * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and 063 * if they are not the reserved words <code>true</code>, <code>false</code>, 064 * or <code>null</code>.</li> 065 * <li>Values can be followed by <code>;</code> as well as by <code>,</code></li> 066 * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or 067 * <code>0x-</code> <small>(hex)</small> prefix.</li> 068 * <li>Line comments can begin with <code>#</code></li> 069 * </ul> 070 * 071 * @author JSON.org 072 * @version 1 073 */ 074 public class JSONArray 075 { 076 077 /** 078 * The getArrayList where the JSONArray's properties are kept. 079 */ 080 private ArrayList myArrayList; 081 082 /** 083 * Construct an empty JSONArray. 084 */ 085 public JSONArray() 086 { 087 this.myArrayList = new ArrayList(); 088 } 089 090 /** 091 * Construct a JSONArray from a JSONTokener. 092 * 093 * @param x 094 * A JSONTokener 095 * @exception ParseException 096 * A JSONArray must start with '[' 097 * @exception ParseException 098 * Expected a ',' or ']' 099 */ 100 public JSONArray(JSONTokener x) 101 throws ParseException 102 { 103 this(); 104 if (x.nextClean() != '[') { throw x 105 .syntaxError("A JSONArray must start with '['"); } 106 if (x.nextClean() == ']') { return; } 107 x.back(); 108 while(true) 109 { 110 if (x.nextClean() == ',') 111 { 112 x.back(); 113 this.myArrayList.add(null); 114 } 115 else 116 { 117 x.back(); 118 this.myArrayList.add(x.nextValue()); 119 } 120 switch(x.nextClean()) 121 { 122 case ';': 123 case ',': 124 if (x.nextClean() == ']') { return; } 125 x.back(); 126 break; 127 case ']': 128 return; 129 default: 130 throw x.syntaxError("Expected a ',' or ']'"); 131 } 132 } 133 } 134 135 /** 136 * Construct a JSONArray from a source string. 137 * 138 * @param string 139 * A string that begins with <code>[</code> <small>(left 140 * bracket)</small> and ends with <code>]</code> <small>(right 141 * bracket)</small>. 142 * @exception ParseException 143 * The string must conform to JSON syntax. 144 */ 145 public JSONArray(String string) 146 throws ParseException 147 { 148 this(new JSONTokener(string)); 149 } 150 151 /** 152 * Construct a JSONArray from a Collection. 153 * 154 * @param collection 155 * A Collection. 156 */ 157 public JSONArray(Collection collection) 158 { 159 this.myArrayList = new ArrayList(collection); 160 } 161 162 /** 163 * Get the object value associated with an index. 164 * 165 * @param index 166 * The index must be between 0 and length() - 1. 167 * @return An object value. 168 * @exception NoSuchElementException 169 */ 170 public Object get(int index) 171 { 172 Object o = opt(index); 173 if (o == null) { throw new NoSuchElementException("JSONArray[" + index 174 + "] not found."); } 175 return o; 176 } 177 178 /** 179 * Get the ArrayList which is holding the elements of the JSONArray. 180 * 181 * @return The ArrayList. 182 */ 183 ArrayList getArrayList() 184 { 185 return this.myArrayList; 186 } 187 188 /** 189 * Get the boolean value associated with an index. The string values "true" 190 * and "false" are converted to boolean. 191 * 192 * @param index 193 * The index must be between 0 and length() - 1. 194 * @return The truth. 195 * @exception NoSuchElementException 196 * if the index is not found 197 * @exception ClassCastException 198 */ 199 public boolean getBoolean(int index) 200 { 201 Object o = get(index); 202 if (o.equals(Boolean.FALSE) 203 || (o instanceof String && ((String) o) 204 .equalsIgnoreCase("false"))) 205 { 206 return false; 207 } 208 else if (o.equals(Boolean.TRUE) 209 || (o instanceof String && ((String) o) 210 .equalsIgnoreCase("true"))) { return true; } 211 throw new ClassCastException("JSONArray[" + index + "] not a Boolean."); 212 } 213 214 /** 215 * Get the double value associated with an index. 216 * 217 * @param index 218 * The index must be between 0 and length() - 1. 219 * @return The value. 220 * @exception NoSuchElementException 221 * if the key is not found 222 * @exception NumberFormatException 223 * if the value cannot be converted to a number. 224 */ 225 public double getDouble(int index) 226 { 227 Object o = get(index); 228 if (o instanceof Number) { return ((Number) o).doubleValue(); } 229 if (o instanceof String) { return new Double((String) o).doubleValue(); } 230 throw new NumberFormatException("JSONObject[" + index 231 + "] is not a number."); 232 } 233 234 /** 235 * Get the int value associated with an index. 236 * 237 * @param index 238 * The index must be between 0 and length() - 1. 239 * @return The value. 240 * @exception NoSuchElementException 241 * if the key is not found 242 * @exception NumberFormatException 243 * if the value cannot be converted to a number. 244 */ 245 public int getInt(int index) 246 { 247 Object o = get(index); 248 return o instanceof Number ? ((Number) o).intValue() 249 : (int) getDouble(index); 250 } 251 252 /** 253 * Get the JSONArray associated with an index. 254 * 255 * @param index 256 * The index must be between 0 and length() - 1. 257 * @return A JSONArray value. 258 * @exception NoSuchElementException 259 * if the index is not found or if the value is not a 260 * JSONArray 261 */ 262 public JSONArray getJSONArray(int index) 263 { 264 Object o = get(index); 265 if (o instanceof JSONArray) { return (JSONArray) o; } 266 throw new NoSuchElementException("JSONArray[" + index 267 + "] is not a JSONArray."); 268 } 269 270 /** 271 * Get the JSONObject associated with an index. 272 * 273 * @param index 274 * subscript 275 * @return A JSONObject value. 276 * @exception NoSuchElementException 277 * if the index is not found or if the value is not a 278 * JSONObject 279 */ 280 public JSONObject getJSONObject(int index) 281 { 282 Object o = get(index); 283 if (o instanceof JSONObject) { return (JSONObject) o; } 284 throw new NoSuchElementException("JSONArray[" + index 285 + "] is not a JSONObject."); 286 } 287 288 /** 289 * Get the string associated with an index. 290 * 291 * @param index 292 * The index must be between 0 and length() - 1. 293 * @return A string value. 294 * @exception NoSuchElementException 295 */ 296 public String getString(int index) 297 { 298 return get(index).toString(); 299 } 300 301 /** 302 * Determine if the value is null. 303 * 304 * @param index 305 * The index must be between 0 and length() - 1. 306 * @return true if the value at the index is null, or if there is no value. 307 */ 308 public boolean isNull(int index) 309 { 310 Object o = opt(index); 311 return o == null || o.equals(null); 312 } 313 314 /** 315 * Make a string from the contents of this JSONArray. The separator string 316 * is inserted between each element. Warning: This method assumes that the 317 * data structure is acyclical. 318 * 319 * @param separator 320 * A string that will be inserted between the elements. 321 * @return a string. 322 */ 323 public String join(String separator) 324 { 325 int len = length(); 326 StringBuffer sb = new StringBuffer(); 327 for(int i = 0; i < len; i += 1) 328 { 329 if (i > 0) 330 { 331 sb.append(separator); 332 } 333 sb.append(JSONObject.valueToString(this.myArrayList.get(i))); 334 } 335 return sb.toString(); 336 } 337 338 /** 339 * Get the length of the JSONArray. 340 * 341 * @return The length (or size). 342 */ 343 public int length() 344 { 345 return this.myArrayList.size(); 346 } 347 348 /** 349 * Get the optional object value associated with an index. 350 * 351 * @param index 352 * The index must be between 0 and length() - 1. 353 * @return An object value, or null if there is no object at that index. 354 */ 355 public Object opt(int index) 356 { 357 return index < 0 || index >= length() ? null : this.myArrayList 358 .get(index); 359 } 360 361 /** 362 * Get the optional boolean value associated with an index. It returns false 363 * if there is no value at that index, or if the value is not Boolean.TRUE 364 * or the String "true". 365 * 366 * @param index 367 * The index must be between 0 and length() - 1. 368 * @return The truth. 369 */ 370 public boolean optBoolean(int index) 371 { 372 return optBoolean(index, false); 373 } 374 375 /** 376 * Get the optional boolean value associated with an index. It returns the 377 * defaultValue if there is no value at that index or if it is not a Boolean 378 * or the String "true" or "false" (case insensitive). 379 * 380 * @param index 381 * The index must be between 0 and length() - 1. 382 * @param defaultValue 383 * A boolean default. 384 * @return The truth. 385 */ 386 public boolean optBoolean(int index, boolean defaultValue) 387 { 388 Object o = opt(index); 389 if (o != null) 390 { 391 if (o.equals(Boolean.FALSE) 392 || (o instanceof String && ((String) o) 393 .equalsIgnoreCase("false"))) 394 { 395 return false; 396 } 397 else if (o.equals(Boolean.TRUE) 398 || (o instanceof String && ((String) o) 399 .equalsIgnoreCase("true"))) { return true; } 400 } 401 return defaultValue; 402 } 403 404 /** 405 * Get the optional double value associated with an index. NaN is returned 406 * if the index is not found, or if the value is not a number and cannot be 407 * converted to a number. 408 * 409 * @param index 410 * The index must be between 0 and length() - 1. 411 * @return The value. 412 */ 413 public double optDouble(int index) 414 { 415 return optDouble(index, Double.NaN); 416 } 417 418 /** 419 * Get the optional double value associated with an index. The defaultValue 420 * is returned if the index is not found, or if the value is not a number 421 * and cannot be converted to a number. 422 * 423 * @param index 424 * subscript 425 * @param defaultValue 426 * The default value. 427 * @return The value. 428 */ 429 public double optDouble(int index, double defaultValue) 430 { 431 Object o = opt(index); 432 if (o != null) 433 { 434 if (o instanceof Number) { return ((Number) o).doubleValue(); } 435 try 436 { 437 return new Double((String) o).doubleValue(); 438 } 439 catch (Exception e) 440 { 441 return defaultValue; 442 } 443 } 444 return defaultValue; 445 } 446 447 /** 448 * Get the optional int value associated with an index. Zero is returned if 449 * the index is not found, or if the value is not a number and cannot be 450 * converted to a number. 451 * 452 * @param index 453 * The index must be between 0 and length() - 1. 454 * @return The value. 455 */ 456 public int optInt(int index) 457 { 458 return optInt(index, 0); 459 } 460 461 /** 462 * Get the optional int value associated with an index. The defaultValue is 463 * returned if the index is not found, or if the value is not a number and 464 * cannot be converted to a number. 465 * 466 * @param index 467 * The index must be between 0 and length() - 1. 468 * @param defaultValue 469 * The default value. 470 * @return The value. 471 */ 472 public int optInt(int index, int defaultValue) 473 { 474 Object o = opt(index); 475 if (o != null) 476 { 477 if (o instanceof Number) { return ((Number) o).intValue(); } 478 try 479 { 480 return Integer.parseInt((String) o); 481 } 482 catch (Exception e) 483 { 484 return defaultValue; 485 } 486 } 487 return defaultValue; 488 } 489 490 /** 491 * Get the optional JSONArray associated with an index. 492 * 493 * @param index 494 * subscript 495 * @return A JSONArray value, or null if the index has no value, or if the 496 * value is not a JSONArray. 497 */ 498 public JSONArray optJSONArray(int index) 499 { 500 Object o = opt(index); 501 return o instanceof JSONArray ? (JSONArray) o : null; 502 } 503 504 /** 505 * Get the optional JSONObject associated with an index. Null is returned if 506 * the key is not found, or null if the index has no value, or if the value 507 * is not a JSONObject. 508 * 509 * @param index 510 * The index must be between 0 and length() - 1. 511 * @return A JSONObject value. 512 */ 513 public JSONObject optJSONObject(int index) 514 { 515 Object o = opt(index); 516 return o instanceof JSONObject ? (JSONObject) o : null; 517 } 518 519 /** 520 * Get the optional string value associated with an index. It returns an 521 * empty string if there is no value at that index. If the value is not a 522 * string and is not null, then it is coverted to a string. 523 * 524 * @param index 525 * The index must be between 0 and length() - 1. 526 * @return A String value. 527 */ 528 public String optString(int index) 529 { 530 return optString(index, ""); 531 } 532 533 /** 534 * Get the optional string associated with an index. The defaultValue is 535 * returned if the key is not found. 536 * 537 * @param index 538 * The index must be between 0 and length() - 1. 539 * @param defaultValue 540 * The default value. 541 * @return A String value. 542 */ 543 public String optString(int index, String defaultValue) 544 { 545 Object o = opt(index); 546 return o != null ? o.toString() : defaultValue; 547 } 548 549 /** 550 * Append a boolean value. 551 * 552 * @param value 553 * A boolean value. 554 * @return this. 555 */ 556 public JSONArray put(boolean value) 557 { 558 put(Boolean.valueOf(value)); 559 return this; 560 } 561 562 /** 563 * Append a double value. 564 * 565 * @param value 566 * A double value. 567 * @return this. 568 */ 569 public JSONArray put(double value) 570 { 571 put(new Double(value)); 572 return this; 573 } 574 575 /** 576 * Append an int value. 577 * 578 * @param value 579 * An int value. 580 * @return this. 581 */ 582 public JSONArray put(int value) 583 { 584 put(new Integer(value)); 585 return this; 586 } 587 588 /** 589 * Append an object value. 590 * 591 * @param value 592 * An object value. The value should be a Boolean, Double, 593 * Integer, JSONArray, JSObject, or String, or the 594 * JSONObject.NULL object. 595 * @return this. 596 */ 597 public JSONArray put(Object value) 598 { 599 this.myArrayList.add(value); 600 return this; 601 } 602 603 /** 604 * Put or replace a boolean value in the JSONArray. 605 * 606 * @param index 607 * subscript The subscript. If the index is greater than the 608 * length of the JSONArray, then null elements will be added as 609 * necessary to pad it out. 610 * @param value 611 * A boolean value. 612 * @return this. 613 * @exception NoSuchElementException 614 * The index must not be negative. 615 */ 616 public JSONArray put(int index, boolean value) 617 { 618 put(index, Boolean.valueOf(value)); 619 return this; 620 } 621 622 /** 623 * Put or replace a double value. 624 * 625 * @param index 626 * subscript The subscript. If the index is greater than the 627 * length of the JSONArray, then null elements will be added as 628 * necessary to pad it out. 629 * @param value 630 * A double value. 631 * @return this. 632 * @exception NoSuchElementException 633 * The index must not be negative. 634 */ 635 public JSONArray put(int index, double value) 636 { 637 put(index, new Double(value)); 638 return this; 639 } 640 641 /** 642 * Put or replace an int value. 643 * 644 * @param index 645 * subscript The subscript. If the index is greater than the 646 * length of the JSONArray, then null elements will be added as 647 * necessary to pad it out. 648 * @param value 649 * An int value. 650 * @return this. 651 * @exception NoSuchElementException 652 * The index must not be negative. 653 */ 654 public JSONArray put(int index, int value) 655 { 656 put(index, new Integer(value)); 657 return this; 658 } 659 660 /** 661 * Put or replace an object value in the JSONArray. 662 * 663 * @param index 664 * The subscript. If the index is greater than the length of the 665 * JSONArray, then null elements will be added as necessary to 666 * pad it out. 667 * @param value 668 * An object value. 669 * @return this. 670 * @exception NoSuchElementException 671 * The index must not be negative. 672 * @exception NullPointerException 673 * The index must not be null. 674 */ 675 public JSONArray put(int index, Object value) 676 { 677 if (index < 0) 678 { 679 throw new NoSuchElementException("JSONArray[" + index 680 + "] not found."); 681 } 682 else if (value == null) 683 { 684 throw new NullPointerException(); 685 } 686 else if (index < length()) 687 { 688 this.myArrayList.set(index, value); 689 } 690 else 691 { 692 while(index != length()) 693 { 694 put(null); 695 } 696 put(value); 697 } 698 return this; 699 } 700 701 /** 702 * Produce a JSONObject by combining a JSONArray of names with the values of 703 * this JSONArray. 704 * 705 * @param names 706 * A JSONArray containing a list of key strings. These will be 707 * paired with the values. 708 * @return A JSONObject, or null if there are no names or if this JSONArray 709 * has no values. 710 */ 711 public JSONObject toJSONObject(JSONArray names) 712 { 713 if (names == null || names.length() == 0 || length() == 0) { return null; } 714 JSONObject jo = new JSONObject(); 715 for(int i = 0; i < names.length(); i += 1) 716 { 717 jo.put(names.getString(i), this.opt(i)); 718 } 719 return jo; 720 } 721 722 /** 723 * Make an JSON external form string of this JSONArray. For compactness, no 724 * unnecessary whitespace is added. Warning: This method assumes that the 725 * data structure is acyclical. 726 * 727 * @return a printable, displayable, transmittable representation of the 728 * array. 729 */ 730 public String toString() 731 { 732 return '[' + join(",") + ']'; 733 } 734 735 /** 736 * Make a prettyprinted JSON string of this JSONArray. Warning: This method 737 * assumes that the data structure is non-cyclical. 738 * 739 * @param indentFactor 740 * The number of spaces to add to each level of indentation. 741 * @return a printable, displayable, transmittable representation of the 742 * object, beginning with <code>[</code> <small>(left 743 * bracket)</small> and ending with <code>]</code> <small>(right 744 * bracket)</small>. 745 */ 746 public String toString(int indentFactor) 747 { 748 return toString(indentFactor, 0); 749 } 750 751 /** 752 * Make a prettyprinted string of this JSONArray. Warning: This method 753 * assumes that the data structure is non-cyclical. 754 * 755 * @param indentFactor 756 * The number of spaces to add to each level of indentation. 757 * @param indent 758 * The indention of the top level. 759 * @return a printable, displayable, transmittable representation of the 760 * array. 761 */ 762 String toString(int indentFactor, int indent) 763 { 764 int len = length(); 765 if (len == 0) { return "[]"; } 766 int i; 767 StringBuffer sb = new StringBuffer("["); 768 if (len == 1) 769 { 770 sb.append(JSONObject.valueToString(this.myArrayList.get(0), 771 indentFactor, indent)); 772 } 773 else 774 { 775 int newindent = indent + indentFactor; 776 sb.append('\n'); 777 for(i = 0; i < len; i += 1) 778 { 779 if (i > 0) 780 { 781 sb.append(",\n"); 782 } 783 for(int j = 0; j < newindent; j += 1) 784 { 785 sb.append(' '); 786 } 787 sb.append(JSONObject.valueToString(this.myArrayList.get(i), 788 indentFactor, newindent)); 789 } 790 sb.append('\n'); 791 for(i = 0; i < indent; i += 1) 792 { 793 sb.append(' '); 794 } 795 } 796 sb.append(']'); 797 return sb.toString(); 798 } 799 }