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 }