001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.json; 014 015/* 016 * Copyright (c) 2002 JSON.org 017 * Permission is hereby granted, free of charge, to any person obtaining a copy 018 * of this software and associated documentation files (the "Software"), to deal 019 * in the Software without restriction, including without limitation the rights 020 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 021 * copies of the Software, and to permit persons to whom the Software is 022 * furnished to do so, subject to the following conditions: 023 * The above copyright notice and this permission notice shall be included in all 024 * copies or substantial portions of the Software. 025 * The Software shall be used for Good, not Evil. 026 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 027 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 028 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 029 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 030 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 031 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 032 * SOFTWARE. 033 */ 034 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.Iterator; 038import java.util.List; 039 040/** 041 * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with 042 * commas separating the values. The internal form is an object having {@code get} and {@code opt} methods for 043 * accessing the values by index, and {@code put} methods for adding or replacing values. The values can be any of 044 * these types: {@code Boolean}, {@code JSONArray}, {@code JSONObject}, {@code Number}, 045 * {@code String}, or the {@code JSONObject.NULL object}. 046 * 047 * The constructor can convert a JSON text into a Java object. The {@code toString} method converts to JSON text. 048 * 049 * A {@code get} method returns a value if one can be found, and throws an exception if one cannot be found. An 050 * {@code opt} method returns a default value instead of throwing an exception, and so is useful for obtaining 051 * optional values. 052 * 053 * The generic {@code get()} and {@code opt()} methods return an object which you can cast or query for type. 054 * There are also typed {@code get} and {@code opt} methods that do type checking and type coersion for you. 055 * 056 * The texts produced by the {@code toString} methods strictly conform to JSON syntax rules. The constructors are 057 * more forgiving in the texts they will accept: 058 * <ul> 059 * <li>An extra {@code ,} <small>(comma)</small> may appear just before the closing bracket.</li> 060 * <li>The {@code null} value will be inserted when there is {@code ,} <small>(comma)</small> elision.</li> 061 * <li>Strings may be quoted with {@code '} <small>(single quote)</small>.</li> 062 * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not 063 * contain leading or trailing spaces, and if they do not contain any of these characters: 064 * {@code { } [ ] / \ : , = ; #} and if they do not look like numbers and if they are not the reserved words 065 * {@code true}, {@code false}, or {@code null}.</li> 066 * <li>Values can be separated by {@code ;} <small>(semicolon)</small> as well as by {@code ,} 067 * <small>(comma)</small>.</li> 068 * <li>Numbers may have the {@code 0-} <small>(octal)</small> or {@code 0x-} <small>(hex)</small> prefix.</li> 069 * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li> 070 * </ul> 071 * 072 * @author JSON.org 073 * @version 2 074 */ 075public final class JSONArray extends JSONCollection implements Iterable<Object> 076{ 077 078 /** 079 * The arrayList where the JSONArray's properties are kept. 080 */ 081 private final List<Object> list = new ArrayList<Object>(); 082 083 /** 084 * Construct an empty JSONArray. 085 */ 086 public JSONArray() 087 { 088 } 089 090 public JSONArray(String text) 091 { 092 JSONTokener tokener = new JSONTokener(text); 093 094 parse(tokener); 095 } 096 097 public JSONArray(Object... values) 098 { 099 for (Object value : values) 100 put(value); 101 } 102 103 /** 104 * Create a new array, and adds all values fro the iterable to the array (using {@link #putAll(Iterable)}. 105 * 106 * This is implemented as a static method so as not to break the semantics of the existing {@link #JSONArray(Object...)} constructor. 107 * Adding a constructor of type Iterable would change the meaning of <code>new JSONArray(new JSONArray())</code>. 108 * 109 * @param iterable 110 * collection ot value to include, or null 111 * @since 5.4 112 */ 113 public static JSONArray from(Iterable<?> iterable) 114 { 115 return new JSONArray().putAll(iterable); 116 } 117 118 @Override 119 public Iterator<Object> iterator() 120 { 121 return list.iterator(); 122 } 123 124 /** 125 * Construct a JSONArray from a JSONTokener. 126 * 127 * @param tokenizer 128 * A JSONTokener 129 * @throws RuntimeException 130 * If there is a syntax error. 131 */ 132 JSONArray(JSONTokener tokenizer) 133 { 134 assert tokenizer != null; 135 136 parse(tokenizer); 137 } 138 139 private void parse(JSONTokener tokenizer) 140 { 141 if (tokenizer.nextClean() != '[') 142 { 143 throw tokenizer.syntaxError("A JSONArray text must start with '['"); 144 } 145 146 if (tokenizer.nextClean() == ']') 147 { 148 return; 149 } 150 151 tokenizer.back(); 152 153 while (true) 154 { 155 if (tokenizer.nextClean() == ',') 156 { 157 tokenizer.back(); 158 list.add(JSONObject.NULL); 159 } else 160 { 161 tokenizer.back(); 162 list.add(tokenizer.nextValue()); 163 } 164 165 switch (tokenizer.nextClean()) 166 { 167 case ';': 168 case ',': 169 if (tokenizer.nextClean() == ']') 170 { 171 return; 172 } 173 tokenizer.back(); 174 break; 175 176 case ']': 177 return; 178 179 default: 180 throw tokenizer.syntaxError("Expected a ',' or ']'"); 181 } 182 } 183 } 184 185 /** 186 * Get the object value associated with an index. 187 * 188 * @param index 189 * The index must be between 0 and length() - 1. 190 * @return An object value. 191 * @throws RuntimeException 192 * If there is no value for the index. 193 */ 194 public Object get(int index) 195 { 196 return list.get(index); 197 } 198 199 /** 200 * Remove the object associated with the index. 201 * 202 * @param index 203 * The index must be between 0 and length() - 1. 204 * @return An object removed. 205 * @throws RuntimeException 206 * If there is no value for the index. 207 */ 208 public Object remove(int index) 209 { 210 return list.remove(index); 211 } 212 213 /** 214 * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. 215 * 216 * @param index 217 * The index must be between 0 and length() - 1. 218 * @return The truth. 219 * @throws RuntimeException 220 * If there is no value for the index or if the value is not convertable to boolean. 221 */ 222 public boolean getBoolean(int index) 223 { 224 Object value = get(index); 225 226 if (value instanceof Boolean) 227 { 228 return (Boolean) value; 229 } 230 231 if (value instanceof String) 232 { 233 String asString = (String) value; 234 235 if (asString.equalsIgnoreCase("false")) 236 return false; 237 238 if (asString.equalsIgnoreCase("true")) 239 return true; 240 } 241 242 throw new RuntimeException("JSONArray[" + index + "] is not a Boolean."); 243 } 244 245 /** 246 * Get the double value associated with an index. 247 * 248 * @param index 249 * The index must be between 0 and length() - 1. 250 * @return The value. 251 * @throws IllegalArgumentException 252 * If the key is not found or if the value cannot be converted to a number. 253 */ 254 public double getDouble(int index) 255 { 256 Object value = get(index); 257 258 try 259 { 260 if (value instanceof Number) 261 return ((Number) value).doubleValue(); 262 263 return Double.valueOf((String) value); 264 } catch (Exception e) 265 { 266 throw new IllegalArgumentException("JSONArray[" + index + "] is not a number."); 267 } 268 } 269 270 /** 271 * Get the int value associated with an index. 272 * 273 * @param index 274 * The index must be between 0 and length() - 1. 275 * @return The value. 276 * @throws IllegalArgumentException 277 * If the key is not found or if the value cannot be converted to a number. if the 278 * value cannot be converted to a number. 279 */ 280 public int getInt(int index) 281 { 282 Object o = get(index); 283 return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(index); 284 } 285 286 /** 287 * Get the JSONArray associated with an index. 288 * 289 * @param index 290 * The index must be between 0 and length() - 1. 291 * @return A JSONArray value. 292 * @throws RuntimeException 293 * If there is no value for the index. or if the value is not a JSONArray 294 */ 295 public JSONArray getJSONArray(int index) 296 { 297 Object o = get(index); 298 if (o instanceof JSONArray) 299 { 300 return (JSONArray) o; 301 } 302 303 throw new RuntimeException("JSONArray[" + index + "] is not a JSONArray."); 304 } 305 306 /** 307 * Get the JSONObject associated with an index. 308 * 309 * @param index 310 * subscript 311 * @return A JSONObject value. 312 * @throws RuntimeException 313 * If there is no value for the index or if the value is not a JSONObject 314 */ 315 public JSONObject getJSONObject(int index) 316 { 317 Object o = get(index); 318 if (o instanceof JSONObject) 319 { 320 return (JSONObject) o; 321 } 322 323 throw new RuntimeException("JSONArray[" + index + "] is not a JSONObject."); 324 } 325 326 /** 327 * Get the long value associated with an index. 328 * 329 * @param index 330 * The index must be between 0 and length() - 1. 331 * @return The value. 332 * @throws IllegalArgumentException 333 * If the key is not found or if the value cannot be converted to a number. 334 */ 335 public long getLong(int index) 336 { 337 Object o = get(index); 338 return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(index); 339 } 340 341 /** 342 * Get the string associated with an index. 343 * 344 * @param index 345 * The index must be between 0 and length() - 1. 346 * @return A string value. 347 * @throws RuntimeException 348 * If there is no value for the index. 349 */ 350 public String getString(int index) 351 { 352 return get(index).toString(); 353 } 354 355 /** 356 * Determine if the value is null. 357 * 358 * @param index 359 * The index must be between 0 and length() - 1. 360 * @return true if the value at the index is null, or if there is no value. 361 */ 362 public boolean isNull(int index) 363 { 364 return get(index) == JSONObject.NULL; 365 } 366 367 /** 368 * Get the number of elements in the JSONArray, included nulls. 369 * 370 * @return The length (or size). 371 */ 372 public int length() 373 { 374 return list.size(); 375 } 376 377 /** 378 * Append an object value. This increases the array's length by one. 379 * 380 * @param value 381 * An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, JSONLiteral, 382 * Long, or String, or the JSONObject.NULL singleton. 383 * @return this array 384 */ 385 public JSONArray put(Object value) 386 { 387 // now testValidity checks for null values. 388 // assert value != null; 389 390 JSONObject.testValidity(value); 391 392 list.add(value); 393 394 return this; 395 } 396 397 /** 398 * Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then 399 * null elements will be added as necessary to pad it out. 400 * 401 * @param index 402 * The subscript. 403 * @param value 404 * The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray, 405 * JSONObject, JSONString, Long, or String, or the JSONObject.NULL singeton. 406 * @return this array 407 * @throws RuntimeException 408 * If the index is negative or if the the value is an invalid number. 409 */ 410 public JSONArray put(int index, Object value) 411 { 412 assert value != null; 413 414 if (index < 0) 415 { 416 throw new RuntimeException("JSONArray[" + index + "] not found."); 417 } 418 419 JSONObject.testValidity(value); 420 421 if (index < length()) 422 { 423 list.set(index, value); 424 } else 425 { 426 while (index != length()) 427 list.add(JSONObject.NULL); 428 429 list.add(value); 430 } 431 432 return this; 433 } 434 435 @Override 436 public boolean equals(Object obj) 437 { 438 if (obj == null) 439 return false; 440 441 if (!(obj instanceof JSONArray)) 442 return false; 443 444 JSONArray other = (JSONArray) obj; 445 446 return list.equals(other.list); 447 } 448 449 @Override 450 void print(JSONPrintSession session) 451 { 452 session.printSymbol('['); 453 454 session.indent(); 455 456 boolean comma = false; 457 458 for (Object value : list) 459 { 460 if (comma) 461 session.printSymbol(','); 462 463 session.newline(); 464 465 JSONObject.printValue(session, value); 466 467 comma = true; 468 } 469 470 session.outdent(); 471 472 if (comma) 473 session.newline(); 474 475 session.printSymbol(']'); 476 } 477 478 /** 479 * Puts all objects from the collection into this JSONArray, using {@link #put(Object)}. 480 * 481 * @param collection 482 * List, array, JSONArray, or other iterable object, or null 483 * @return this JSONArray 484 * @since 5.4 485 */ 486 public JSONArray putAll(Iterable<?> collection) 487 { 488 if (collection != null) 489 { 490 for (Object o : collection) 491 { 492 put(o); 493 } 494 } 495 496 return this; 497 } 498 499 /** 500 * Returns an unmodifiable list of the contents of the array. This is a wrapper around the list's internal 501 * storage and is live (changes to the JSONArray affect the returned List). 502 * 503 * @return unmodifiable list of array contents 504 * @since 5.4 505 */ 506 public List<Object> toList() 507 { 508 return Collections.unmodifiableList(list); 509 } 510}