001/* 002 * Copyright (C) 2010 The Android Open Source Project 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.apache.tapestry5.json; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.List; 024 025import org.apache.tapestry5.json.exceptions.JSONArrayIndexOutOfBoundsException; 026import org.apache.tapestry5.json.exceptions.JSONSyntaxException; 027import org.apache.tapestry5.json.exceptions.JSONTypeMismatchException; 028import org.apache.tapestry5.json.exceptions.JSONValueNotFoundException; 029 030// Note: this class was written without inspecting the non-free org.json sourcecode. 031 032/** 033 * A dense indexed sequence of values. Values may be any mix of 034 * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings, 035 * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}. 036 * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite() 037 * infinities}, or of any type not listed here. 038 * 039 * {@code JSONArray} has the same type coercion behavior and 040 * optional/mandatory accessors as {@link JSONObject}. See that class' 041 * documentation for details. 042 * 043 * <strong>Warning:</strong> this class represents null in two incompatible 044 * ways: the standard Java {@code null} reference, and the sentinel value {@link 045 * JSONObject#NULL}. In particular, {@code get} fails if the requested index 046 * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}. 047 * 048 * Instances of this class are not thread safe. 049 */ 050public final class JSONArray extends JSONCollection implements Collection<Object> { 051 052 private static final long serialVersionUID = 1L; 053 054 private final List<Object> values; 055 056 /** 057 * Creates a {@code JSONArray} with no values. 058 */ 059 public JSONArray() { 060 values = new ArrayList<Object>(); 061 } 062 063 /** 064 * Creates a new {@code JSONArray} with values from the next array in the 065 * tokener. 066 * 067 * @param readFrom a tokener whose nextValue() method will yield a 068 * {@code JSONArray}. 069 * @throws JSONSyntaxException if the parse fails 070 * @throws JSONTypeMismatchException if it doesn't yield a 071 * {@code JSONArray}. 072 */ 073 JSONArray(JSONTokener readFrom) { 074 /* 075 * Getting the parser to populate this could get tricky. Instead, just 076 * parse to temporary JSONArray and then steal the data from that. 077 */ 078 Object object = readFrom.nextValue(JSONArray.class); 079 if (object instanceof JSONArray) { 080 values = ((JSONArray) object).values; 081 } else { 082 throw JSONExceptionBuilder.tokenerTypeMismatch(object, JSONType.ARRAY); 083 } 084 } 085 086 /** 087 * Creates a new {@code JSONArray} with values from the JSON string. 088 * 089 * @param json a JSON-encoded string containing an array. 090 * @throws JSONSyntaxException if the parse fails 091 * @throws JSONTypeMismatchException if it doesn't yield a 092 * {@code JSONArray}. 093 */ 094 public JSONArray(String json) { 095 this(new JSONTokener(json)); 096 } 097 098 /** 099 * Creates a new {@code JSONArray} with values from the given primitive array. 100 * 101 * @param values The values to use. 102 * @throws IllegalArgumentException if any of the values are non-finite double values (i.e. NaN or infinite) 103 */ 104 public JSONArray(Object... values) { 105 this(); 106 for (int i = 0; i < values.length; ++i) { 107 checkedPut(values[i]); 108 } 109 } 110 111 /** 112 * Create a new array, and adds all values from the iterable to the array (using {@link #putAll(Iterable)}. 113 * 114 * This is implemented as a static method so as not to break the semantics of the existing {@link #JSONArray(Object...)} constructor. 115 * Adding a constructor of type Iterable would change the meaning of <code>new JSONArray(new JSONArray())</code>. 116 * 117 * @param iterable 118 * collection ot value to include, or null 119 * @since 5.4 120 */ 121 public static JSONArray from(Iterable<?> iterable) 122 { 123 return new JSONArray().putAll(iterable); 124 } 125 126 /** 127 * @return Returns the number of values in this array. 128 * @deprecated Use {@link #size()} instead. 129 */ 130 public int length() { 131 return size(); 132 } 133 134 /** 135 * Returns the number of values in this array. 136 * If this list contains more than {@code Integer.MAX_VALUE} elements, returns 137 * {@code Integer.MAX_VALUE}. 138 * 139 * @return the number of values in this array 140 * @since 5.7 141 */ 142 @Override 143 public int size() 144 { 145 return values.size(); 146 } 147 148 /** 149 * Returns {@code true} if this array contains no values. 150 * 151 * @return {@code true} if this array contains no values 152 * @since 5.7 153 */ 154 @Override 155 public boolean isEmpty() 156 { 157 return values.isEmpty(); 158 } 159 160 /** 161 * Appends {@code value} to the end of this array. 162 * 163 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 164 * Integer, Long, Double, or {@link JSONObject#NULL}}. May 165 * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 166 * infinities}. Unsupported values are not permitted and will cause the 167 * array to be in an inconsistent state. 168 * @return this array. 169 * @deprecated The use of {@link #add(Object)} is encouraged. 170 */ 171 public JSONArray put(Object value) { 172 add(value); 173 return this; 174 } 175 176 /** 177 * Appends {@code value} to the end of this array. 178 * 179 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 180 * Integer, Long, Double, or {@link JSONObject#NULL}}. May 181 * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 182 * infinities}. Unsupported values are not permitted and will cause the 183 * array to be in an inconsistent state. 184 * @return {@code true} (as specified by {@link Collection#add}) 185 * @since 5.7 186 */ 187 @Override 188 public boolean add(Object value) 189 { 190 JSON.testValidity(value); 191 return values.add(value); 192 } 193 194 /** 195 * Same as {@link #put}, with added validity checks. 196 * 197 * @param value The value to append. 198 */ 199 void checkedPut(Object value) { 200 JSON.testValidity(value); 201 if (value instanceof Number) { 202 JSON.checkDouble(((Number) value).doubleValue()); 203 } 204 205 put(value); 206 } 207 208 /** 209 * Sets the value at {@code index} to {@code value}, null padding this array 210 * to the required length if necessary. If a value already exists at {@code 211 * index}, it will be replaced. 212 * 213 * @param index Where to put the value. 214 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 215 * Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May 216 * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 217 * infinities}. 218 * @return this array. 219 * @throws IllegalArgumentException If the value cannot be represented as a finite double value. 220 * @throws ArrayIndexOutOfBoundsException if the index is lower than 0 221 */ 222 public JSONArray put(int index, Object value) { 223 if (index < 0) 224 { 225 throw new JSONArrayIndexOutOfBoundsException(index); 226 } 227 JSON.testValidity(value); 228 if (value instanceof Number) { 229 // deviate from the original by checking all Numbers, not just floats & doubles 230 JSON.checkDouble(((Number) value).doubleValue()); 231 } 232 while (values.size() <= index) { 233 values.add(null); 234 } 235 values.set(index, value); 236 return this; 237 } 238 239 /** 240 * Returns true if this array has no value at {@code index}, or if its value 241 * is the {@code null} reference or {@link JSONObject#NULL}. 242 * 243 * @param index Which value to check. 244 * @return true if the value is null. 245 */ 246 public boolean isNull(int index) { 247 Object value = values.get(index); 248 return value == null || value == JSONObject.NULL; 249 } 250 251 /** 252 * Returns the value at {@code index}. 253 * 254 * @param index Which value to get. 255 * @return the value at the specified location. 256 * @throws JSONArrayIndexOutOfBoundsException if the given index is out of bounds. 257 * @throws JSONValueNotFoundException if this array has no value at {@code index}, or if 258 * that value is the {@code null} reference. This method returns 259 * normally if the value is {@code JSONObject#NULL}. 260 */ 261 public Object get(int index) { 262 try { 263 Object value = values.get(index); 264 if (value == null) { 265 throw JSONExceptionBuilder.valueNotFound(true, index, JSONType.ANY); 266 } 267 return value; 268 } 269 catch (IndexOutOfBoundsException e) { 270 throw new JSONArrayIndexOutOfBoundsException(index); 271 } 272 } 273 274 /** 275 * Removes and returns the value at {@code index}, or null if the array has no value 276 * at {@code index}. 277 * 278 * @param index Which value to remove. 279 * @return The value previously at the specified location. 280 */ 281 public Object remove(int index) { 282 if (index < 0 || index >= values.size()) { 283 return null; 284 } 285 return values.remove(index); 286 } 287 288 /** 289 * Removes the first occurrence of the specified value from this JSONArray, 290 * if it is present. 291 * 292 * @param value value to be removed from this JSONArray, if present 293 * @return {@code true} if the element was removed 294 * @since 5.7 295 */ 296 @Override 297 public boolean remove(Object value) 298 { 299 return values.remove(value); 300 } 301 302 /** 303 * Removes from this JSONArray all of its values that are contained in the 304 * specified collection. 305 * 306 * @param collection collection containing value to be removed from this JSONArray 307 * @return {@code true} if this JSONArray changed as a result of the call 308 * @throws NullPointerException if the specified collection is null. 309 * @see Collection#contains(Object) 310 * @since 5.7 311 */ 312 @Override 313 public boolean removeAll(Collection<?> collection) 314 { 315 return values.removeAll(collection); 316 } 317 318 /** 319 * Removes all of the values from this JSONArray. 320 * 321 * @since 5.7 322 */ 323 @Override 324 public void clear() 325 { 326 values.clear(); 327 } 328 329 /** 330 * Retains only the values in this JSONArray that are contained in the 331 * specified collection. 332 * 333 * @param collection collection containing elements to be retained in this list 334 * @return {@code true} if this list changed as a result of the call 335 * @since 5.7 336 */ 337 @Override 338 public boolean retainAll(Collection<?> collection) 339 { 340 return values.retainAll(collection); 341 } 342 343 /** 344 * Returns the value at {@code index} if it exists and is a boolean or can 345 * be coerced to a boolean. 346 * 347 * @param index Which value to get. 348 * @return the value at the specified location. 349 * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or 350 * cannot be coerced to a boolean. 351 */ 352 public boolean getBoolean(int index) { 353 Object object = get(index); 354 Boolean result = JSON.toBoolean(object); 355 if (result == null) { 356 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.BOOLEAN); 357 } 358 return result; 359 } 360 361 /** 362 * Returns the value at {@code index} if it exists and is a double or can 363 * be coerced to a double. 364 * 365 * @param index Which value to get. 366 * @return the value at the specified location. 367 * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or 368 * cannot be coerced to a double. 369 */ 370 public double getDouble(int index) { 371 Object object = get(index); 372 Double result = JSON.toDouble(object); 373 if (result == null) { 374 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.NUMBER); 375 } 376 return result; 377 } 378 379 /** 380 * Returns the value at {@code index} if it exists and is an int or 381 * can be coerced to an int. 382 * 383 * @param index Which value to get. 384 * @return the value at the specified location. 385 * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or 386 * cannot be coerced to a int. 387 */ 388 public int getInt(int index) { 389 Object object = get(index); 390 Integer result = JSON.toInteger(object); 391 if (result == null) { 392 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.NUMBER); 393 } 394 return result; 395 } 396 397 /** 398 * Returns the value at {@code index} if it exists and is a long or 399 * can be coerced to a long. 400 * 401 * @param index Which value to get. 402 * @return the value at the specified location. 403 * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or 404 * cannot be coerced to a long. 405 */ 406 public long getLong(int index) { 407 Object object = get(index); 408 Long result = JSON.toLong(object); 409 if (result == null) { 410 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.NUMBER); 411 } 412 return result; 413 } 414 415 /** 416 * Returns the value at {@code index} if it exists, coercing it if 417 * necessary. 418 * 419 * @param index Which value to get. 420 * @return the value at the specified location. 421 * @throws JSONTypeMismatchException if no such value exists. 422 */ 423 public String getString(int index) { 424 Object object = get(index); 425 String result = JSON.toString(object); 426 if (result == null) { 427 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.STRING); 428 } 429 return result; 430 } 431 432 /** 433 * Returns the value at {@code index} if it exists and is a {@code 434 * JSONArray}. 435 * 436 * @param index Which value to get. 437 * @return the value at the specified location. 438 * @throws JSONTypeMismatchException if the value doesn't exist or is not a {@code 439 * JSONArray}. 440 */ 441 public JSONArray getJSONArray(int index) { 442 Object object = get(index); 443 if (object instanceof JSONArray) { 444 return (JSONArray) object; 445 } else { 446 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.ARRAY); 447 } 448 } 449 450 /** 451 * Returns the value at {@code index} if it exists and is a {@code 452 * JSONObject}. 453 * 454 * @param index Which value to get. 455 * @return the value at the specified location. 456 * @throws JSONTypeMismatchException if the value doesn't exist or is not a {@code 457 * JSONObject}. 458 */ 459 public JSONObject getJSONObject(int index) { 460 Object object = get(index); 461 if (object instanceof JSONObject) { 462 return (JSONObject) object; 463 } else { 464 throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.OBJECT); 465 } 466 } 467 468 @Override 469 public boolean equals(Object o) { 470 return o instanceof JSONArray && ((JSONArray) o).values.equals(values); 471 } 472 473 @Override 474 public int hashCode() { 475 // diverge from the original, which doesn't implement hashCode 476 return values.hashCode(); 477 } 478 479 void print(JSONPrintSession session) 480 { 481 session.printSymbol('['); 482 483 session.indent(); 484 485 boolean comma = false; 486 487 for (Object value : values) 488 { 489 if (comma) 490 session.printSymbol(','); 491 492 session.newline(); 493 494 JSONObject.printValue(session, value); 495 496 comma = true; 497 } 498 499 session.outdent(); 500 501 if (comma) 502 session.newline(); 503 504 session.printSymbol(']'); 505 } 506 507 /** 508 * Puts all objects from the collection into this JSONArray, using {@link #put(Object)}. 509 * 510 * @param collection 511 * List, array, JSONArray, or other iterable object, or null 512 * @return this JSONArray 513 * @since 5.4 514 */ 515 public JSONArray putAll(Iterable<?> collection) 516 { 517 if (collection != null) 518 { 519 for (Object o : collection) 520 { 521 put(o); 522 } 523 } 524 525 return this; 526 } 527 528 /** 529 * Adds all objects from the collection into this JSONArray, using {@link #add(Object)}. 530 * 531 * @param collection Any collection, or null 532 * @return boolean true, if JSONArray was changed. 533 * @since 5.7 534 */ 535 @Override 536 public boolean addAll(Collection<? extends Object> collection) 537 { 538 if (collection == null) 539 { 540 return false; 541 } 542 543 boolean changed = false; 544 for (Object value : collection) 545 { 546 changed = add(value) || changed; 547 } 548 549 return changed; 550 } 551 552 /** 553 * Returns an unmodifiable list of the contents of the array. This is a wrapper around the list's internal 554 * storage and is live (changes to the JSONArray affect the returned List). 555 * 556 * @return unmodifiable list of array contents 557 * @since 5.4 558 */ 559 public List<Object> toList() 560 { 561 return Collections.unmodifiableList(values); 562 } 563 564 /** 565 * Returns an array containing all of the values in this JSONArray in proper 566 * sequence. 567 * 568 * @return an array containing all of the values in this JSONArray in proper 569 * sequence 570 * @since 5.7 571 */ 572 @Override 573 public Object[] toArray() 574 { 575 return values.toArray(); 576 } 577 578 /** 579 * Returns an array containing all of the values in this JSONArray in 580 * proper sequence; the runtime type of the returned array is that of 581 * the specified array. 582 * 583 * @param array 584 * the array into which the values of this JSONArray are to 585 * be stored, if it is big enough; otherwise, a new array of the 586 * same runtime type is allocated for this purpose. 587 * @return an array containing the values of this JSONArray 588 * @throws ArrayStoreException 589 * if the runtime type of the specified array 590 * is not a supertype of the runtime type of every element in 591 * this list 592 * @throws NullPointerException 593 * if the specified array is null 594 * @since 5.7 595 */ 596 @Override 597 public <T> T[] toArray(T[] array) 598 { 599 return values.toArray(array); 600 } 601 602 /** 603 * Returns an iterator over the values in this array in proper sequence. 604 * 605 * @return an iterator over the values in this array in proper sequence 606 */ 607 @Override 608 public Iterator<Object> iterator() 609 { 610 return values.iterator(); 611 } 612 613 /** 614 * Returns {@code true} if this JSONArray contains the specified value. 615 * 616 * @param value value whose presence in this JSONArray is to be tested 617 * @return {@code true} if this JSONArray contains the specified 618 * value 619 * @since 5.7 620 */ 621 @Override 622 public boolean contains(Object value) 623 { 624 return values.contains(value); 625 } 626 627 /** 628 * Returns {@code true} if this JSONArray contains all of the values 629 * in the specified collection. 630 * 631 * @param c collection to be checked for containment in this collection 632 * @return {@code true} if this collection contains all of the elements 633 * in the specified collection 634 * @throws NullPointerException 635 * if the specified collection is null. 636 * @see #contains(Object) 637 * @since 5.7 638 */ 639 @Override 640 public boolean containsAll(Collection<?> c) 641 { 642 return values.containsAll(c); 643 } 644}