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.Collections; 021import java.util.Iterator; 022import java.util.List; 023 024// Note: this class was written without inspecting the non-free org.json sourcecode. 025 026/** 027 * A dense indexed sequence of values. Values may be any mix of 028 * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings, 029 * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}. 030 * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite() 031 * infinities}, or of any type not listed here. 032 * 033 * {@code JSONArray} has the same type coercion behavior and 034 * optional/mandatory accessors as {@link JSONObject}. See that class' 035 * documentation for details. 036 * 037 * <strong>Warning:</strong> this class represents null in two incompatible 038 * ways: the standard Java {@code null} reference, and the sentinel value {@link 039 * JSONObject#NULL}. In particular, {@code get} fails if the requested index 040 * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}. 041 * 042 * Instances of this class are not thread safe. 043 */ 044public final class JSONArray extends JSONCollection implements Iterable<Object> { 045 046 private final List<Object> values; 047 048 /** 049 * Creates a {@code JSONArray} with no values. 050 */ 051 public JSONArray() { 052 values = new ArrayList<Object>(); 053 } 054 055 /** 056 * Creates a new {@code JSONArray} with values from the next array in the 057 * tokener. 058 * 059 * @param readFrom a tokener whose nextValue() method will yield a 060 * {@code JSONArray}. 061 * @throws RuntimeException if the parse fails or doesn't yield a 062 * {@code JSONArray}. 063 */ 064 JSONArray(JSONTokener readFrom) { 065 /* 066 * Getting the parser to populate this could get tricky. Instead, just 067 * parse to temporary JSONArray and then steal the data from that. 068 */ 069 Object object = readFrom.nextValue(JSONArray.class); 070 if (object instanceof JSONArray) { 071 values = ((JSONArray) object).values; 072 } else { 073 throw JSON.typeMismatch(object, "JSONArray"); 074 } 075 } 076 077 /** 078 * Creates a new {@code JSONArray} with values from the JSON string. 079 * 080 * @param json a JSON-encoded string containing an array. 081 * @throws RuntimeException if the parse fails or doesn't yield a {@code 082 * JSONArray}. 083 */ 084 public JSONArray(String json) { 085 this(new JSONTokener(json)); 086 } 087 088 /** 089 * Creates a new {@code JSONArray} with values from the given primitive array. 090 * 091 * @param values The values to use. 092 * @throws RuntimeException if any of the values are non-finite double values (i.e. NaN or infinite) 093 */ 094 public JSONArray(Object... values) { 095 this(); 096 for (int i = 0; i < values.length; ++i) { 097 put(values[i]); 098 } 099 } 100 101 /** 102 * Create a new array, and adds all values fro the iterable to the array (using {@link #putAll(Iterable)}. 103 * 104 * This is implemented as a static method so as not to break the semantics of the existing {@link #JSONArray(Object...)} constructor. 105 * Adding a constructor of type Iterable would change the meaning of <code>new JSONArray(new JSONArray())</code>. 106 * 107 * @param iterable 108 * collection ot value to include, or null 109 * @since 5.4 110 */ 111 public static JSONArray from(Iterable<?> iterable) 112 { 113 return new JSONArray().putAll(iterable); 114 } 115 116 /** 117 * @return Returns the number of values in this array. 118 */ 119 public int length() { 120 return values.size(); 121 } 122 123 /** 124 * Appends {@code value} to the end of this array. 125 * 126 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 127 * Integer, Long, Double, or {@link JSONObject#NULL}}. May 128 * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 129 * infinities}. Unsupported values are not permitted and will cause the 130 * array to be in an inconsistent state. 131 * @return this array. 132 */ 133 public JSONArray put(Object value) { 134 JSONObject.testValidity(value); 135 values.add(value); 136 return this; 137 } 138 139 /** 140 * Same as {@link #put}, with added validity checks. 141 * 142 * @param value The value to append. 143 */ 144 void checkedPut(Object value) { 145 JSONObject.testValidity(value); 146 if (value instanceof Number) { 147 JSON.checkDouble(((Number) value).doubleValue()); 148 } 149 150 put(value); 151 } 152 153 /** 154 * Sets the value at {@code index} to {@code value}, null padding this array 155 * to the required length if necessary. If a value already exists at {@code 156 * index}, it will be replaced. 157 * 158 * @param index Where to put the value. 159 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 160 * Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May 161 * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 162 * infinities}. 163 * @return this array. 164 * @throws RuntimeException If the value cannot be represented as a finite double value. 165 */ 166 public JSONArray put(int index, Object value) { 167 if (index < 0) 168 { 169 throw new RuntimeException("JSONArray[" + index + "] not found."); 170 } 171 JSONObject.testValidity(value); 172 if (value instanceof Number) { 173 // deviate from the original by checking all Numbers, not just floats & doubles 174 JSON.checkDouble(((Number) value).doubleValue()); 175 } 176 while (values.size() <= index) { 177 values.add(null); 178 } 179 values.set(index, value); 180 return this; 181 } 182 183 /** 184 * Returns true if this array has no value at {@code index}, or if its value 185 * is the {@code null} reference or {@link JSONObject#NULL}. 186 * 187 * @param index Which value to check. 188 * @return true if the value is null. 189 */ 190 public boolean isNull(int index) { 191 Object value = values.get(index); 192 return value == null || value == JSONObject.NULL; 193 } 194 195 /** 196 * Returns the value at {@code index}. 197 * 198 * @param index Which value to get. 199 * @return the value at the specified location. 200 * @throws RuntimeException if this array has no value at {@code index}, or if 201 * that value is the {@code null} reference. This method returns 202 * normally if the value is {@code JSONObject#NULL}. 203 */ 204 public Object get(int index) { 205 try { 206 Object value = values.get(index); 207 if (value == null) { 208 throw new RuntimeException("Value at " + index + " is null."); 209 } 210 return value; 211 } catch (IndexOutOfBoundsException e) { 212 throw new RuntimeException("Index " + index + " out of range [0.." + values.size() + ")"); 213 } 214 } 215 216 /** 217 * Removes and returns the value at {@code index}, or null if the array has no value 218 * at {@code index}. 219 * 220 * @param index Which value to remove. 221 * @return The value previously at the specified location. 222 */ 223 public Object remove(int index) { 224 if (index < 0 || index >= values.size()) { 225 return null; 226 } 227 return values.remove(index); 228 } 229 230 /** 231 * Returns the value at {@code index} if it exists and is a boolean or can 232 * be coerced to a boolean. 233 * 234 * @param index Which value to get. 235 * @return the value at the specified location. 236 * @throws RuntimeException if the value at {@code index} doesn't exist or 237 * cannot be coerced to a boolean. 238 */ 239 public boolean getBoolean(int index) { 240 Object object = get(index); 241 Boolean result = JSON.toBoolean(object); 242 if (result == null) { 243 throw JSON.typeMismatch(true, index, object, "Boolean"); 244 } 245 return result; 246 } 247 248 /** 249 * Returns the value at {@code index} if it exists and is a double or can 250 * be coerced to a double. 251 * 252 * @param index Which value to get. 253 * @return the value at the specified location. 254 * @throws RuntimeException if the value at {@code index} doesn't exist or 255 * cannot be coerced to a double. 256 */ 257 public double getDouble(int index) { 258 Object object = get(index); 259 Double result = JSON.toDouble(object); 260 if (result == null) { 261 throw JSON.typeMismatch(true, index, object, "number"); 262 } 263 return result; 264 } 265 266 /** 267 * Returns the value at {@code index} if it exists and is an int or 268 * can be coerced to an int. 269 * 270 * @param index Which value to get. 271 * @return the value at the specified location. 272 * @throws RuntimeException if the value at {@code index} doesn't exist or 273 * cannot be coerced to a int. 274 */ 275 public int getInt(int index) { 276 Object object = get(index); 277 Integer result = JSON.toInteger(object); 278 if (result == null) { 279 throw JSON.typeMismatch(true, index, object, "int"); 280 } 281 return result; 282 } 283 284 /** 285 * Returns the value at {@code index} if it exists and is a long or 286 * can be coerced to a long. 287 * 288 * @param index Which value to get. 289 * @return the value at the specified location. 290 * @throws RuntimeException if the value at {@code index} doesn't exist or 291 * cannot be coerced to a long. 292 */ 293 public long getLong(int index) { 294 Object object = get(index); 295 Long result = JSON.toLong(object); 296 if (result == null) { 297 throw JSON.typeMismatch(true, index, object, "long"); 298 } 299 return result; 300 } 301 302 /** 303 * Returns the value at {@code index} if it exists, coercing it if 304 * necessary. 305 * 306 * @param index Which value to get. 307 * @return the value at the specified location. 308 * @throws RuntimeException if no such value exists. 309 */ 310 public String getString(int index) { 311 Object object = get(index); 312 String result = JSON.toString(object); 313 if (result == null) { 314 throw JSON.typeMismatch(true, index, object, "String"); 315 } 316 return result; 317 } 318 319 /** 320 * Returns the value at {@code index} if it exists and is a {@code 321 * JSONArray}. 322 * 323 * @param index Which value to get. 324 * @return the value at the specified location. 325 * @throws RuntimeException if the value doesn't exist or is not a {@code 326 * JSONArray}. 327 */ 328 public JSONArray getJSONArray(int index) { 329 Object object = get(index); 330 if (object instanceof JSONArray) { 331 return (JSONArray) object; 332 } else { 333 throw JSON.typeMismatch(true, index, object, "JSONArray"); 334 } 335 } 336 337 /** 338 * Returns the value at {@code index} if it exists and is a {@code 339 * JSONObject}. 340 * 341 * @param index Which value to get. 342 * @return the value at the specified location. 343 * @throws RuntimeException if the value doesn't exist or is not a {@code 344 * JSONObject}. 345 */ 346 public JSONObject getJSONObject(int index) { 347 Object object = get(index); 348 if (object instanceof JSONObject) { 349 return (JSONObject) object; 350 } else { 351 throw JSON.typeMismatch(true, index, object, "JSONObject"); 352 } 353 } 354 355 @Override 356 public boolean equals(Object o) { 357 return o instanceof JSONArray && ((JSONArray) o).values.equals(values); 358 } 359 360 @Override 361 public int hashCode() { 362 // diverge from the original, which doesn't implement hashCode 363 return values.hashCode(); 364 } 365 366 void print(JSONPrintSession session) 367 { 368 session.printSymbol('['); 369 370 session.indent(); 371 372 boolean comma = false; 373 374 for (Object value : values) 375 { 376 if (comma) 377 session.printSymbol(','); 378 379 session.newline(); 380 381 JSONObject.printValue(session, value); 382 383 comma = true; 384 } 385 386 session.outdent(); 387 388 if (comma) 389 session.newline(); 390 391 session.printSymbol(']'); 392 } 393 394 /** 395 * Puts all objects from the collection into this JSONArray, using {@link #put(Object)}. 396 * 397 * @param collection 398 * List, array, JSONArray, or other iterable object, or null 399 * @return this JSONArray 400 * @since 5.4 401 */ 402 public JSONArray putAll(Iterable<?> collection) 403 { 404 if (collection != null) 405 { 406 for (Object o : collection) 407 { 408 put(o); 409 } 410 } 411 412 return this; 413 } 414 415 /** 416 * Returns an unmodifiable list of the contents of the array. This is a wrapper around the list's internal 417 * storage and is live (changes to the JSONArray affect the returned List). 418 * 419 * @return unmodifiable list of array contents 420 * @since 5.4 421 */ 422 public List<Object> toList() 423 { 424 return Collections.unmodifiableList(values); 425 } 426 427 428 @Override 429 public Iterator<Object> iterator() 430 { 431 return values.iterator(); 432 } 433 434 435}