001 // Copyright 2007, 2008, 2010, 2011 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 015 package 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 037 import java.util.ArrayList; 038 import java.util.Iterator; 039 import java.util.List; 040 041 /** 042 * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with 043 * commas separating the values. The internal form is an object having {@code get} and {@code opt} methods for 044 * accessing the values by index, and {@code put} methods for adding or replacing values. The values can be any of 045 * these types: {@code Boolean}, {@code JSONArray}, {@code JSONObject}, {@code Number}, 046 * {@code String}, or the {@code JSONObject.NULL object}. 047 * <p/> 048 * The constructor can convert a JSON text into a Java object. The {@code toString} method converts to JSON text. 049 * <p/> 050 * A {@code get} method returns a value if one can be found, and throws an exception if one cannot be found. An 051 * {@code opt} method returns a default value instead of throwing an exception, and so is useful for obtaining 052 * optional values. 053 * <p/> 054 * The generic {@code get()} and {@code opt()} methods return an object which you can cast or query for type. 055 * There are also typed {@code get} and {@code opt} methods that do type checking and type coersion for you. 056 * <p/> 057 * The texts produced by the {@code toString} methods strictly conform to JSON syntax rules. The constructors are 058 * more forgiving in the texts they will accept: 059 * <ul> 060 * <li>An extra {@code ,} <small>(comma)</small> may appear just before the closing bracket.</li> 061 * <li>The {@code null} value will be inserted when there is {@code ,} <small>(comma)</small> elision.</li> 062 * <li>Strings may be quoted with {@code '} <small>(single quote)</small>.</li> 063 * <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 064 * contain leading or trailing spaces, and if they do not contain any of these characters: 065 * {@code { } [ ] / \ : , = ; #} and if they do not look like numbers and if they are not the reserved words 066 * {@code true}, {@code false}, or {@code null}.</li> 067 * <li>Values can be separated by {@code ;} <small>(semicolon)</small> as well as by {@code ,} 068 * <small>(comma)</small>.</li> 069 * <li>Numbers may have the {@code 0-} <small>(octal)</small> or {@code 0x-} <small>(hex)</small> prefix.</li> 070 * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li> 071 * </ul> 072 * 073 * @author JSON.org 074 * @version 2 075 */ 076 public final class JSONArray extends JSONCollection implements Iterable<Object> 077 { 078 079 /** 080 * The arrayList where the JSONArray's properties are kept. 081 */ 082 private final List<Object> list = new ArrayList<Object>(); 083 084 /** 085 * Construct an empty JSONArray. 086 */ 087 public JSONArray() 088 { 089 } 090 091 public JSONArray(String text) 092 { 093 JSONTokener tokener = new JSONTokener(text); 094 095 parse(tokener); 096 } 097 098 public JSONArray(Object... values) 099 { 100 for (Object value : values) 101 put(value); 102 } 103 104 public Iterator<Object> iterator() 105 { 106 return list.iterator(); 107 } 108 109 /** 110 * Construct a JSONArray from a JSONTokener. 111 * 112 * @param tokenizer 113 * A JSONTokener 114 * @throws RuntimeException 115 * If there is a syntax error. 116 */ 117 JSONArray(JSONTokener tokenizer) 118 { 119 assert tokenizer != null; 120 121 parse(tokenizer); 122 } 123 124 private void parse(JSONTokener tokenizer) 125 { 126 if (tokenizer.nextClean() != '[') { throw tokenizer.syntaxError("A JSONArray text must start with '['"); } 127 128 if (tokenizer.nextClean() == ']') { return; } 129 130 tokenizer.back(); 131 132 while (true) 133 { 134 if (tokenizer.nextClean() == ',') 135 { 136 tokenizer.back(); 137 list.add(JSONObject.NULL); 138 } 139 else 140 { 141 tokenizer.back(); 142 list.add(tokenizer.nextValue()); 143 } 144 145 switch (tokenizer.nextClean()) 146 { 147 case ';': 148 case ',': 149 if (tokenizer.nextClean() == ']') { return; } 150 tokenizer.back(); 151 break; 152 153 case ']': 154 return; 155 156 default: 157 throw tokenizer.syntaxError("Expected a ',' or ']'"); 158 } 159 } 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 * @throws RuntimeException 169 * If there is no value for the index. 170 */ 171 public Object get(int index) 172 { 173 return list.get(index); 174 } 175 176 /** 177 * Remove the object associated with the index. 178 * 179 * @param index 180 * The index must be between 0 and length() - 1. 181 * @return An object removed. 182 * @throws RuntimeException 183 * If there is no value for the index. 184 */ 185 public Object remove(int index) 186 { 187 return list.remove(index); 188 } 189 190 /** 191 * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. 192 * 193 * @param index 194 * The index must be between 0 and length() - 1. 195 * @return The truth. 196 * @throws RuntimeException 197 * If there is no value for the index or if the value is not convertable to boolean. 198 */ 199 public boolean getBoolean(int index) 200 { 201 Object value = get(index); 202 203 if (value instanceof Boolean) { return (Boolean) value; } 204 205 if (value instanceof String) 206 { 207 String asString = (String) value; 208 209 if (asString.equalsIgnoreCase("false")) 210 return false; 211 212 if (asString.equalsIgnoreCase("true")) 213 return true; 214 } 215 216 throw new RuntimeException("JSONArray[" + index + "] is not a Boolean."); 217 } 218 219 /** 220 * Get the double value associated with an index. 221 * 222 * @param index 223 * The index must be between 0 and length() - 1. 224 * @return The value. 225 * @throws IllegalArgumentException 226 * If the key is not found or if the value cannot be converted to a number. 227 */ 228 public double getDouble(int index) 229 { 230 Object value = get(index); 231 232 try 233 { 234 if (value instanceof Number) 235 return ((Number) value).doubleValue(); 236 237 return Double.valueOf((String) value); 238 } 239 catch (Exception e) 240 { 241 throw new IllegalArgumentException("JSONArray[" + index + "] is not a number."); 242 } 243 } 244 245 /** 246 * Get the int 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. if the 253 * value cannot be converted to a number. 254 */ 255 public int getInt(int index) 256 { 257 Object o = get(index); 258 return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(index); 259 } 260 261 /** 262 * Get the JSONArray associated with an index. 263 * 264 * @param index 265 * The index must be between 0 and length() - 1. 266 * @return A JSONArray value. 267 * @throws RuntimeException 268 * If there is no value for the index. or if the value is not a JSONArray 269 */ 270 public JSONArray getJSONArray(int index) 271 { 272 Object o = get(index); 273 if (o instanceof JSONArray) { return (JSONArray) o; } 274 275 throw new RuntimeException("JSONArray[" + index + "] is not a JSONArray."); 276 } 277 278 /** 279 * Get the JSONObject associated with an index. 280 * 281 * @param index 282 * subscript 283 * @return A JSONObject value. 284 * @throws RuntimeException 285 * If there is no value for the index or if the value is not a JSONObject 286 */ 287 public JSONObject getJSONObject(int index) 288 { 289 Object o = get(index); 290 if (o instanceof JSONObject) { return (JSONObject) o; } 291 292 throw new RuntimeException("JSONArray[" + index + "] is not a JSONObject."); 293 } 294 295 /** 296 * Get the long value associated with an index. 297 * 298 * @param index 299 * The index must be between 0 and length() - 1. 300 * @return The value. 301 * @throws IllegalArgumentException 302 * If the key is not found or if the value cannot be converted to a number. 303 */ 304 public long getLong(int index) 305 { 306 Object o = get(index); 307 return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(index); 308 } 309 310 /** 311 * Get the string associated with an index. 312 * 313 * @param index 314 * The index must be between 0 and length() - 1. 315 * @return A string value. 316 * @throws RuntimeException 317 * If there is no value for the index. 318 */ 319 public String getString(int index) 320 { 321 return get(index).toString(); 322 } 323 324 /** 325 * Determine if the value is null. 326 * 327 * @param index 328 * The index must be between 0 and length() - 1. 329 * @return true if the value at the index is null, or if there is no value. 330 */ 331 public boolean isNull(int index) 332 { 333 return get(index) == JSONObject.NULL; 334 } 335 336 /** 337 * Get the number of elements in the JSONArray, included nulls. 338 * 339 * @return The length (or size). 340 */ 341 public int length() 342 { 343 return list.size(); 344 } 345 346 /** 347 * Append an object value. This increases the array's length by one. 348 * 349 * @param value 350 * An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, JSONLiteral, 351 * Long, or String, or the JSONObject.NULL singleton. 352 * @return this array 353 */ 354 public JSONArray put(Object value) 355 { 356 assert value != null; 357 358 JSONObject.testValidity(value); 359 360 list.add(value); 361 362 return this; 363 } 364 365 /** 366 * Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then 367 * null elements will be added as necessary to pad it out. 368 * 369 * @param index 370 * The subscript. 371 * @param value 372 * The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray, 373 * JSONObject, JSONString, Long, or String, or the JSONObject.NULL singeton. 374 * @return this array 375 * @throws RuntimeException 376 * If the index is negative or if the the value is an invalid number. 377 */ 378 public JSONArray put(int index, Object value) 379 { 380 assert value != null; 381 382 if (index < 0) { throw new RuntimeException("JSONArray[" + index + "] not found."); } 383 384 JSONObject.testValidity(value); 385 386 if (index < length()) 387 { 388 list.set(index, value); 389 } 390 else 391 { 392 while (index != length()) 393 list.add(JSONObject.NULL); 394 395 list.add(value); 396 } 397 398 return this; 399 } 400 401 /** Used for testing. */ 402 Object[] toArray() 403 { 404 return list.toArray(); 405 } 406 407 @Override 408 public boolean equals(Object obj) 409 { 410 if (obj == null) 411 return false; 412 413 if (!(obj instanceof JSONArray)) 414 return false; 415 416 JSONArray other = (JSONArray) obj; 417 418 return list.equals(other.list); 419 } 420 421 void print(JSONPrintSession session) 422 { 423 session.printSymbol('['); 424 425 session.indent(); 426 427 boolean comma = false; 428 429 for (Object value : list) 430 { 431 if (comma) 432 session.printSymbol(','); 433 434 session.newline(); 435 436 JSONObject.printValue(session, value); 437 438 comma = true; 439 } 440 441 session.outdent(); 442 443 if (comma) 444 session.newline(); 445 446 session.printSymbol(']'); 447 } 448 }