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> <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> <small>(comma)</small>
057 * elision.</li>
058 * <li>Strings may be quoted with <code>'</code> <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> <small>(left
140 * bracket)</small> and ends with <code>]</code> <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> <small>(left
743 * bracket)</small> and ending with <code>]</code> <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 }