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