    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.Collection;
    021import java.util.Collections;
    022import java.util.Iterator;
    023import java.util.List;
    024
    025import org.apache.tapestry5.json.exceptions.JSONArrayIndexOutOfBoundsException;
    026import org.apache.tapestry5.json.exceptions.JSONSyntaxException;
    027import org.apache.tapestry5.json.exceptions.JSONTypeMismatchException;
    028import org.apache.tapestry5.json.exceptions.JSONValueNotFoundException;
    029
    030// Note: this class was written without inspecting the non-free org.json sourcecode.
    031
    032/**
    033 * A dense indexed sequence of values. Values may be any mix of
    034 * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings,
    035 * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}.
    036 * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite()
    037 * infinities}, or of any type not listed here.
    038 *
    039 * {@code JSONArray} has the same type coercion behavior and
    040 * optional/mandatory accessors as {@link JSONObject}. See that class'
    041 * documentation for details.
    042 *
    043 * <strong>Warning:</strong> this class represents null in two incompatible
    044 * ways: the standard Java {@code null} reference, and the sentinel value {@link
    045 * JSONObject#NULL}. In particular, {@code get} fails if the requested index
    046 * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}.
    047 *
    048 * Instances of this class are not thread safe.
    049 */
    050public final class JSONArray extends JSONCollection implements Collection<Object> {
    051
    052    private static final long serialVersionUID = 1L;
    053
    054    private final List<Object> values;
    055
    056    /**
    057     * Creates a {@code JSONArray} with no values.
    058     */
    059    public JSONArray() {
    060        values = new ArrayList<Object>();
    061    }
    062
    063    /**
    064     * Creates a new {@code JSONArray} with values from the next array in the
    065     * tokener.
    066     *
    067     * @param readFrom a tokener whose nextValue() method will yield a
    068     *                 {@code JSONArray}.
    069     * @throws JSONSyntaxException if the parse fails
    070     * @throws JSONTypeMismatchException if it doesn't yield a
    071     *                       {@code JSONArray}.
    072     */
    073    JSONArray(JSONTokener readFrom) {
    074        /*
    075         * Getting the parser to populate this could get tricky. Instead, just
    076         * parse to temporary JSONArray and then steal the data from that.
    077         */
    078        Object object = readFrom.nextValue(JSONArray.class);
    079        if (object instanceof JSONArray) {
    080            values = ((JSONArray) object).values;
    081        } else {
    082            throw JSONExceptionBuilder.tokenerTypeMismatch(object, JSONType.ARRAY);
    083        }
    084    }
    085
    086    /**
    087     * Creates a new {@code JSONArray} with values from the JSON string.
    088     *
    089     * @param json a JSON-encoded string containing an array.
    090     * @throws JSONSyntaxException if the parse fails
    091     * @throws JSONTypeMismatchException if it doesn't yield a
    092     *                       {@code JSONArray}.
    093     */
    094    public JSONArray(String json) {
    095        this(new JSONTokener(json));
    096    }
    097
    098    /**
    099     * Creates a new {@code JSONArray} with values from the given primitive array.
    100     *
    101     * @param values The values to use.
    102     * @throws IllegalArgumentException if any of the values are non-finite double values (i.e. NaN or infinite)
    103     */
    104    public JSONArray(Object... values) {
    105        this();
    106        for (int i = 0; i < values.length; ++i) {
    107            checkedPut(values[i]);
    108        }
    109    }
    110
    111    /**
    112     * Create a new array, and adds all values from the iterable to the array (using {@link #putAll(Iterable)}.
    113     *
    114     * This is implemented as a static method so as not to break the semantics of the existing {@link #JSONArray(Object...)} constructor.
    115     * Adding a constructor of type Iterable would change the meaning of <code>new JSONArray(new JSONArray())</code>.
    116     *
    117     * @param iterable
    118     *         collection ot value to include, or null
    119     * @since 5.4
    120     */
    121    public static JSONArray from(Iterable<?> iterable)
    122    {
    123        return new JSONArray().putAll(iterable);
    124    }
    125
    126    /**
    127     * @return Returns the number of values in this array.
    128     * @deprecated Use {@link #size()} instead.
    129     */
    130    public int length() {
    131        return size();
    132    }
    133
    134    /**
    135     * Returns the number of values in this array.
    136     * If this list contains more than {@code Integer.MAX_VALUE} elements, returns
    137     * {@code Integer.MAX_VALUE}.
    138     *
    139     * @return the number of values in this array
    140     * @since 5.7
    141     */
    142    @Override
    143    public int size()
    144    {
    145        return values.size();
    146    }
    147
    148    /**
    149     * Returns {@code true} if this array contains no values.
    150     *
    151     * @return {@code true} if this array contains no values
    152     * @since 5.7
    153     */
    154    @Override
    155    public boolean isEmpty()
    156    {
    157        return values.isEmpty();
    158    }
    159
    160    /**
    161     * Appends {@code value} to the end of this array.
    162     *
    163     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
    164     *              Integer, Long, Double, or {@link JSONObject#NULL}}. May
    165     *              not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
    166     *              infinities}. Unsupported values are not permitted and will cause the
    167     *              array to be in an inconsistent state.
    168     * @return this array.
    169     * @deprecated The use of {@link #add(Object)} is encouraged.
    170     */
    171    public JSONArray put(Object value) {
    172        add(value);
    173        return this;
    174    }
    175
    176    /**
    177     * Appends {@code value} to the end of this array.
    178     *
    179     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
    180     *              Integer, Long, Double, or {@link JSONObject#NULL}}. May
    181     *              not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
    182     *              infinities}. Unsupported values are not permitted and will cause the
    183     *              array to be in an inconsistent state.
    184     * @return {@code true} (as specified by {@link Collection#add})
    185     * @since 5.7
    186     */
    187    @Override
    188    public boolean add(Object value)
    189    {
    190        JSON.testValidity(value);
    191        return values.add(value);
    192    }
    193
    194    /**
    195     * Same as {@link #put}, with added validity checks.
    196     *
    197     * @param value The value to append.
    198     */
    199    void checkedPut(Object value) {
    200        JSON.testValidity(value);
    201        if (value instanceof Number) {
    202            JSON.checkDouble(((Number) value).doubleValue());
    203        }
    204
    205        put(value);
    206    }
    207
    208    /**
    209     * Sets the value at {@code index} to {@code value}, null padding this array
    210     * to the required length if necessary. If a value already exists at {@code
    211     * index}, it will be replaced.
    212     *
    213     * @param index Where to put the value.
    214     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
    215     *              Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
    216     *              not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
    217     *              infinities}.
    218     * @return this array.
    219     * @throws IllegalArgumentException If the value cannot be represented as a finite double value.
    220     * @throws ArrayIndexOutOfBoundsException if the index is lower than 0
    221     */
    222    public JSONArray put(int index, Object value) {
    223        if (index < 0)
    224        {
    225            throw new JSONArrayIndexOutOfBoundsException(index);
    226        }
    227        JSON.testValidity(value);
    228        if (value instanceof Number) {
    229            // deviate from the original by checking all Numbers, not just floats & doubles
    230            JSON.checkDouble(((Number) value).doubleValue());
    231        }
    232        while (values.size() <= index) {
    233            values.add(null);
    234        }
    235        values.set(index, value);
    236        return this;
    237    }
    238
    239    /**
    240     * Returns true if this array has no value at {@code index}, or if its value
    241     * is the {@code null} reference or {@link JSONObject#NULL}.
    242     *
    243     * @param index Which value to check.
    244     * @return true if the value is null.
    245     */
    246    public boolean isNull(int index) {
    247        Object value = values.get(index);
    248        return value == null || value == JSONObject.NULL;
    249    }
    250
    251    /**
    252     * Returns the value at {@code index}.
    253     *
    254     * @param index Which value to get.
    255     * @return the value at the specified location.
    256     * @throws JSONArrayIndexOutOfBoundsException if the given index is out of bounds.
    257     * @throws JSONValueNotFoundException if this array has no value at {@code index}, or if
    258     *                       that value is the {@code null} reference. This method returns
    259     *                       normally if the value is {@code JSONObject#NULL}.
    260     */
    261    public Object get(int index) {
    262        try {
    263            Object value = values.get(index);
    264            if (value == null) {
    265                throw JSONExceptionBuilder.valueNotFound(true, index, JSONType.ANY);
    266            }
    267            return value;
    268        }
    269        catch (IndexOutOfBoundsException e) {
    270            throw new JSONArrayIndexOutOfBoundsException(index);
    271        }
    272    }
    273
    274    /**
    275     * Removes and returns the value at {@code index}, or null if the array has no value
    276     * at {@code index}.
    277     *
    278     * @param index Which value to remove.
    279     * @return The value previously at the specified location.
    280     */
    281    public Object remove(int index) {
    282        if (index < 0 || index >= values.size()) {
    283            return null;
    284        }
    285        return values.remove(index);
    286    }
    287
    288    /**
    289     * Removes the first occurrence of the specified value from this JSONArray,
    290     * if it is present.
    291     *
    292     * @param value value to be removed from this JSONArray, if present
    293     * @return {@code true} if the element was removed
    294     * @since 5.7
    295     */
    296    @Override
    297    public boolean remove(Object value)
    298    {
    299        return values.remove(value);
    300    }
    301
    302    /**
    303     * Removes from this JSONArray all of its values that are contained in the
    304     * specified collection.
    305     *
    306     * @param collection collection containing value to be removed from this JSONArray
    307     * @return {@code true} if this JSONArray changed as a result of the call
    308     * @throws NullPointerException if the specified collection is null.
    309     * @see Collection#contains(Object)
    310     * @since 5.7
    311     */
    312    @Override
    313    public boolean removeAll(Collection<?> collection)
    314    {
    315        return values.removeAll(collection);
    316    }
    317
    318    /**
    319     * Removes all of the values from this JSONArray.
    320     *
    321     * @since 5.7
    322     */
    323    @Override
    324    public void clear()
    325    {
    326        values.clear();
    327    }
    328
    329    /**
    330     * Retains only the values in this JSONArray that are contained in the
    331     * specified collection.
    332     *
    333     * @param collection collection containing elements to be retained in this list
    334     * @return {@code true} if this list changed as a result of the call
    335     * @since 5.7
    336     */
    337    @Override
    338    public boolean retainAll(Collection<?> collection)
    339    {
    340        return values.retainAll(collection);
    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.
    346     *
    347     * @param index Which value to get.
    348     * @return the value at the specified location.
    349     * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or
    350     *                       cannot be coerced to a boolean.
    351     */
    352    public boolean getBoolean(int index) {
    353        Object object = get(index);
    354        Boolean result = JSON.toBoolean(object);
    355        if (result == null) {
    356            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.BOOLEAN);
    357        }
    358        return result;
    359    }
    360
    361    /**
    362     * Returns the value at {@code index} if it exists and is a double or can
    363     * be coerced to a double.
    364     *
    365     * @param index Which value to get.
    366     * @return the value at the specified location.
    367     * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or
    368     *                       cannot be coerced to a double.
    369     */
    370    public double getDouble(int index) {
    371        Object object = get(index);
    372        Double result = JSON.toDouble(object);
    373        if (result == null) {
    374            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.NUMBER);
    375        }
    376        return result;
    377    }
    378
    379    /**
    380     * Returns the value at {@code index} if it exists and is an int or
    381     * can be coerced to an int.
    382     *
    383     * @param index Which value to get.
    384     * @return the value at the specified location.
    385     * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or
    386     *                       cannot be coerced to a int.
    387     */
    388    public int getInt(int index) {
    389        Object object = get(index);
    390        Integer result = JSON.toInteger(object);
    391        if (result == null) {
    392            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.NUMBER);
    393        }
    394        return result;
    395    }
    396
    397    /**
    398     * Returns the value at {@code index} if it exists and is a long or
    399     * can be coerced to a long.
    400     *
    401     * @param index Which value to get.
    402     * @return the value at the specified location.
    403     * @throws JSONTypeMismatchException if the value at {@code index} doesn't exist or
    404     *                       cannot be coerced to a long.
    405     */
    406    public long getLong(int index) {
    407        Object object = get(index);
    408        Long result = JSON.toLong(object);
    409        if (result == null) {
    410            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.NUMBER);
    411        }
    412        return result;
    413    }
    414
    415    /**
    416     * Returns the value at {@code index} if it exists, coercing it if
    417     * necessary.
    418     *
    419     * @param index Which value to get.
    420     * @return the value at the specified location.
    421     * @throws JSONTypeMismatchException if no such value exists.
    422     */
    423    public String getString(int index) {
    424        Object object = get(index);
    425        String result = JSON.toString(object);
    426        if (result == null) {
    427            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.STRING);
    428        }
    429        return result;
    430    }
    431
    432    /**
    433     * Returns the value at {@code index} if it exists and is a {@code
    434     * JSONArray}.
    435     *
    436     * @param index Which value to get.
    437     * @return the value at the specified location.
    438     * @throws JSONTypeMismatchException if the value doesn't exist or is not a {@code
    439     *                       JSONArray}.
    440     */
    441    public JSONArray getJSONArray(int index) {
    442        Object object = get(index);
    443        if (object instanceof JSONArray) {
    444            return (JSONArray) object;
    445        } else {
    446            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.ARRAY);
    447        }
    448    }
    449
    450    /**
    451     * Returns the value at {@code index} if it exists and is a {@code
    452     * JSONObject}.
    453     *
    454     * @param index Which value to get.
    455     * @return the value at the specified location.
    456     * @throws JSONTypeMismatchException if the value doesn't exist or is not a {@code
    457     *                       JSONObject}.
    458     */
    459    public JSONObject getJSONObject(int index) {
    460        Object object = get(index);
    461        if (object instanceof JSONObject) {
    462            return (JSONObject) object;
    463        } else {
    464            throw JSONExceptionBuilder.typeMismatch(true, index, object, JSONType.OBJECT);
    465        }
    466    }
    467
    468    @Override
    469    public boolean equals(Object o) {
    470        return o instanceof JSONArray && ((JSONArray) o).values.equals(values);
    471    }
    472
    473    @Override
    474    public int hashCode() {
    475        // diverge from the original, which doesn't implement hashCode
    476        return values.hashCode();
    477    }
    478
    479    void print(JSONPrintSession session)
    480    {
    481        session.printSymbol('[');
    482
    483        session.indent();
    484
    485        boolean comma = false;
    486
    487        for (Object value : values)
    488        {
    489            if (comma)
    490                session.printSymbol(',');
    491
    492            session.newline();
    493
    494            JSONObject.printValue(session, value);
    495
    496            comma = true;
    497        }
    498
    499        session.outdent();
    500
    501        if (comma)
    502            session.newline();
    503
    504        session.printSymbol(']');
    505    }
    506
    507    /**
    508     * Puts all objects from the collection into this JSONArray, using {@link #put(Object)}.
    509     *
    510     * @param collection
    511     *         List, array, JSONArray, or other iterable object, or null
    512     * @return this JSONArray
    513     * @since 5.4
    514     */
    515    public JSONArray putAll(Iterable<?> collection)
    516    {
    517        if (collection != null)
    518        {
    519            for (Object o : collection)
    520            {
    521                put(o);
    522            }
    523        }
    524
    525        return this;
    526    }
    527
    528    /**
    529     * Adds all objects from the collection into this JSONArray, using {@link #add(Object)}.
    530     *
    531     * @param collection Any collection, or null
    532     * @return boolean true, if JSONArray was changed.
    533     * @since 5.7
    534     */
    535    @Override
    536    public boolean addAll(Collection<? extends Object> collection)
    537    {
    538        if (collection == null)
    539        {
    540            return false;
    541        }
    542
    543        boolean changed = false;
    544        for (Object value : collection)
    545        {
    546            changed = add(value) || changed;
    547        }
    548
    549        return changed;
    550    }
    551
    552    /**
    553     * Returns an unmodifiable list of the contents of the array. This is a wrapper around the list's internal
    554     * storage and is live (changes to the JSONArray affect the returned List).
    555     *
    556     * @return unmodifiable list of array contents
    557     * @since 5.4
    558     */
    559    public List<Object> toList()
    560    {
    561        return Collections.unmodifiableList(values);
    562    }
    563
    564    /**
    565     * Returns an array containing all of the values in this JSONArray in proper
    566     * sequence.
    567     *
    568     * @return an array containing all of the values in this JSONArray in proper
    569     *         sequence
    570     * @since 5.7
    571     */
    572    @Override
    573    public Object[] toArray()
    574    {
    575        return values.toArray();
    576    }
    577
    578    /**
    579     * Returns an array containing all of the values in this JSONArray in
    580     * proper sequence; the runtime type of the returned array is that of
    581     * the specified array.
    582     *
    583     * @param array
    584     *            the array into which the values of this JSONArray are to
    585     *            be stored, if it is big enough; otherwise, a new array of the
    586     *            same runtime type is allocated for this purpose.
    587     * @return an array containing the values of this JSONArray
    588     * @throws ArrayStoreException
    589     *             if the runtime type of the specified array
    590     *             is not a supertype of the runtime type of every element in
    591     *             this list
    592     * @throws NullPointerException
    593     *             if the specified array is null
    594     * @since 5.7
    595     */
    596    @Override
    597    public <T> T[] toArray(T[] array)
    598    {
    599        return values.toArray(array);
    600    }
    601
    602    /**
    603     * Returns an iterator over the values in this array in proper sequence.
    604     *
    605     * @return an iterator over the values in this array in proper sequence
    606     */
    607    @Override
    608    public Iterator<Object> iterator()
    609    {
    610        return values.iterator();
    611    }
    612
    613    /**
    614     * Returns {@code true} if this JSONArray contains the specified value.
    615     *
    616     * @param value value whose presence in this JSONArray is to be tested
    617     * @return {@code true} if this JSONArray contains the specified
    618     *         value
    619     * @since 5.7
    620     */
    621    @Override
    622    public boolean contains(Object value)
    623    {
    624        return values.contains(value);
    625    }
    626
    627    /**
    628     * Returns {@code true} if this JSONArray contains all of the values
    629     * in the specified collection.
    630     *
    631     * @param c collection to be checked for containment in this collection
    632     * @return {@code true} if this collection contains all of the elements
    633     *         in the specified collection
    634     * @throws NullPointerException
    635     *             if the specified collection is null.
    636     * @see #contains(Object)
    637     * @since 5.7
    638     */
    639    @Override
    640    public boolean containsAll(Collection<?> c)
    641    {
    642        return values.containsAll(c);
    643    }
    644}