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 public Iterator<Object> iterator() 121 { 122 return list.iterator(); 123 } 124 125 /** 126 * Construct a JSONArray from a JSONTokener. 127 * 128 * @param tokenizer 129 * A JSONTokener 130 * @throws RuntimeException 131 * If there is a syntax error. 132 */ 133 JSONArray(JSONTokener tokenizer) 134 { 135 assert tokenizer != null; 136 137 parse(tokenizer); 138 } 139 140 private void parse(JSONTokener tokenizer) 141 { 142 if (tokenizer.nextClean() != '[') 143 { 144 throw tokenizer.syntaxError("A JSONArray text must start with '['"); 145 } 146 147 if (tokenizer.nextClean() == ']') 148 { 149 return; 150 } 151 152 tokenizer.back(); 153 154 while (true) 155 { 156 if (tokenizer.nextClean() == ',') 157 { 158 tokenizer.back(); 159 list.add(JSONObject.NULL); 160 } else 161 { 162 tokenizer.back(); 163 list.add(tokenizer.nextValue()); 164 } 165 166 switch (tokenizer.nextClean()) 167 { 168 case ';': 169 case ',': 170 if (tokenizer.nextClean() == ']') 171 { 172 return; 173 } 174 tokenizer.back(); 175 break; 176 177 case ']': 178 return; 179 180 default: 181 throw tokenizer.syntaxError("Expected a ',' or ']'"); 182 } 183 } 184 } 185 186 /** 187 * Get the object value associated with an index. 188 * 189 * @param index 190 * The index must be between 0 and length() - 1. 191 * @return An object value. 192 * @throws RuntimeException 193 * If there is no value for the index. 194 */ 195 public Object get(int index) 196 { 197 return list.get(index); 198 } 199 200 /** 201 * Remove the object associated with the index. 202 * 203 * @param index 204 * The index must be between 0 and length() - 1. 205 * @return An object removed. 206 * @throws RuntimeException 207 * If there is no value for the index. 208 */ 209 public Object remove(int index) 210 { 211 return list.remove(index); 212 } 213 214 /** 215 * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. 216 * 217 * @param index 218 * The index must be between 0 and length() - 1. 219 * @return The truth. 220 * @throws RuntimeException 221 * If there is no value for the index or if the value is not convertable to boolean. 222 */ 223 public boolean getBoolean(int index) 224 { 225 Object value = get(index); 226 227 if (value instanceof Boolean) 228 { 229 return (Boolean) value; 230 } 231 232 if (value instanceof String) 233 { 234 String asString = (String) value; 235 236 if (asString.equalsIgnoreCase("false")) 237 return false; 238 239 if (asString.equalsIgnoreCase("true")) 240 return true; 241 } 242 243 throw new RuntimeException("JSONArray[" + index + "] is not a Boolean."); 244 } 245 246 /** 247 * Get the double value associated with an index. 248 * 249 * @param index 250 * The index must be between 0 and length() - 1. 251 * @return The value. 252 * @throws IllegalArgumentException 253 * If the key is not found or if the value cannot be converted to a number. 254 */ 255 public double getDouble(int index) 256 { 257 Object value = get(index); 258 259 try 260 { 261 if (value instanceof Number) 262 return ((Number) value).doubleValue(); 263 264 return Double.valueOf((String) value); 265 } catch (Exception e) 266 { 267 throw new IllegalArgumentException("JSONArray[" + index + "] is not a number."); 268 } 269 } 270 271 /** 272 * Get the int value associated with an index. 273 * 274 * @param index 275 * The index must be between 0 and length() - 1. 276 * @return The value. 277 * @throws IllegalArgumentException 278 * If the key is not found or if the value cannot be converted to a number. if the 279 * value cannot be converted to a number. 280 */ 281 public int getInt(int index) 282 { 283 Object o = get(index); 284 return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(index); 285 } 286 287 /** 288 * Get the JSONArray associated with an index. 289 * 290 * @param index 291 * The index must be between 0 and length() - 1. 292 * @return A JSONArray value. 293 * @throws RuntimeException 294 * If there is no value for the index. or if the value is not a JSONArray 295 */ 296 public JSONArray getJSONArray(int index) 297 { 298 Object o = get(index); 299 if (o instanceof JSONArray) 300 { 301 return (JSONArray) o; 302 } 303 304 throw new RuntimeException("JSONArray[" + index + "] is not a JSONArray."); 305 } 306 307 /** 308 * Get the JSONObject associated with an index. 309 * 310 * @param index 311 * subscript 312 * @return A JSONObject value. 313 * @throws RuntimeException 314 * If there is no value for the index or if the value is not a JSONObject 315 */ 316 public JSONObject getJSONObject(int index) 317 { 318 Object o = get(index); 319 if (o instanceof JSONObject) 320 { 321 return (JSONObject) o; 322 } 323 324 throw new RuntimeException("JSONArray[" + index + "] is not a JSONObject."); 325 } 326 327 /** 328 * Get the long value associated with an index. 329 * 330 * @param index 331 * The index must be between 0 and length() - 1. 332 * @return The value. 333 * @throws IllegalArgumentException 334 * If the key is not found or if the value cannot be converted to a number. 335 */ 336 public long getLong(int index) 337 { 338 Object o = get(index); 339 return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(index); 340 } 341 342 /** 343 * Get the string associated with an index. 344 * 345 * @param index 346 * The index must be between 0 and length() - 1. 347 * @return A string value. 348 * @throws RuntimeException 349 * If there is no value for the index. 350 */ 351 public String getString(int index) 352 { 353 return get(index).toString(); 354 } 355 356 /** 357 * Determine if the value is null. 358 * 359 * @param index 360 * The index must be between 0 and length() - 1. 361 * @return true if the value at the index is null, or if there is no value. 362 */ 363 public boolean isNull(int index) 364 { 365 return get(index) == JSONObject.NULL; 366 } 367 368 /** 369 * Get the number of elements in the JSONArray, included nulls. 370 * 371 * @return The length (or size). 372 */ 373 public int length() 374 { 375 return list.size(); 376 } 377 378 /** 379 * Append an object value. This increases the array's length by one. 380 * 381 * @param value 382 * An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, JSONLiteral, 383 * Long, or String, or the JSONObject.NULL singleton. 384 * @return this array 385 */ 386 public JSONArray put(Object value) 387 { 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 void print(JSONPrintSession session) 450 { 451 session.printSymbol('['); 452 453 session.indent(); 454 455 boolean comma = false; 456 457 for (Object value : list) 458 { 459 if (comma) 460 session.printSymbol(','); 461 462 session.newline(); 463 464 JSONObject.printValue(session, value); 465 466 comma = true; 467 } 468 469 session.outdent(); 470 471 if (comma) 472 session.newline(); 473 474 session.printSymbol(']'); 475 } 476 477 /** 478 * Puts all objects from the collection into this JSONArray, using {@link #put(Object)}. 479 * 480 * @param collection 481 * List, array, JSONArray, or other iterable object, or null 482 * @return this JSONArray 483 * @since 5.4 484 */ 485 public JSONArray putAll(Iterable<?> collection) 486 { 487 if (collection != null) 488 { 489 for (Object o : collection) 490 { 491 put(o); 492 } 493 } 494 495 return this; 496 } 497 498 /** 499 * Returns an unmodifiable list of the contents of the array. This is a wrapper around the list's internal 500 * storage and is live (changes to the JSONArray affect the returned List). 501 * 502 * @return unmodifiable list of array contents 503 * @since 5.4 504 */ 505 public List<Object> toList() 506 { 507 return Collections.unmodifiableList(list); 508 } 509}