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