    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}