1 // Copyright 2007, 2008 The Apache Software Foundation 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package org.apache.tapestry5.json; 16 17 /* 18 Copyright (c) 2002 JSON.org 19 20 Permission is hereby granted, free of charge, to any person obtaining a copy 21 of this software and associated documentation files (the "Software"), to deal 22 in the Software without restriction, including without limitation the rights 23 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 copies of the Software, and to permit persons to whom the Software is 25 furnished to do so, subject to the following conditions: 26 27 The above copyright notice and this permission notice shall be included in all 28 copies or substantial portions of the Software. 29 30 The Software shall be used for Good, not Evil. 31 32 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 38 SOFTWARE. 39 */ 40 41 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 42 43 import java.util.List; 44 45 /** 46 * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with 47 * commas separating the values. The internal form is an object having <code>get</code> and <code>opt</code> methods for 48 * accessing the values by index, and <code>put</code> methods for adding or replacing values. The values can be any of 49 * these types: <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>, 50 * <code>String</code>, or the <code>JSONObject.NULL object</code>. 51 * <p/> 52 * The constructor can convert a JSON text into a Java object. The <code>toString</code> method converts to JSON text. 53 * <p/> 54 * A <code>get</code> method returns a value if one can be found, and throws an exception if one cannot be found. An 55 * <code>opt</code> method returns a default value instead of throwing an exception, and so is useful for obtaining 56 * optional values. 57 * <p/> 58 * The generic <code>get()</code> and <code>opt()</code> methods return an object which you can cast or query for type. 59 * There are also typed <code>get</code> and <code>opt</code> methods that do type checking and type coersion for you. 60 * <p/> 61 * The texts produced by the <code>toString</code> methods strictly conform to JSON syntax rules. The constructors are 62 * more forgiving in the texts they will accept: <ul> <li>An extra <code>,</code> <small>(comma)</small> may appear 63 * just before the closing bracket.</li> <li>The <code>null</code> value will be inserted when there is 64 * <code>,</code> <small>(comma)</small> elision.</li> <li>Strings may be quoted with 65 * <code>'</code> <small>(single quote)</small>.</li> <li>Strings do not need to be quoted at all if they do not 66 * begin with a quote or single quote, and if they do not contain leading or trailing spaces, and if they do not contain 67 * any of these characters: <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and if they are not 68 * the reserved words <code>true</code>, <code>false</code>, or <code>null</code>.</li> <li>Values can be separated by 69 * <code>;</code> <small>(semicolon)</small> as well as by <code>,</code> <small>(comma)</small>.</li> <li>Numbers may 70 * have the <code>0-</code> <small>(octal)</small> or <code>0x-</code> <small>(hex)</small> prefix.</li> <li>Comments 71 * written in the slashshlash, slashstar, and hash conventions will be ignored.</li> </ul> 72 * 73 * @author JSON.org 74 * @version 2 75 */ 76 public final class JSONArray 77 { 78 79 /** 80 * The arrayList where the JSONArray's properties are kept. 81 */ 82 private final List<Object> list = CollectionFactory.newList(); 83 84 /** 85 * Construct an empty JSONArray. 86 */ 87 public JSONArray() 88 { 89 } 90 91 public JSONArray(String text) 92 { 93 JSONTokener tokener = new JSONTokener(text); 94 95 parse(tokener); 96 } 97 98 public JSONArray(Object... values) 99 { 100 for (Object value : values) put(value); 101 } 102 103 /** 104 * Construct a JSONArray from a JSONTokener. 105 * 106 * @param tokenizer A JSONTokener 107 * @throws RuntimeException If there is a syntax error. 108 */ 109 JSONArray(JSONTokener tokenizer) 110 { 111 assert tokenizer != null; 112 113 parse(tokenizer); 114 } 115 116 private void parse(JSONTokener tokenizer) 117 { 118 if (tokenizer.nextClean() != '[') 119 { 120 throw tokenizer 121 .syntaxError("A JSONArray text must start with '['"); 122 } 123 124 if (tokenizer.nextClean() == ']') 125 { 126 return; 127 } 128 129 tokenizer.back(); 130 131 while (true) 132 { 133 if (tokenizer.nextClean() == ',') 134 { 135 tokenizer.back(); 136 list.add(null); 137 } 138 else 139 { 140 tokenizer.back(); 141 list.add(tokenizer.nextValue()); 142 } 143 144 switch (tokenizer.nextClean()) 145 { 146 case ';': 147 case ',': 148 if (tokenizer.nextClean() == ']') 149 { 150 return; 151 } 152 tokenizer.back(); 153 break; 154 155 case ']': 156 return; 157 158 default: 159 throw tokenizer.syntaxError("Expected a ',' or ']'"); 160 } 161 } 162 } 163 164 /** 165 * Get the object value associated with an index. 166 * 167 * @param index The index must be between 0 and length() - 1. 168 * @return An object value. 169 * @throws RuntimeException 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 * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. 178 * 179 * @param index The index must be between 0 and length() - 1. 180 * @return The truth. 181 * @throws RuntimeException If there is no value for the index or if the value is not convertable to boolean. 182 */ 183 public boolean getBoolean(int index) 184 { 185 Object value = get(index); 186 187 if (value instanceof Boolean) 188 { 189 return (Boolean) value; 190 } 191 192 if (value instanceof String) 193 { 194 String asString = (String) value; 195 196 if (asString.equalsIgnoreCase("false")) return false; 197 198 if (asString.equalsIgnoreCase("true")) return true; 199 } 200 201 throw new RuntimeException("JSONArray[" + index + "] is not a Boolean."); 202 } 203 204 /** 205 * Get the double value associated with an index. 206 * 207 * @param index The index must be between 0 and length() - 1. 208 * @return The value. 209 * @throws IllegalArgumentException If the key is not found or if the value cannot be converted to a number. 210 */ 211 public double getDouble(int index) 212 { 213 Object value = get(index); 214 215 try 216 { 217 if (value instanceof Number) return ((Number) value).doubleValue(); 218 219 return Double.valueOf((String) value); 220 } 221 catch (Exception e) 222 { 223 throw new IllegalArgumentException("JSONArray[" + index + "] is not a number."); 224 } 225 } 226 227 /** 228 * Get the int value associated with an index. 229 * 230 * @param index The index must be between 0 and length() - 1. 231 * @return The value. 232 * @throws IllegalArgumentException If the key is not found or if the value cannot be converted to a number. if the 233 * value cannot be converted to a number. 234 */ 235 public int getInt(int index) 236 { 237 Object o = get(index); 238 return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(index); 239 } 240 241 /** 242 * Get the JSONArray associated with an index. 243 * 244 * @param index The index must be between 0 and length() - 1. 245 * @return A JSONArray value. 246 * @throws RuntimeException If there is no value for the index. or if the value is not a JSONArray 247 */ 248 public JSONArray getJSONArray(int index) 249 { 250 Object o = get(index); 251 if (o instanceof JSONArray) 252 { 253 return (JSONArray) o; 254 } 255 256 throw new RuntimeException("JSONArray[" + index + "] is not a JSONArray."); 257 } 258 259 /** 260 * Get the JSONObject associated with an index. 261 * 262 * @param index subscript 263 * @return A JSONObject value. 264 * @throws RuntimeException If there is no value for the index or if the value is not a JSONObject 265 */ 266 public JSONObject getJSONObject(int index) 267 { 268 Object o = get(index); 269 if (o instanceof JSONObject) 270 { 271 return (JSONObject) o; 272 } 273 274 throw new RuntimeException("JSONArray[" + index + "] is not a JSONObject."); 275 } 276 277 /** 278 * Get the long value associated with an index. 279 * 280 * @param index The index must be between 0 and length() - 1. 281 * @return The value. 282 * @throws IllegalArgumentException If the key is not found or if the value cannot be converted to a number. 283 */ 284 public long getLong(int index) 285 { 286 Object o = get(index); 287 return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(index); 288 } 289 290 /** 291 * Get the string associated with an index. 292 * 293 * @param index The index must be between 0 and length() - 1. 294 * @return A string value. 295 * @throws RuntimeException If there is no value for the index. 296 */ 297 public String getString(int index) 298 { 299 return get(index).toString(); 300 } 301 302 /** 303 * Determine if the value is null. 304 * 305 * @param index The index must be between 0 and length() - 1. 306 * @return true if the value at the index is null, or if there is no value. 307 */ 308 public boolean isNull(int index) 309 { 310 return get(index) == JSONObject.NULL; 311 } 312 313 /** 314 * Make a string from the contents of this JSONArray. The <code>separator</code> string is inserted between each 315 * element. Warning: This method assumes that the data structure is acyclical. 316 * 317 * @param separator A string that will be inserted between the elements. 318 * @return a string. 319 * @throws RuntimeException If the array contains an invalid number. 320 */ 321 public String join(String separator) 322 { 323 int len = length(); 324 StringBuilder buffer = new StringBuilder(); 325 326 for (int i = 0; i < len; i += 1) 327 { 328 if (i > 0) buffer.append(separator); 329 330 buffer.append(JSONObject.valueToString(list.get(i))); 331 } 332 333 return buffer.toString(); 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 An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or 350 * String, or the JSONObject.NULL object. 351 * @return 352 */ 353 public JSONArray put(Object value) 354 { 355 assert value != null; 356 357 JSONObject.testValidity(value); 358 359 list.add(value); 360 361 return this; 362 } 363 364 /** 365 * Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then 366 * null elements will be added as necessary to pad it out. 367 * 368 * @param index The subscript. 369 * @param value The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray, 370 * JSONObject, Long, or String, or the JSONObject.NULL object. 371 * @return 372 * @throws RuntimeException If the index is negative or if the the value is an invalid number. 373 */ 374 public JSONArray put(int index, Object value) 375 { 376 assert value != null; 377 378 if (index < 0) 379 { 380 throw new RuntimeException("JSONArray[" + index + "] not found."); 381 } 382 383 JSONObject.testValidity(value); 384 385 if (index < length()) 386 { 387 list.set(index, value); 388 } 389 else 390 { 391 while (index != length()) list.add(JSONObject.NULL); 392 393 list.add(value); 394 } 395 396 return this; 397 } 398 399 /** 400 * Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If it is not possible to 401 * produce a syntactically correct JSON text then null will be returned instead. This could occur if the array 402 * contains an invalid number. 403 * <p/> 404 * Warning: This method assumes that the data structure is acyclical. 405 * 406 * @return a printable, displayable, transmittable representation of the array. 407 */ 408 @Override 409 public String toString() 410 { 411 try 412 { 413 return '[' + join(",") + ']'; 414 } 415 catch (Exception e) 416 { 417 return null; 418 } 419 } 420 421 Object[] toArray() 422 { 423 return list.toArray(); 424 } 425 426 @Override 427 public boolean equals(Object obj) 428 { 429 if (obj == null) return false; 430 431 if (!(obj instanceof JSONArray)) return false; 432 433 JSONArray other = (JSONArray) obj; 434 435 return list.equals(other.list); 436 } 437 }