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}