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}