001    package org.apache.tapestry.json;
002    
003    /*
004     Copyright (c) 2002 JSON.org
005    
006     Permission is hereby granted, free of charge, to any person obtaining a copy
007     of this software and associated documentation files (the "Software"), to deal
008     in the Software without restriction, including without limitation the rights
009     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010     copies of the Software, and to permit persons to whom the Software is
011     furnished to do so, subject to the following conditions:
012    
013     The above copyright notice and this permission notice shall be included in all
014     copies or substantial portions of the Software.
015    
016     The Software shall be used for Good, not Evil.
017    
018     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
021     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
023     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
024     SOFTWARE.
025     */
026    
027    import java.text.ParseException;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.NoSuchElementException;
031    
032    /**
033     * A JSONArray is an ordered sequence of values. Its external form is a string
034     * wrapped in square brackets with commas between the values. The internal form
035     * is an object having get() and opt() methods for accessing the values by
036     * index, and put() methods for adding or replacing values. The values can be
037     * any of these types: Boolean, JSONArray, JSONObject, Number, String, or the
038     * JSONObject.NULL object.
039     * <p>
040     * The constructor can convert a JSON external form string into an internal form
041     * Java object. The toString() method creates an external form string.
042     * <p>
043     * A get() method returns a value if one can be found, and throws an exception
044     * if one cannot be found. An opt() method returns a default value instead of
045     * throwing an exception, and so is useful for obtaining optional values.
046     * <p>
047     * The generic get() and opt() methods return an object which you can cast or
048     * query for type. There are also typed get() and opt() methods that do typing
049     * checking and type coersion for you.
050     * <p>
051     * The texts produced by the toString() methods are very strict. The
052     * constructors are more forgiving in the texts they will accept.
053     * <ul>
054     * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
055     * before the closing bracket.</li>
056     * <li>The null value will be inserted when there is <code>,</code>&nbsp;<small>(comma)</small>
057     * elision.</li>
058     * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
059     * <li>Strings do not need to be quoted at all if they do not begin with a
060     * quote or single quote, and if they do not contain leading or trailing spaces,
061     * and if they do not contain any of these characters:
062     * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and
063     * if they are not the reserved words <code>true</code>, <code>false</code>,
064     * or <code>null</code>.</li>
065     * <li>Values can be followed by <code>;</code> as well as by <code>,</code></li>
066     * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
067     * <code>0x-</code> <small>(hex)</small> prefix.</li>
068     * <li>Line comments can begin with <code>#</code></li>
069     * </ul>
070     * 
071     * @author JSON.org
072     * @version 1
073     */
074    public class JSONArray
075    {
076    
077        /**
078         * The getArrayList where the JSONArray's properties are kept.
079         */
080        private ArrayList myArrayList;
081    
082        /**
083         * Construct an empty JSONArray.
084         */
085        public JSONArray()
086        {
087            this.myArrayList = new ArrayList();
088        }
089    
090        /**
091         * Construct a JSONArray from a JSONTokener.
092         * 
093         * @param x
094         *            A JSONTokener
095         * @exception ParseException
096         *                A JSONArray must start with '['
097         * @exception ParseException
098         *                Expected a ',' or ']'
099         */
100        public JSONArray(JSONTokener x)
101            throws ParseException
102        {
103            this();
104            if (x.nextClean() != '[') { throw x
105                    .syntaxError("A JSONArray must start with '['"); }
106            if (x.nextClean() == ']') { return; }
107            x.back();
108            while(true)
109            {
110                if (x.nextClean() == ',')
111                {
112                    x.back();
113                    this.myArrayList.add(null);
114                }
115                else
116                {
117                    x.back();
118                    this.myArrayList.add(x.nextValue());
119                }
120                switch(x.nextClean())
121                {
122                case ';':
123                case ',':
124                    if (x.nextClean() == ']') { return; }
125                    x.back();
126                    break;
127                case ']':
128                    return;
129                default:
130                    throw x.syntaxError("Expected a ',' or ']'");
131                }
132            }
133        }
134    
135        /**
136         * Construct a JSONArray from a source string.
137         * 
138         * @param string
139         *            A string that begins with <code>[</code>&nbsp;<small>(left
140         *            bracket)</small> and ends with <code>]</code>&nbsp;<small>(right
141         *            bracket)</small>.
142         * @exception ParseException
143         *                The string must conform to JSON syntax.
144         */
145        public JSONArray(String string)
146            throws ParseException
147        {
148            this(new JSONTokener(string));
149        }
150    
151        /**
152         * Construct a JSONArray from a Collection.
153         * 
154         * @param collection
155         *            A Collection.
156         */
157        public JSONArray(Collection collection)
158        {
159            this.myArrayList = new ArrayList(collection);
160        }
161    
162        /**
163         * Get the object value associated with an index.
164         * 
165         * @param index
166         *            The index must be between 0 and length() - 1.
167         * @return An object value.
168         * @exception NoSuchElementException
169         */
170        public Object get(int index)
171        {
172            Object o = opt(index);
173            if (o == null) { throw new NoSuchElementException("JSONArray[" + index
174                    + "] not found."); }
175            return o;
176        }
177    
178        /**
179         * Get the ArrayList which is holding the elements of the JSONArray.
180         * 
181         * @return The ArrayList.
182         */
183        ArrayList getArrayList()
184        {
185            return this.myArrayList;
186        }
187    
188        /**
189         * Get the boolean value associated with an index. The string values "true"
190         * and "false" are converted to boolean.
191         * 
192         * @param index
193         *            The index must be between 0 and length() - 1.
194         * @return The truth.
195         * @exception NoSuchElementException
196         *                if the index is not found
197         * @exception ClassCastException
198         */
199        public boolean getBoolean(int index)
200        {
201            Object o = get(index);
202            if (o.equals(Boolean.FALSE)
203                    || (o instanceof String && ((String) o)
204                            .equalsIgnoreCase("false")))
205            {
206                return false;
207            }
208            else if (o.equals(Boolean.TRUE)
209                    || (o instanceof String && ((String) o)
210                            .equalsIgnoreCase("true"))) { return true; }
211            throw new ClassCastException("JSONArray[" + index + "] not a Boolean.");
212        }
213    
214        /**
215         * Get the double value associated with an index.
216         * 
217         * @param index
218         *            The index must be between 0 and length() - 1.
219         * @return The value.
220         * @exception NoSuchElementException
221         *                if the key is not found
222         * @exception NumberFormatException
223         *                if the value cannot be converted to a number.
224         */
225        public double getDouble(int index)
226        {
227            Object o = get(index);
228            if (o instanceof Number) { return ((Number) o).doubleValue(); }
229            if (o instanceof String) { return new Double((String) o).doubleValue(); }
230            throw new NumberFormatException("JSONObject[" + index
231                    + "] is not a number.");
232        }
233    
234        /**
235         * Get the int value associated with an index.
236         * 
237         * @param index
238         *            The index must be between 0 and length() - 1.
239         * @return The value.
240         * @exception NoSuchElementException
241         *                if the key is not found
242         * @exception NumberFormatException
243         *                if the value cannot be converted to a number.
244         */
245        public int getInt(int index)
246        {
247            Object o = get(index);
248            return o instanceof Number ? ((Number) o).intValue()
249                    : (int) getDouble(index);
250        }
251    
252        /**
253         * Get the JSONArray associated with an index.
254         * 
255         * @param index
256         *            The index must be between 0 and length() - 1.
257         * @return A JSONArray value.
258         * @exception NoSuchElementException
259         *                if the index is not found or if the value is not a
260         *                JSONArray
261         */
262        public JSONArray getJSONArray(int index)
263        {
264            Object o = get(index);
265            if (o instanceof JSONArray) { return (JSONArray) o; }
266            throw new NoSuchElementException("JSONArray[" + index
267                    + "] is not a JSONArray.");
268        }
269    
270        /**
271         * Get the JSONObject associated with an index.
272         * 
273         * @param index
274         *            subscript
275         * @return A JSONObject value.
276         * @exception NoSuchElementException
277         *                if the index is not found or if the value is not a
278         *                JSONObject
279         */
280        public JSONObject getJSONObject(int index)
281        {
282            Object o = get(index);
283            if (o instanceof JSONObject) { return (JSONObject) o; }
284            throw new NoSuchElementException("JSONArray[" + index
285                    + "] is not a JSONObject.");
286        }
287    
288        /**
289         * Get the string associated with an index.
290         * 
291         * @param index
292         *            The index must be between 0 and length() - 1.
293         * @return A string value.
294         * @exception NoSuchElementException
295         */
296        public String getString(int index)
297        {
298            return get(index).toString();
299        }
300    
301        /**
302         * Determine if the value is null.
303         * 
304         * @param index
305         *            The index must be between 0 and length() - 1.
306         * @return true if the value at the index is null, or if there is no value.
307         */
308        public boolean isNull(int index)
309        {
310            Object o = opt(index);
311            return o == null || o.equals(null);
312        }
313    
314        /**
315         * Make a string from the contents of this JSONArray. The separator string
316         * is inserted between each element. Warning: This method assumes that the
317         * data structure is acyclical.
318         * 
319         * @param separator
320         *            A string that will be inserted between the elements.
321         * @return a string.
322         */
323        public String join(String separator)
324        {
325            int len = length();
326            StringBuffer sb = new StringBuffer();
327            for(int i = 0; i < len; i += 1)
328            {
329                if (i > 0)
330                {
331                    sb.append(separator);
332                }
333                sb.append(JSONObject.valueToString(this.myArrayList.get(i)));
334            }
335            return sb.toString();
336        }
337    
338        /**
339         * Get the length of the JSONArray.
340         * 
341         * @return The length (or size).
342         */
343        public int length()
344        {
345            return this.myArrayList.size();
346        }
347    
348        /**
349         * Get the optional object value associated with an index.
350         * 
351         * @param index
352         *            The index must be between 0 and length() - 1.
353         * @return An object value, or null if there is no object at that index.
354         */
355        public Object opt(int index)
356        {
357            return index < 0 || index >= length() ? null : this.myArrayList
358                    .get(index);
359        }
360    
361        /**
362         * Get the optional boolean value associated with an index. It returns false
363         * if there is no value at that index, or if the value is not Boolean.TRUE
364         * or the String "true".
365         * 
366         * @param index
367         *            The index must be between 0 and length() - 1.
368         * @return The truth.
369         */
370        public boolean optBoolean(int index)
371        {
372            return optBoolean(index, false);
373        }
374    
375        /**
376         * Get the optional boolean value associated with an index. It returns the
377         * defaultValue if there is no value at that index or if it is not a Boolean
378         * or the String "true" or "false" (case insensitive).
379         * 
380         * @param index
381         *            The index must be between 0 and length() - 1.
382         * @param defaultValue
383         *            A boolean default.
384         * @return The truth.
385         */
386        public boolean optBoolean(int index, boolean defaultValue)
387        {
388            Object o = opt(index);
389            if (o != null)
390            {
391                if (o.equals(Boolean.FALSE)
392                        || (o instanceof String && ((String) o)
393                                .equalsIgnoreCase("false")))
394                {
395                    return false;
396                }
397                else if (o.equals(Boolean.TRUE)
398                        || (o instanceof String && ((String) o)
399                                .equalsIgnoreCase("true"))) { return true; }
400            }
401            return defaultValue;
402        }
403    
404        /**
405         * Get the optional double value associated with an index. NaN is returned
406         * if the index is not found, or if the value is not a number and cannot be
407         * converted to a number.
408         * 
409         * @param index
410         *            The index must be between 0 and length() - 1.
411         * @return The value.
412         */
413        public double optDouble(int index)
414        {
415            return optDouble(index, Double.NaN);
416        }
417    
418        /**
419         * Get the optional double value associated with an index. The defaultValue
420         * is returned if the index is not found, or if the value is not a number
421         * and cannot be converted to a number.
422         * 
423         * @param index
424         *            subscript
425         * @param defaultValue
426         *            The default value.
427         * @return The value.
428         */
429        public double optDouble(int index, double defaultValue)
430        {
431            Object o = opt(index);
432            if (o != null)
433            {
434                if (o instanceof Number) { return ((Number) o).doubleValue(); }
435                try
436                {
437                    return new Double((String) o).doubleValue();
438                }
439                catch (Exception e)
440                {
441                    return defaultValue;
442                }
443            }
444            return defaultValue;
445        }
446    
447        /**
448         * Get the optional int value associated with an index. Zero is returned if
449         * the index is not found, or if the value is not a number and cannot be
450         * converted to a number.
451         * 
452         * @param index
453         *            The index must be between 0 and length() - 1.
454         * @return The value.
455         */
456        public int optInt(int index)
457        {
458            return optInt(index, 0);
459        }
460    
461        /**
462         * Get the optional int value associated with an index. The defaultValue is
463         * returned if the index is not found, or if the value is not a number and
464         * cannot be converted to a number.
465         * 
466         * @param index
467         *            The index must be between 0 and length() - 1.
468         * @param defaultValue
469         *            The default value.
470         * @return The value.
471         */
472        public int optInt(int index, int defaultValue)
473        {
474            Object o = opt(index);
475            if (o != null)
476            {
477                if (o instanceof Number) { return ((Number) o).intValue(); }
478                try
479                {
480                    return Integer.parseInt((String) o);
481                }
482                catch (Exception e)
483                {
484                    return defaultValue;
485                }
486            }
487            return defaultValue;
488        }
489    
490        /**
491         * Get the optional JSONArray associated with an index.
492         * 
493         * @param index
494         *            subscript
495         * @return A JSONArray value, or null if the index has no value, or if the
496         *         value is not a JSONArray.
497         */
498        public JSONArray optJSONArray(int index)
499        {
500            Object o = opt(index);
501            return o instanceof JSONArray ? (JSONArray) o : null;
502        }
503    
504        /**
505         * Get the optional JSONObject associated with an index. Null is returned if
506         * the key is not found, or null if the index has no value, or if the value
507         * is not a JSONObject.
508         * 
509         * @param index
510         *            The index must be between 0 and length() - 1.
511         * @return A JSONObject value.
512         */
513        public JSONObject optJSONObject(int index)
514        {
515            Object o = opt(index);
516            return o instanceof JSONObject ? (JSONObject) o : null;
517        }
518    
519        /**
520         * Get the optional string value associated with an index. It returns an
521         * empty string if there is no value at that index. If the value is not a
522         * string and is not null, then it is coverted to a string.
523         * 
524         * @param index
525         *            The index must be between 0 and length() - 1.
526         * @return A String value.
527         */
528        public String optString(int index)
529        {
530            return optString(index, "");
531        }
532    
533        /**
534         * Get the optional string associated with an index. The defaultValue is
535         * returned if the key is not found.
536         * 
537         * @param index
538         *            The index must be between 0 and length() - 1.
539         * @param defaultValue
540         *            The default value.
541         * @return A String value.
542         */
543        public String optString(int index, String defaultValue)
544        {
545            Object o = opt(index);
546            return o != null ? o.toString() : defaultValue;
547        }
548    
549        /**
550         * Append a boolean value.
551         * 
552         * @param value
553         *            A boolean value.
554         * @return this.
555         */
556        public JSONArray put(boolean value)
557        {
558            put(Boolean.valueOf(value));
559            return this;
560        }
561    
562        /**
563         * Append a double value.
564         * 
565         * @param value
566         *            A double value.
567         * @return this.
568         */
569        public JSONArray put(double value)
570        {
571            put(new Double(value));
572            return this;
573        }
574    
575        /**
576         * Append an int value.
577         * 
578         * @param value
579         *            An int value.
580         * @return this.
581         */
582        public JSONArray put(int value)
583        {
584            put(new Integer(value));
585            return this;
586        }
587    
588        /**
589         * Append an object value.
590         * 
591         * @param value
592         *            An object value. The value should be a Boolean, Double,
593         *            Integer, JSONArray, JSObject, or String, or the
594         *            JSONObject.NULL object.
595         * @return this.
596         */
597        public JSONArray put(Object value)
598        {
599            this.myArrayList.add(value);
600            return this;
601        }
602    
603        /**
604         * Put or replace a boolean value in the JSONArray.
605         * 
606         * @param index
607         *            subscript The subscript. If the index is greater than the
608         *            length of the JSONArray, then null elements will be added as
609         *            necessary to pad it out.
610         * @param value
611         *            A boolean value.
612         * @return this.
613         * @exception NoSuchElementException
614         *                The index must not be negative.
615         */
616        public JSONArray put(int index, boolean value)
617        {
618            put(index, Boolean.valueOf(value));
619            return this;
620        }
621    
622        /**
623         * Put or replace a double value.
624         * 
625         * @param index
626         *            subscript The subscript. If the index is greater than the
627         *            length of the JSONArray, then null elements will be added as
628         *            necessary to pad it out.
629         * @param value
630         *            A double value.
631         * @return this.
632         * @exception NoSuchElementException
633         *                The index must not be negative.
634         */
635        public JSONArray put(int index, double value)
636        {
637            put(index, new Double(value));
638            return this;
639        }
640    
641        /**
642         * Put or replace an int value.
643         * 
644         * @param index
645         *            subscript The subscript. If the index is greater than the
646         *            length of the JSONArray, then null elements will be added as
647         *            necessary to pad it out.
648         * @param value
649         *            An int value.
650         * @return this.
651         * @exception NoSuchElementException
652         *                The index must not be negative.
653         */
654        public JSONArray put(int index, int value)
655        {
656            put(index, new Integer(value));
657            return this;
658        }
659    
660        /**
661         * Put or replace an object value in the JSONArray.
662         * 
663         * @param index
664         *            The subscript. If the index is greater than the length of the
665         *            JSONArray, then null elements will be added as necessary to
666         *            pad it out.
667         * @param value
668         *            An object value.
669         * @return this.
670         * @exception NoSuchElementException
671         *                The index must not be negative.
672         * @exception NullPointerException
673         *                The index must not be null.
674         */
675        public JSONArray put(int index, Object value)
676        {
677            if (index < 0)
678            {
679                throw new NoSuchElementException("JSONArray[" + index
680                        + "] not found.");
681            }
682            else if (value == null)
683            {
684                throw new NullPointerException();
685            }
686            else if (index < length())
687            {
688                this.myArrayList.set(index, value);
689            }
690            else
691            {
692                while(index != length())
693                {
694                    put(null);
695                }
696                put(value);
697            }
698            return this;
699        }
700    
701        /**
702         * Produce a JSONObject by combining a JSONArray of names with the values of
703         * this JSONArray.
704         * 
705         * @param names
706         *            A JSONArray containing a list of key strings. These will be
707         *            paired with the values.
708         * @return A JSONObject, or null if there are no names or if this JSONArray
709         *         has no values.
710         */
711        public JSONObject toJSONObject(JSONArray names)
712        {
713            if (names == null || names.length() == 0 || length() == 0) { return null; }
714            JSONObject jo = new JSONObject();
715            for(int i = 0; i < names.length(); i += 1)
716            {
717                jo.put(names.getString(i), this.opt(i));
718            }
719            return jo;
720        }
721    
722        /**
723         * Make an JSON external form string of this JSONArray. For compactness, no
724         * unnecessary whitespace is added. Warning: This method assumes that the
725         * data structure is acyclical.
726         * 
727         * @return a printable, displayable, transmittable representation of the
728         *         array.
729         */
730        public String toString()
731        {
732            return '[' + join(",") + ']';
733        }
734    
735        /**
736         * Make a prettyprinted JSON string of this JSONArray. Warning: This method
737         * assumes that the data structure is non-cyclical.
738         * 
739         * @param indentFactor
740         *            The number of spaces to add to each level of indentation.
741         * @return a printable, displayable, transmittable representation of the
742         *         object, beginning with <code>[</code>&nbsp;<small>(left
743         *         bracket)</small> and ending with <code>]</code>&nbsp;<small>(right
744         *         bracket)</small>.
745         */
746        public String toString(int indentFactor)
747        {
748            return toString(indentFactor, 0);
749        }
750    
751        /**
752         * Make a prettyprinted string of this JSONArray. Warning: This method
753         * assumes that the data structure is non-cyclical.
754         * 
755         * @param indentFactor
756         *            The number of spaces to add to each level of indentation.
757         * @param indent
758         *            The indention of the top level.
759         * @return a printable, displayable, transmittable representation of the
760         *         array.
761         */
762        String toString(int indentFactor, int indent)
763        {
764            int len = length();
765            if (len == 0) { return "[]"; }
766            int i;
767            StringBuffer sb = new StringBuffer("[");
768            if (len == 1)
769            {
770                sb.append(JSONObject.valueToString(this.myArrayList.get(0),
771                        indentFactor, indent));
772            }
773            else
774            {
775                int newindent = indent + indentFactor;
776                sb.append('\n');
777                for(i = 0; i < len; i += 1)
778                {
779                    if (i > 0)
780                    {
781                        sb.append(",\n");
782                    }
783                    for(int j = 0; j < newindent; j += 1)
784                    {
785                        sb.append(' ');
786                    }
787                    sb.append(JSONObject.valueToString(this.myArrayList.get(i),
788                            indentFactor, newindent));
789                }
790                sb.append('\n');
791                for(i = 0; i < indent; i += 1)
792                {
793                    sb.append(' ');
794                }
795            }
796            sb.append(']');
797            return sb.toString();
798        }
799    }