GRAILS-1019: Allowing expressions to be used with the 'disabled' attribute for g...
[grails.git] / src / web / org / codehaus / groovy / grails / web / json / JSONArray.java
blobd73d8ffd0e1ad57081d5e91b3d27b645e3628563
1 package org.codehaus.groovy.grails.web.json;
3 /*
4 Copyright (c) 2002 JSON.org
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
16 The Software shall be used for Good, not Evil.
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
27 import org.apache.commons.lang.UnhandledException;
29 import java.io.IOException;
30 import java.io.Writer;
31 import java.util.*;
33 /**
34 * A JSONArray is an ordered sequence of values. Its external text form is a
35 * string wrapped in square brackets with commas separating the values. The
36 * internal form is an object having <code>get</code> and <code>opt</code>
37 * methods for accessing the values by index, and <code>put</code> methods for
38 * adding or replacing values. The values can be any of these types:
39 * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
40 * <code>Number</code>, <code>String</code>, or the
41 * <code>JSONObject.NULL object</code>.
42 * <p/>
43 * The constructor can convert a JSON text into a Java object. The
44 * <code>toString</code> method converts to JSON text.
45 * <p/>
46 * A <code>get</code> method returns a value if one can be found, and throws an
47 * exception if one cannot be found. An <code>opt</code> method returns a
48 * default value instead of throwing an exception, and so is useful for
49 * obtaining optional values.
50 * <p/>
51 * The generic <code>get()</code> and <code>opt()</code> methods return an
52 * object which you can cast or query for type. There are also typed
53 * <code>get</code> and <code>opt</code> methods that do type checking and type
54 * coersion for you.
55 * <p/>
56 * The texts produced by the <code>toString</code> methods strictly conform to
57 * JSON syntax rules. The constructors are more forgiving in the texts they will
58 * accept:
59 * <ul>
60 * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
61 * before the closing bracket.</li>
62 * <li>The <code>null</code> value will be inserted when there
63 * is <code>,</code>&nbsp;<small>(comma)</small> elision.</li>
64 * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
65 * quote)</small>.</li>
66 * <li>Strings do not need to be quoted at all if they do not begin with a quote
67 * or single quote, and if they do not contain leading or trailing spaces,
68 * and if they do not contain any of these characters:
69 * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
70 * and if they are not the reserved words <code>true</code>,
71 * <code>false</code>, or <code>null</code>.</li>
72 * <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as
73 * well as by <code>,</code> <small>(comma)</small>.</li>
74 * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
75 * <code>0x-</code> <small>(hex)</small> prefix.</li>
76 * <li>Comments written in the slashshlash, slashstar, and hash conventions
77 * will be ignored.</li>
78 * </ul>
80 * @author JSON.org
81 * @version 2
83 public class JSONArray implements List {
86 /**
87 * The arrayList where the JSONArray's properties are kept.
89 private ArrayList myArrayList;
92 /**
93 * Construct an empty JSONArray.
95 public JSONArray() {
96 this.myArrayList = new ArrayList();
99 /**
100 * Construct a JSONArray from a JSONTokener.
102 * @param x A JSONTokener
103 * @throws JSONException If there is a syntax error.
105 public JSONArray(JSONTokener x) throws JSONException {
106 this();
107 if (x.nextClean() != '[') {
108 throw x.syntaxError("A JSONArray text must start with '['");
110 if (x.nextClean() == ']') {
111 return;
113 x.back();
114 for (; ;) {
115 if (x.nextClean() == ',') {
116 x.back();
117 this.myArrayList.add(null);
118 } else {
119 x.back();
120 this.myArrayList.add(x.nextValue());
122 switch (x.nextClean()) {
123 case ';':
124 case ',':
125 if (x.nextClean() == ']') {
126 return;
128 x.back();
129 break;
130 case ']':
131 return;
132 default:
133 throw x.syntaxError("Expected a ',' or ']'");
140 * Construct a JSONArray from a source sJSON text.
142 * @param string A string that begins with
143 * <code>[</code>&nbsp;<small>(left bracket)</small>
144 * and ends with <code>]</code>&nbsp;<small>(right bracket)</small>.
145 * @throws JSONException If there is a syntax error.
147 public JSONArray(String string) throws JSONException {
148 this(new JSONTokener(string));
153 * Construct a JSONArray from a Collection.
155 * @param collection A Collection.
157 public JSONArray(Collection collection) {
158 this.myArrayList = new ArrayList(collection);
163 * Get the object value associated with an index.
165 * @param index The index must be between 0 and length() - 1.
166 * @return An object value.
167 * @throws JSONException If there is no value for the index.
169 public Object get(int index) {
170 Object o = opt(index);
171 if (o == null) {
172 throw new UnhandledException(new JSONException("JSONArray[" + index + "] not found."));
174 return o;
177 public Object set(int i, Object o) {
178 return myArrayList.set(i, o);
181 public boolean add(Object o) {
182 return myArrayList.add(o);
185 public void add(int i, Object o) {
186 myArrayList.add(i, o);
189 public Object remove(int i) {
190 return myArrayList.remove(i);
193 public boolean remove(Object o) {
194 return myArrayList.remove(o);
197 public void clear() {
198 myArrayList.clear();
201 public boolean addAll(Collection collection) {
202 return myArrayList.addAll(collection);
205 public boolean addAll(int i, Collection collection) {
206 return myArrayList.addAll(i, collection);
209 public Iterator iterator() {
210 return myArrayList.iterator();
213 public ListIterator listIterator() {
214 return myArrayList.listIterator();
217 public ListIterator listIterator(int i) {
218 return myArrayList.listIterator(i);
221 public List subList(int i, int i1) {
222 return myArrayList.subList(i, i1);
225 public boolean containsAll(Collection collection) {
226 return myArrayList.containsAll(collection);
229 public boolean removeAll(Collection collection) {
230 return myArrayList.removeAll(collection);
233 public boolean retainAll(Collection collection) {
234 return myArrayList.retainAll(collection);
239 * Get the boolean value associated with an index.
240 * The string values "true" and "false" are converted to boolean.
242 * @param index The index must be between 0 and length() - 1.
243 * @return The truth.
244 * @throws JSONException If there is no value for the index or if the
245 * value is not convertable to boolean.
247 public boolean getBoolean(int index) throws JSONException {
248 Object o = get(index);
249 if (o.equals(Boolean.FALSE) ||
250 (o instanceof String &&
251 ((String) o).equalsIgnoreCase("false"))) {
252 return false;
253 } else if (o.equals(Boolean.TRUE) ||
254 (o instanceof String &&
255 ((String) o).equalsIgnoreCase("true"))) {
256 return true;
258 throw new JSONException("JSONArray[" + index + "] is not a Boolean.");
263 * Get the double value associated with an index.
265 * @param index The index must be between 0 and length() - 1.
266 * @return The value.
267 * @throws JSONException If the key is not found or if the value cannot
268 * be converted to a number.
270 public double getDouble(int index) throws JSONException {
271 Object o = get(index);
272 try {
273 return o instanceof Number ?
274 ((Number) o).doubleValue() : Double.parseDouble((String) o);
275 } catch (Exception e) {
276 throw new JSONException("JSONArray[" + index +
277 "] is not a number.");
283 * Get the int value associated with an index.
285 * @param index The index must be between 0 and length() - 1.
286 * @return The value.
287 * @throws JSONException If the key is not found or if the value cannot
288 * be converted to a number.
289 * if the value cannot be converted to a number.
291 public int getInt(int index) throws JSONException {
292 Object o = get(index);
293 return o instanceof Number ?
294 ((Number) o).intValue() : (int) getDouble(index);
299 * Get the JSONArray associated with an index.
301 * @param index The index must be between 0 and length() - 1.
302 * @return A JSONArray value.
303 * @throws JSONException If there is no value for the index. or if the
304 * value is not a JSONArray
306 public JSONArray getJSONArray(int index) throws JSONException {
307 Object o = get(index);
308 if (o instanceof JSONArray) {
309 return (JSONArray) o;
311 throw new JSONException("JSONArray[" + index +
312 "] is not a JSONArray.");
317 * Get the JSONObject associated with an index.
319 * @param index subscript
320 * @return A JSONObject value.
321 * @throws JSONException If there is no value for the index or if the
322 * value is not a JSONObject
324 public JSONObject getJSONObject(int index) throws JSONException {
325 Object o = get(index);
326 if (o instanceof JSONObject) {
327 return (JSONObject) o;
329 throw new JSONException("JSONArray[" + index +
330 "] is not a JSONObject.");
335 * Get the long value associated with an index.
337 * @param index The index must be between 0 and length() - 1.
338 * @return The value.
339 * @throws JSONException If the key is not found or if the value cannot
340 * be converted to a number.
342 public long getLong(int index) throws JSONException {
343 Object o = get(index);
344 return o instanceof Number ?
345 ((Number) o).longValue() : (long) getDouble(index);
350 * Get the string associated with an index.
352 * @param index The index must be between 0 and length() - 1.
353 * @return A string value.
354 * @throws JSONException If there is no value for the index.
356 public String getString(int index) throws JSONException {
357 return get(index).toString();
362 * Determine if the value is null.
364 * @param index The index must be between 0 and length() - 1.
365 * @return true if the value at the index is null, or if there is no value.
367 public boolean isNull(int index) {
368 return JSONObject.NULL.equals(opt(index));
373 * Make a string from the contents of this JSONArray. The
374 * <code>separator</code> string is inserted between each element.
375 * Warning: This method assumes that the data structure is acyclical.
377 * @param separator A string that will be inserted between the elements.
378 * @return a string.
379 * @throws JSONException If the array contains an invalid number.
381 public String join(String separator) throws JSONException {
382 int len = length();
383 StringBuffer sb = new StringBuffer();
385 for (int i = 0; i < len; i += 1) {
386 if (i > 0) {
387 sb.append(separator);
389 sb.append(JSONObject.valueToString(this.myArrayList.get(i)));
391 return sb.toString();
396 * Get the number of elements in the JSONArray, included nulls.
398 * @return The length (or size).
400 public int length() {
401 return this.myArrayList.size();
406 * Get the optional object value associated with an index.
408 * @param index The index must be between 0 and length() - 1.
409 * @return An object value, or null if there is no
410 * object at that index.
412 public Object opt(int index) {
413 return (index < 0 || index >= length()) ?
414 null : this.myArrayList.get(index);
419 * Get the optional boolean value associated with an index.
420 * It returns false if there is no value at that index,
421 * or if the value is not Boolean.TRUE or the String "true".
423 * @param index The index must be between 0 and length() - 1.
424 * @return The truth.
426 public boolean optBoolean(int index) {
427 return optBoolean(index, false);
432 * Get the optional boolean value associated with an index.
433 * It returns the defaultValue if there is no value at that index or if
434 * it is not a Boolean or the String "true" or "false" (case insensitive).
436 * @param index The index must be between 0 and length() - 1.
437 * @param defaultValue A boolean default.
438 * @return The truth.
440 public boolean optBoolean(int index, boolean defaultValue) {
441 try {
442 return getBoolean(index);
443 } catch (Exception e) {
444 return defaultValue;
450 * Get the optional double value associated with an index.
451 * NaN is returned if there is no value for the index,
452 * or if the value is not a number and cannot be converted to a number.
454 * @param index The index must be between 0 and length() - 1.
455 * @return The value.
457 public double optDouble(int index) {
458 return optDouble(index, Double.NaN);
463 * Get the optional double value associated with an index.
464 * The defaultValue is returned if there is no value for the index,
465 * or if the value is not a number and cannot be converted to a number.
467 * @param index subscript
468 * @param defaultValue The default value.
469 * @return The value.
471 public double optDouble(int index, double defaultValue) {
472 try {
473 return getDouble(index);
474 } catch (Exception e) {
475 return defaultValue;
481 * Get the optional int value associated with an index.
482 * Zero is returned if there is no value for the index,
483 * or if the value is not a number and cannot be converted to a number.
485 * @param index The index must be between 0 and length() - 1.
486 * @return The value.
488 public int optInt(int index) {
489 return optInt(index, 0);
494 * Get the optional int value associated with an index.
495 * The defaultValue is returned if there is no value for the index,
496 * or if the value is not a number and cannot be converted to a number.
498 * @param index The index must be between 0 and length() - 1.
499 * @param defaultValue The default value.
500 * @return The value.
502 public int optInt(int index, int defaultValue) {
503 try {
504 return getInt(index);
505 } catch (Exception e) {
506 return defaultValue;
512 * Get the optional JSONArray associated with an index.
514 * @param index subscript
515 * @return A JSONArray value, or null if the index has no value,
516 * or if the value is not a JSONArray.
518 public JSONArray optJSONArray(int index) {
519 Object o = opt(index);
520 return o instanceof JSONArray ? (JSONArray) o : null;
525 * Get the optional JSONObject associated with an index.
526 * Null is returned if the key is not found, or null if the index has
527 * no value, or if the value is not a JSONObject.
529 * @param index The index must be between 0 and length() - 1.
530 * @return A JSONObject value.
532 public JSONObject optJSONObject(int index) {
533 Object o = opt(index);
534 return o instanceof JSONObject ? (JSONObject) o : null;
539 * Get the optional long value associated with an index.
540 * Zero is returned if there is no value for the index,
541 * or if the value is not a number and cannot be converted to a number.
543 * @param index The index must be between 0 and length() - 1.
544 * @return The value.
546 public long optLong(int index) {
547 return optLong(index, 0);
552 * Get the optional long value associated with an index.
553 * The defaultValue is returned if there is no value for the index,
554 * or if the value is not a number and cannot be converted to a number.
556 * @param index The index must be between 0 and length() - 1.
557 * @param defaultValue The default value.
558 * @return The value.
560 public long optLong(int index, long defaultValue) {
561 try {
562 return getLong(index);
563 } catch (Exception e) {
564 return defaultValue;
570 * Get the optional string value associated with an index. It returns an
571 * empty string if there is no value at that index. If the value
572 * is not a string and is not null, then it is coverted to a string.
574 * @param index The index must be between 0 and length() - 1.
575 * @return A String value.
577 public String optString(int index) {
578 return optString(index, "");
583 * Get the optional string associated with an index.
584 * The defaultValue is returned if the key is not found.
586 * @param index The index must be between 0 and length() - 1.
587 * @param defaultValue The default value.
588 * @return A String value.
590 public String optString(int index, String defaultValue) {
591 Object o = opt(index);
592 return o != null ? o.toString() : defaultValue;
597 * Append a boolean value. This increases the array's length by one.
599 * @param value A boolean value.
600 * @return this.
602 public JSONArray put(boolean value) {
603 put(value ? Boolean.TRUE : Boolean.FALSE);
604 return this;
609 * Append a double value. This increases the array's length by one.
611 * @param value A double value.
612 * @return this.
613 * @throws JSONException if the value is not finite.
615 public JSONArray put(double value) throws JSONException {
616 Double d = new Double(value);
617 JSONObject.testValidity(d);
618 put(d);
619 return this;
624 * Append an int value. This increases the array's length by one.
626 * @param value An int value.
627 * @return this.
629 public JSONArray put(int value) {
630 put(new Integer(value));
631 return this;
636 * Append an long value. This increases the array's length by one.
638 * @param value A long value.
639 * @return this.
641 public JSONArray put(long value) {
642 put(new Long(value));
643 return this;
648 * Append an object value. This increases the array's length by one.
650 * @param value An object value. The value should be a
651 * Boolean, Double, Integer, JSONArray, JSObject, Long, or String, or the
652 * JSONObject.NULL object.
653 * @return this.
655 public JSONArray put(Object value) {
656 this.myArrayList.add(value);
657 return this;
662 * Put or replace a boolean value in the JSONArray. If the index is greater
663 * than the length of the JSONArray, then null elements will be added as
664 * necessary to pad it out.
666 * @param index The subscript.
667 * @param value A boolean value.
668 * @return this.
669 * @throws JSONException If the index is negative.
671 public JSONArray put(int index, boolean value) throws JSONException {
672 put(index, value ? Boolean.TRUE : Boolean.FALSE);
673 return this;
678 * Put or replace a double value. If the index is greater than the length of
679 * the JSONArray, then null elements will be added as necessary to pad
680 * it out.
682 * @param index The subscript.
683 * @param value A double value.
684 * @return this.
685 * @throws JSONException If the index is negative or if the value is
686 * not finite.
688 public JSONArray put(int index, double value) throws JSONException {
689 put(index, new Double(value));
690 return this;
695 * Put or replace an int value. If the index is greater than the length of
696 * the JSONArray, then null elements will be added as necessary to pad
697 * it out.
699 * @param index The subscript.
700 * @param value An int value.
701 * @return this.
702 * @throws JSONException If the index is negative.
704 public JSONArray put(int index, int value) throws JSONException {
705 put(index, new Integer(value));
706 return this;
711 * Put or replace a long value. If the index is greater than the length of
712 * the JSONArray, then null elements will be added as necessary to pad
713 * it out.
715 * @param index The subscript.
716 * @param value A long value.
717 * @return this.
718 * @throws JSONException If the index is negative.
720 public JSONArray put(int index, long value) throws JSONException {
721 put(index, new Long(value));
722 return this;
727 * Put or replace an object value in the JSONArray. If the index is greater
728 * than the length of the JSONArray, then null elements will be added as
729 * necessary to pad it out.
731 * @param index The subscript.
732 * @param value The value to put into the array.
733 * @return this.
734 * @throws JSONException If the index is negative or if the the value is
735 * an invalid number.
737 public JSONArray put(int index, Object value) throws JSONException {
738 JSONObject.testValidity(value);
739 if (index < 0) {
740 throw new JSONException("JSONArray[" + index + "] not found.");
742 if (index < length()) {
743 this.myArrayList.set(index, value);
744 } else {
745 while (index != length()) {
746 put(null);
748 put(value);
750 return this;
755 * Produce a JSONObject by combining a JSONArray of names with the values
756 * of this JSONArray.
758 * @param names A JSONArray containing a list of key strings. These will be
759 * paired with the values.
760 * @return A JSONObject, or null if there are no names or if this JSONArray
761 * has no values.
762 * @throws JSONException If any of the names are null.
764 public JSONObject toJSONObject(JSONArray names) throws JSONException {
765 if (names == null || names.length() == 0 || length() == 0) {
766 return null;
768 JSONObject jo = new JSONObject();
769 for (int i = 0; i < names.length(); i += 1) {
770 jo.put(names.getString(i), this.opt(i));
772 return jo;
777 * Make an JSON text of this JSONArray. For compactness, no
778 * unnecessary whitespace is added. If it is not possible to produce a
779 * syntactically correct JSON text then null will be returned instead. This
780 * could occur if the array contains an invalid number.
781 * <p/>
782 * Warning: This method assumes that the data structure is acyclical.
784 * @return a printable, displayable, transmittable
785 * representation of the array.
787 public String toString() {
788 try {
789 return '[' + join(",") + ']';
790 } catch (Exception e) {
791 return null;
797 * Make a prettyprinted JSON text of this JSONArray.
798 * Warning: This method assumes that the data structure is acyclical.
800 * @param indentFactor The number of spaces to add to each level of
801 * indentation.
802 * @return a printable, displayable, transmittable
803 * representation of the object, beginning
804 * with <code>[</code>&nbsp;<small>(left bracket)</small> and ending
805 * with <code>]</code>&nbsp;<small>(right bracket)</small>.
806 * @throws JSONException
808 public String toString(int indentFactor) throws JSONException {
809 return toString(indentFactor, 0);
814 * Make a prettyprinted JSON text of this JSONArray.
815 * Warning: This method assumes that the data structure is acyclical.
817 * @param indentFactor The number of spaces to add to each level of
818 * indentation.
819 * @param indent The indention of the top level.
820 * @return a printable, displayable, transmittable
821 * representation of the array.
822 * @throws JSONException
824 String toString(int indentFactor, int indent) throws JSONException {
825 int len = length();
826 if (len == 0) {
827 return "[]";
829 int i;
830 StringBuffer sb = new StringBuffer("[");
831 if (len == 1) {
832 sb.append(JSONObject.valueToString(this.myArrayList.get(0),
833 indentFactor, indent));
834 } else {
835 int newindent = indent + indentFactor;
836 sb.append('\n');
837 for (i = 0; i < len; i += 1) {
838 if (i > 0) {
839 sb.append(",\n");
841 for (int j = 0; j < newindent; j += 1) {
842 sb.append(' ');
844 sb.append(JSONObject.valueToString(this.myArrayList.get(i),
845 indentFactor, newindent));
847 sb.append('\n');
848 for (i = 0; i < indent; i += 1) {
849 sb.append(' ');
852 sb.append(']');
853 return sb.toString();
858 * Write the contents of the JSONArray as JSON text to a writer.
859 * For compactness, no whitespace is added.
860 * <p/>
861 * Warning: This method assumes that the data structure is acyclical.
863 * @return The writer.
864 * @throws JSONException
866 public Writer write(Writer writer) throws JSONException {
867 try {
868 boolean b = false;
869 int len = length();
871 writer.write('[');
873 for (int i = 0; i < len; i += 1) {
874 if (b) {
875 writer.write(',');
877 Object v = this.myArrayList.get(i);
878 if (v instanceof JSONObject) {
879 ((JSONObject) v).write(writer);
880 } else if (v instanceof JSONArray) {
881 ((JSONArray) v).write(writer);
882 } else {
883 writer.write(JSONObject.valueToString(v));
885 b = true;
887 writer.write(']');
888 return writer;
889 } catch (IOException e) {
890 throw new JSONException(e);
894 public void trimToSize() {
895 myArrayList.trimToSize();
898 public void ensureCapacity(int i) {
899 myArrayList.ensureCapacity(i);
902 public int size() {
903 return myArrayList.size();
906 public boolean isEmpty() {
907 return myArrayList.isEmpty();
910 public boolean contains(Object o) {
911 return myArrayList.contains(o);
914 public int indexOf(Object o) {
915 return myArrayList.indexOf(o);
918 public int lastIndexOf(Object o) {
919 return myArrayList.lastIndexOf(o);
922 public Object clone() {
923 return myArrayList.clone();
926 public Object[] toArray() {
927 return myArrayList.toArray();
930 public Object[] toArray(Object[] objects) {
931 return myArrayList.toArray(objects);