1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.json;
18
19import java.lang.reflect.Array;
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.Iterator;
23import java.util.List;
24
25// Note: this class was written without inspecting the non-free org.json sourcecode.
26
27/**
28 * A dense indexed sequence of values. Values may be any mix of
29 * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings,
30 * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}.
31 * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite()
32 * infinities}, or of any type not listed here.
33 *
34 * <p>{@code JSONArray} has the same type coercion behavior and
35 * optional/mandatory accessors as {@link JSONObject}. See that class'
36 * documentation for details.
37 *
38 * <p><strong>Warning:</strong> this class represents null in two incompatible
39 * ways: the standard Java {@code null} reference, and the sentinel value {@link
40 * JSONObject#NULL}. In particular, {@code get} fails if the requested index
41 * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}.
42 *
43 * <p>Instances of this class are not thread safe. Although this class is
44 * nonfinal, it was not designed for inheritance and should not be subclassed.
45 * In particular, self-use by overridable methods is not specified. See
46 * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else
47 * prohibit it" for further information.
48 */
49public class JSONArray {
50
51    private final List<Object> values;
52
53    /**
54     * Creates a {@code JSONArray} with no values.
55     */
56    public JSONArray() {
57        values = new ArrayList<Object>();
58    }
59
60    /**
61     * Creates a new {@code JSONArray} by copying all values from the given
62     * collection.
63     *
64     * @param copyFrom a collection whose values are of supported types.
65     *     Unsupported values are not permitted and will yield an array in an
66     *     inconsistent state.
67     */
68    /* Accept a raw type for API compatibility */
69    public JSONArray(Collection copyFrom) {
70        this();
71        if (copyFrom != null) {
72            for (Iterator it = copyFrom.iterator(); it.hasNext();) {
73                put(JSONObject.wrap(it.next()));
74            }
75        }
76    }
77
78    /**
79     * Creates a new {@code JSONArray} with values from the next array in the
80     * tokener.
81     *
82     * @param readFrom a tokener whose nextValue() method will yield a
83     *     {@code JSONArray}.
84     * @throws JSONException if the parse fails or doesn't yield a
85     *     {@code JSONArray}.
86     */
87    public JSONArray(JSONTokener readFrom) throws JSONException {
88        /*
89         * Getting the parser to populate this could get tricky. Instead, just
90         * parse to temporary JSONArray and then steal the data from that.
91         */
92        Object object = readFrom.nextValue();
93        if (object instanceof JSONArray) {
94            values = ((JSONArray) object).values;
95        } else {
96            throw JSON.typeMismatch(object, "JSONArray");
97        }
98    }
99
100    /**
101     * Creates a new {@code JSONArray} with values from the JSON string.
102     *
103     * @param json a JSON-encoded string containing an array.
104     * @throws JSONException if the parse fails or doesn't yield a {@code
105     *     JSONArray}.
106     */
107    public JSONArray(String json) throws JSONException {
108        this(new JSONTokener(json));
109    }
110
111    /**
112     * Creates a new {@code JSONArray} with values from the given primitive array.
113     */
114    public JSONArray(Object array) throws JSONException {
115        if (!array.getClass().isArray()) {
116            throw new JSONException("Not a primitive array: " + array.getClass());
117        }
118        final int length = Array.getLength(array);
119        values = new ArrayList<Object>(length);
120        for (int i = 0; i < length; ++i) {
121            put(JSONObject.wrap(Array.get(array, i)));
122        }
123    }
124
125    /**
126     * Returns the number of values in this array.
127     */
128    public int length() {
129        return values.size();
130    }
131
132    /**
133     * Appends {@code value} to the end of this array.
134     *
135     * @return this array.
136     */
137    public JSONArray put(boolean value) {
138        values.add(value);
139        return this;
140    }
141
142    /**
143     * Appends {@code value} to the end of this array.
144     *
145     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
146     *     {@link Double#isInfinite() infinities}.
147     * @return this array.
148     */
149    public JSONArray put(double value) throws JSONException {
150        values.add(JSON.checkDouble(value));
151        return this;
152    }
153
154    /**
155     * Appends {@code value} to the end of this array.
156     *
157     * @return this array.
158     */
159    public JSONArray put(int value) {
160        values.add(value);
161        return this;
162    }
163
164    /**
165     * Appends {@code value} to the end of this array.
166     *
167     * @return this array.
168     */
169    public JSONArray put(long value) {
170        values.add(value);
171        return this;
172    }
173
174    /**
175     * Appends {@code value} to the end of this array.
176     *
177     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
178     *     Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
179     *     not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
180     *     infinities}. Unsupported values are not permitted and will cause the
181     *     array to be in an inconsistent state.
182     * @return this array.
183     */
184    public JSONArray put(Object value) {
185        values.add(value);
186        return this;
187    }
188
189    /**
190     * Same as {@link #put}, with added validity checks.
191     */
192    void checkedPut(Object value) throws JSONException {
193        if (value instanceof Number) {
194            JSON.checkDouble(((Number) value).doubleValue());
195        }
196
197        put(value);
198    }
199
200    /**
201     * Sets the value at {@code index} to {@code value}, null padding this array
202     * to the required length if necessary. If a value already exists at {@code
203     * index}, it will be replaced.
204     *
205     * @return this array.
206     */
207    public JSONArray put(int index, boolean value) throws JSONException {
208        return put(index, (Boolean) value);
209    }
210
211    /**
212     * Sets the value at {@code index} to {@code value}, null padding this array
213     * to the required length if necessary. If a value already exists at {@code
214     * index}, it will be replaced.
215     *
216     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
217     *     {@link Double#isInfinite() infinities}.
218     * @return this array.
219     */
220    public JSONArray put(int index, double value) throws JSONException {
221        return put(index, (Double) value);
222    }
223
224    /**
225     * Sets the value at {@code index} to {@code value}, null padding this array
226     * to the required length if necessary. If a value already exists at {@code
227     * index}, it will be replaced.
228     *
229     * @return this array.
230     */
231    public JSONArray put(int index, int value) throws JSONException {
232        return put(index, (Integer) value);
233    }
234
235    /**
236     * Sets the value at {@code index} to {@code value}, null padding this array
237     * to the required length if necessary. If a value already exists at {@code
238     * index}, it will be replaced.
239     *
240     * @return this array.
241     */
242    public JSONArray put(int index, long value) throws JSONException {
243        return put(index, (Long) value);
244    }
245
246    /**
247     * Sets the value at {@code index} to {@code value}, null padding this array
248     * to the required length if necessary. If a value already exists at {@code
249     * index}, it will be replaced.
250     *
251     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
252     *     Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
253     *     not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
254     *     infinities}.
255     * @return this array.
256     */
257    public JSONArray put(int index, Object value) throws JSONException {
258        if (value instanceof Number) {
259            // deviate from the original by checking all Numbers, not just floats & doubles
260            JSON.checkDouble(((Number) value).doubleValue());
261        }
262        while (values.size() <= index) {
263            values.add(null);
264        }
265        values.set(index, value);
266        return this;
267    }
268
269    /**
270     * Returns true if this array has no value at {@code index}, or if its value
271     * is the {@code null} reference or {@link JSONObject#NULL}.
272     */
273    public boolean isNull(int index) {
274        Object value = opt(index);
275        return value == null || value == JSONObject.NULL;
276    }
277
278    /**
279     * Returns the value at {@code index}.
280     *
281     * @throws JSONException if this array has no value at {@code index}, or if
282     *     that value is the {@code null} reference. This method returns
283     *     normally if the value is {@code JSONObject#NULL}.
284     */
285    public Object get(int index) throws JSONException {
286        try {
287            Object value = values.get(index);
288            if (value == null) {
289                throw new JSONException("Value at " + index + " is null.");
290            }
291            return value;
292        } catch (IndexOutOfBoundsException e) {
293            throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")");
294        }
295    }
296
297    /**
298     * Returns the value at {@code index}, or null if the array has no value
299     * at {@code index}.
300     */
301    public Object opt(int index) {
302        if (index < 0 || index >= values.size()) {
303            return null;
304        }
305        return values.get(index);
306    }
307
308    /**
309     * Removes and returns the value at {@code index}, or null if the array has no value
310     * at {@code index}.
311     */
312    public Object remove(int index) {
313        if (index < 0 || index >= values.size()) {
314            return null;
315        }
316        return values.remove(index);
317    }
318
319    /**
320     * Returns the value at {@code index} if it exists and is a boolean or can
321     * be coerced to a boolean.
322     *
323     * @throws JSONException if the value at {@code index} doesn't exist or
324     *     cannot be coerced to a boolean.
325     */
326    public boolean getBoolean(int index) throws JSONException {
327        Object object = get(index);
328        Boolean result = JSON.toBoolean(object);
329        if (result == null) {
330            throw JSON.typeMismatch(index, object, "boolean");
331        }
332        return result;
333    }
334
335    /**
336     * Returns the value at {@code index} if it exists and is a boolean or can
337     * be coerced to a boolean. Returns false otherwise.
338     */
339    public boolean optBoolean(int index) {
340        return optBoolean(index, false);
341    }
342
343    /**
344     * Returns the value at {@code index} if it exists and is a boolean or can
345     * be coerced to a boolean. Returns {@code fallback} otherwise.
346     */
347    public boolean optBoolean(int index, boolean fallback) {
348        Object object = opt(index);
349        Boolean result = JSON.toBoolean(object);
350        return result != null ? result : fallback;
351    }
352
353    /**
354     * Returns the value at {@code index} if it exists and is a double or can
355     * be coerced to a double.
356     *
357     * @throws JSONException if the value at {@code index} doesn't exist or
358     *     cannot be coerced to a double.
359     */
360    public double getDouble(int index) throws JSONException {
361        Object object = get(index);
362        Double result = JSON.toDouble(object);
363        if (result == null) {
364            throw JSON.typeMismatch(index, object, "double");
365        }
366        return result;
367    }
368
369    /**
370     * Returns the value at {@code index} if it exists and is a double or can
371     * be coerced to a double. Returns {@code NaN} otherwise.
372     */
373    public double optDouble(int index) {
374        return optDouble(index, Double.NaN);
375    }
376
377    /**
378     * Returns the value at {@code index} if it exists and is a double or can
379     * be coerced to a double. Returns {@code fallback} otherwise.
380     */
381    public double optDouble(int index, double fallback) {
382        Object object = opt(index);
383        Double result = JSON.toDouble(object);
384        return result != null ? result : fallback;
385    }
386
387    /**
388     * Returns the value at {@code index} if it exists and is an int or
389     * can be coerced to an int.
390     *
391     * @throws JSONException if the value at {@code index} doesn't exist or
392     *     cannot be coerced to a int.
393     */
394    public int getInt(int index) throws JSONException {
395        Object object = get(index);
396        Integer result = JSON.toInteger(object);
397        if (result == null) {
398            throw JSON.typeMismatch(index, object, "int");
399        }
400        return result;
401    }
402
403    /**
404     * Returns the value at {@code index} if it exists and is an int or
405     * can be coerced to an int. Returns 0 otherwise.
406     */
407    public int optInt(int index) {
408        return optInt(index, 0);
409    }
410
411    /**
412     * Returns the value at {@code index} if it exists and is an int or
413     * can be coerced to an int. Returns {@code fallback} otherwise.
414     */
415    public int optInt(int index, int fallback) {
416        Object object = opt(index);
417        Integer result = JSON.toInteger(object);
418        return result != null ? result : fallback;
419    }
420
421    /**
422     * Returns the value at {@code index} if it exists and is a long or
423     * can be coerced to a long.
424     *
425     * @throws JSONException if the value at {@code index} doesn't exist or
426     *     cannot be coerced to a long.
427     */
428    public long getLong(int index) throws JSONException {
429        Object object = get(index);
430        Long result = JSON.toLong(object);
431        if (result == null) {
432            throw JSON.typeMismatch(index, object, "long");
433        }
434        return result;
435    }
436
437    /**
438     * Returns the value at {@code index} if it exists and is a long or
439     * can be coerced to a long. Returns 0 otherwise.
440     */
441    public long optLong(int index) {
442        return optLong(index, 0L);
443    }
444
445    /**
446     * Returns the value at {@code index} if it exists and is a long or
447     * can be coerced to a long. Returns {@code fallback} otherwise.
448     */
449    public long optLong(int index, long fallback) {
450        Object object = opt(index);
451        Long result = JSON.toLong(object);
452        return result != null ? result : fallback;
453    }
454
455    /**
456     * Returns the value at {@code index} if it exists, coercing it if
457     * necessary.
458     *
459     * @throws JSONException if no such value exists.
460     */
461    public String getString(int index) throws JSONException {
462        Object object = get(index);
463        String result = JSON.toString(object);
464        if (result == null) {
465            throw JSON.typeMismatch(index, object, "String");
466        }
467        return result;
468    }
469
470    /**
471     * Returns the value at {@code index} if it exists, coercing it if
472     * necessary. Returns the empty string if no such value exists.
473     */
474    public String optString(int index) {
475        return optString(index, "");
476    }
477
478    /**
479     * Returns the value at {@code index} if it exists, coercing it if
480     * necessary. Returns {@code fallback} if no such value exists.
481     */
482    public String optString(int index, String fallback) {
483        Object object = opt(index);
484        String result = JSON.toString(object);
485        return result != null ? result : fallback;
486    }
487
488    /**
489     * Returns the value at {@code index} if it exists and is a {@code
490     * JSONArray}.
491     *
492     * @throws JSONException if the value doesn't exist or is not a {@code
493     *     JSONArray}.
494     */
495    public JSONArray getJSONArray(int index) throws JSONException {
496        Object object = get(index);
497        if (object instanceof JSONArray) {
498            return (JSONArray) object;
499        } else {
500            throw JSON.typeMismatch(index, object, "JSONArray");
501        }
502    }
503
504    /**
505     * Returns the value at {@code index} if it exists and is a {@code
506     * JSONArray}. Returns null otherwise.
507     */
508    public JSONArray optJSONArray(int index) {
509        Object object = opt(index);
510        return object instanceof JSONArray ? (JSONArray) object : null;
511    }
512
513    /**
514     * Returns the value at {@code index} if it exists and is a {@code
515     * JSONObject}.
516     *
517     * @throws JSONException if the value doesn't exist or is not a {@code
518     *     JSONObject}.
519     */
520    public JSONObject getJSONObject(int index) throws JSONException {
521        Object object = get(index);
522        if (object instanceof JSONObject) {
523            return (JSONObject) object;
524        } else {
525            throw JSON.typeMismatch(index, object, "JSONObject");
526        }
527    }
528
529    /**
530     * Returns the value at {@code index} if it exists and is a {@code
531     * JSONObject}. Returns null otherwise.
532     */
533    public JSONObject optJSONObject(int index) {
534        Object object = opt(index);
535        return object instanceof JSONObject ? (JSONObject) object : null;
536    }
537
538    /**
539     * Returns a new object whose values are the values in this array, and whose
540     * names are the values in {@code names}. Names and values are paired up by
541     * index from 0 through to the shorter array's length. Names that are not
542     * strings will be coerced to strings. This method returns null if either
543     * array is empty.
544     */
545    public JSONObject toJSONObject(JSONArray names) throws JSONException {
546        JSONObject result = new JSONObject();
547        int length = Math.min(names.length(), values.size());
548        if (length == 0) {
549            return null;
550        }
551        for (int i = 0; i < length; i++) {
552            String name = JSON.toString(names.opt(i));
553            result.put(name, opt(i));
554        }
555        return result;
556    }
557
558    /**
559     * Returns a new string by alternating this array's values with {@code
560     * separator}. This array's string values are quoted and have their special
561     * characters escaped. For example, the array containing the strings '12"
562     * pizza', 'taco' and 'soda' joined on '+' returns this:
563     * <pre>"12\" pizza"+"taco"+"soda"</pre>
564     */
565    public String join(String separator) throws JSONException {
566        JSONStringer stringer = new JSONStringer();
567        stringer.open(JSONStringer.Scope.NULL, "");
568        for (int i = 0, size = values.size(); i < size; i++) {
569            if (i > 0) {
570                stringer.out.append(separator);
571            }
572            stringer.value(values.get(i));
573        }
574        stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
575        return stringer.out.toString();
576    }
577
578    /**
579     * Encodes this array as a compact JSON string, such as:
580     * <pre>[94043,90210]</pre>
581     */
582    @Override public String toString() {
583        try {
584            JSONStringer stringer = new JSONStringer();
585            writeTo(stringer);
586            return stringer.toString();
587        } catch (JSONException e) {
588            return null;
589        }
590    }
591
592    /**
593     * Encodes this array as a human readable JSON string for debugging, such
594     * as:
595     * <pre>
596     * [
597     *     94043,
598     *     90210
599     * ]</pre>
600     *
601     * @param indentSpaces the number of spaces to indent for each level of
602     *     nesting.
603     */
604    public String toString(int indentSpaces) throws JSONException {
605        JSONStringer stringer = new JSONStringer(indentSpaces);
606        writeTo(stringer);
607        return stringer.toString();
608    }
609
610    void writeTo(JSONStringer stringer) throws JSONException {
611        stringer.array();
612        for (Object value : values) {
613            stringer.value(value);
614        }
615        stringer.endArray();
616    }
617
618    @Override public boolean equals(Object o) {
619        return o instanceof JSONArray && ((JSONArray) o).values.equals(values);
620    }
621
622    @Override public int hashCode() {
623        // diverge from the original, which doesn't implement hashCode
624        return values.hashCode();
625    }
626}
627