0001: package org.jboss.portal.widget.netvibes.json;
0002:
0003: /*
0004: Copyright (c) 2002 JSON.org
0005:
0006: Permission is hereby granted, free of charge, to any person obtaining a copy
0007: of this software and associated documentation files (the "Software"), to deal
0008: in the Software without restriction, including without limitation the rights
0009: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0010: copies of the Software, and to permit persons to whom the Software is
0011: furnished to do so, subject to the following conditions:
0012:
0013: The above copyright notice and this permission notice shall be included in all
0014: copies or substantial portions of the Software.
0015:
0016: The Software shall be used for Good, not Evil.
0017:
0018: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0019: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0020: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0021: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0022: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0023: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
0024: SOFTWARE.
0025: */
0026:
0027: import java.io.IOException;
0028: import java.io.Writer;
0029: import java.util.Collection;
0030: import java.lang.reflect.Field;
0031: import java.lang.reflect.Method;
0032: import java.util.HashMap;
0033: import java.util.Iterator;
0034: import java.util.Map;
0035:
0036: /**
0037: * A JSONObject is an unordered collection of name/value pairs. Its
0038: * external form is a string wrapped in curly braces with colons between the
0039: * names and values, and commas between the values and names. The internal form
0040: * is an object having <code>get</code> and <code>opt</code> methods for
0041: * accessing the values by name, and <code>put</code> methods for adding or
0042: * replacing values by name. The values can be any of these types:
0043: * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
0044: * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code>
0045: * object. A JSONObject constructor can be used to convert an external form
0046: * JSON text into an internal form whose values can be retrieved with the
0047: * <code>get</code> and <code>opt</code> methods, or to convert values into a
0048: * JSON text using the <code>put</code> and <code>toString</code> methods.
0049: * A <code>get</code> method returns a value if one can be found, and throws an
0050: * exception if one cannot be found. An <code>opt</code> method returns a
0051: * default value instead of throwing an exception, and so is useful for
0052: * obtaining optional values.
0053: * <p>
0054: * The generic <code>get()</code> and <code>opt()</code> methods return an
0055: * object, which you can cast or query for type. There are also typed
0056: * <code>get</code> and <code>opt</code> methods that do type checking and type
0057: * coersion for you.
0058: * <p>
0059: * The <code>put</code> methods adds values to an object. For example, <pre>
0060: * myString = new JSONObject().put("JSON", "Hello, World!").toString();</pre>
0061: * produces the string <code>{"JSON": "Hello, World"}</code>.
0062: * <p>
0063: * The texts produced by the <code>toString</code> methods strictly conform to
0064: * the JSON sysntax rules.
0065: * The constructors are more forgiving in the texts they will accept:
0066: * <ul>
0067: * <li>An extra <code>,</code> <small>(comma)</small> may appear just
0068: * before the closing brace.</li>
0069: * <li>Strings may be quoted with <code>'</code> <small>(single
0070: * quote)</small>.</li>
0071: * <li>Strings do not need to be quoted at all if they do not begin with a quote
0072: * or single quote, and if they do not contain leading or trailing spaces,
0073: * and if they do not contain any of these characters:
0074: * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
0075: * and if they are not the reserved words <code>true</code>,
0076: * <code>false</code>, or <code>null</code>.</li>
0077: * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as
0078: * by <code>:</code>.</li>
0079: * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as
0080: * well as by <code>,</code> <small>(comma)</small>.</li>
0081: * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
0082: * <code>0x-</code> <small>(hex)</small> prefix.</li>
0083: * <li>Comments written in the slashshlash, slashstar, and hash conventions
0084: * will be ignored.</li>
0085: * </ul>
0086: * @author JSON.org
0087: * @version 2
0088: */
0089: public class JSONObject {
0090:
0091: /**
0092: * JSONObject.NULL is equivalent to the value that JavaScript calls null,
0093: * whilst Java's null is equivalent to the value that JavaScript calls
0094: * undefined.
0095: */
0096: private static final class Null {
0097:
0098: /**
0099: * There is only intended to be a single instance of the NULL object,
0100: * so the clone method returns itself.
0101: * @return NULL.
0102: */
0103: protected final Object clone() {
0104: return this ;
0105: }
0106:
0107: /**
0108: * A Null object is equal to the null value and to itself.
0109: * @param object An object to test for nullness.
0110: * @return true if the object parameter is the JSONObject.NULL object
0111: * or null.
0112: */
0113: public boolean equals(Object object) {
0114: return object == null || object == this ;
0115: }
0116:
0117: /**
0118: * Get the "null" string value.
0119: * @return The string "null".
0120: */
0121: public String toString() {
0122: return "null";
0123: }
0124: }
0125:
0126: /**
0127: * The hash map where the JSONObject's properties are kept.
0128: */
0129: private HashMap myHashMap;
0130:
0131: /**
0132: * It is sometimes more convenient and less ambiguous to have a
0133: * <code>NULL</code> object than to use Java's <code>null</code> value.
0134: * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
0135: * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
0136: */
0137: public static final Object NULL = new Null();
0138:
0139: /**
0140: * Construct an empty JSONObject.
0141: */
0142: public JSONObject() {
0143: this .myHashMap = new HashMap();
0144: }
0145:
0146: /**
0147: * Construct a JSONObject from a subset of another JSONObject.
0148: * An array of strings is used to identify the keys that should be copied.
0149: * Missing keys are ignored.
0150: * @param jo A JSONObject.
0151: * @param names An array of strings.
0152: * @exception JSONException If a value is a non-finite number.
0153: */
0154: public JSONObject(JSONObject jo, String[] names)
0155: throws JSONException {
0156: this ();
0157: for (int i = 0; i < names.length; i += 1) {
0158: putOpt(names[i], jo.opt(names[i]));
0159: }
0160: }
0161:
0162: /**
0163: * Construct a JSONObject from a JSONTokener.
0164: * @param x A JSONTokener object containing the source string.
0165: * @throws JSONException If there is a syntax error in the source string.
0166: */
0167: public JSONObject(JSONTokener x) throws JSONException {
0168: this ();
0169: char c;
0170: String key;
0171:
0172: if (x.nextClean() != '{') {
0173: throw x
0174: .syntaxError("A JSONObject text must begin with '{'");
0175: }
0176: for (;;) {
0177: c = x.nextClean();
0178: switch (c) {
0179: case 0:
0180: throw x
0181: .syntaxError("A JSONObject text must end with '}'");
0182: case '}':
0183: return;
0184: default:
0185: x.back();
0186: key = x.nextValue().toString();
0187: }
0188:
0189: /*
0190: * The key is followed by ':'. We will also tolerate '=' or '=>'.
0191: */
0192:
0193: c = x.nextClean();
0194: if (c == '=') {
0195: if (x.next() != '>') {
0196: x.back();
0197: }
0198: } else if (c != ':') {
0199: throw x.syntaxError("Expected a ':' after a key");
0200: }
0201: put(key, x.nextValue());
0202:
0203: /*
0204: * Pairs are separated by ','. We will also tolerate ';'.
0205: */
0206:
0207: switch (x.nextClean()) {
0208: case ';':
0209: case ',':
0210: if (x.nextClean() == '}') {
0211: return;
0212: }
0213: x.back();
0214: break;
0215: case '}':
0216: return;
0217: default:
0218: throw x.syntaxError("Expected a ',' or '}'");
0219: }
0220: }
0221: }
0222:
0223: /**
0224: * Construct a JSONObject from a Map.
0225: * @param map A map object that can be used to initialize the contents of
0226: * the JSONObject.
0227: */
0228: public JSONObject(Map map) {
0229: this .myHashMap = (map == null) ? new HashMap() : new HashMap(
0230: map);
0231: }
0232:
0233: /**
0234: * Construct a JSONObject from an Object using bean getters.
0235: * It reflects on all of the public methods of the object.
0236: * For each of the methods with no parameters and a name starting
0237: * with <code>"get"</code> or <code>"is"</code> followed by an uppercase letter,
0238: * the method is invoked, and a key and the value returned from the getter method
0239: * are put into the new JSONObject.
0240: *
0241: * The key is formed by removing the <code>"get"</code> or <code>"is"</code> prefix. If the second remaining
0242: * character is not upper case, then the first
0243: * character is converted to lower case.
0244: *
0245: * For example, if an object has a method named <code>"getName"</code>, and
0246: * if the result of calling <code>object.getName()</code> is <code>"Larry Fine"</code>,
0247: * then the JSONObject will contain <code>"name": "Larry Fine"</code>.
0248: *
0249: * @param bean An object that has getter methods that should be used
0250: * to make a JSONObject.
0251: */
0252: public JSONObject(Object bean) {
0253: this ();
0254: Class klass = bean.getClass();
0255: Method[] methods = klass.getMethods();
0256: for (int i = 0; i < methods.length; i += 1) {
0257: try {
0258: Method method = methods[i];
0259: String name = method.getName();
0260: String key = "";
0261: if (name.startsWith("get")) {
0262: key = name.substring(3);
0263: } else if (name.startsWith("is")) {
0264: key = name.substring(2);
0265: }
0266: if (key.length() > 0
0267: && Character.isUpperCase(key.charAt(0))
0268: && method.getParameterTypes().length == 0) {
0269: if (key.length() == 1) {
0270: key = key.toLowerCase();
0271: } else if (!Character.isUpperCase(key.charAt(1))) {
0272: key = key.substring(0, 1).toLowerCase()
0273: + key.substring(1);
0274: }
0275: this .put(key, method.invoke(bean, null));
0276: }
0277: } catch (Exception e) {
0278: /* forget about it */
0279: }
0280: }
0281: }
0282:
0283: /**
0284: * Construct a JSONObject from an Object, using reflection to find the
0285: * public members. The resulting JSONObject's keys will be the strings
0286: * from the names array, and the values will be the field values associated
0287: * with those keys in the object. If a key is not found or not visible,
0288: * then it will not be copied into the new JSONObject.
0289: * @param object An object that has fields that should be used to make a
0290: * JSONObject.
0291: * @param names An array of strings, the names of the fields to be obtained
0292: * from the object.
0293: */
0294: public JSONObject(Object object, String names[]) {
0295: this ();
0296: Class c = object.getClass();
0297: for (int i = 0; i < names.length; i += 1) {
0298: String name = names[i];
0299: try {
0300: Field field = c.getField(name);
0301: Object value = field.get(object);
0302: this .put(name, value);
0303: } catch (Exception e) {
0304: /* forget about it */
0305: }
0306: }
0307: }
0308:
0309: /**
0310: * Construct a JSONObject from a source JSON text string.
0311: * This is the most commonly used JSONObject constructor.
0312: * @param source A string beginning
0313: * with <code>{</code> <small>(left brace)</small> and ending
0314: * with <code>}</code> <small>(right brace)</small>.
0315: * @exception JSONException If there is a syntax error in the source string.
0316: */
0317: public JSONObject(String source) throws JSONException {
0318: this (new JSONTokener(source));
0319: }
0320:
0321: /**
0322: * Accumulate values under a key. It is similar to the put method except
0323: * that if there is already an object stored under the key then a
0324: * JSONArray is stored under the key to hold all of the accumulated values.
0325: * If there is already a JSONArray, then the new value is appended to it.
0326: * In contrast, the put method replaces the previous value.
0327: * @param key A key string.
0328: * @param value An object to be accumulated under the key.
0329: * @return this.
0330: * @throws JSONException If the value is an invalid number
0331: * or if the key is null.
0332: */
0333: public JSONObject accumulate(String key, Object value)
0334: throws JSONException {
0335: testValidity(value);
0336: Object o = opt(key);
0337: if (o == null) {
0338: put(key, value instanceof JSONArray ? new JSONArray()
0339: .put(value) : value);
0340: } else if (o instanceof JSONArray) {
0341: ((JSONArray) o).put(value);
0342: } else {
0343: put(key, new JSONArray().put(o).put(value));
0344: }
0345: return this ;
0346: }
0347:
0348: /**
0349: * Append values to the array under a key. If the key does not exist in the
0350: * JSONObject, then the key is put in the JSONObject with its value being a
0351: * JSONArray containing the value parameter. If the key was already
0352: * associated with a JSONArray, then the value parameter is appended to it.
0353: * @param key A key string.
0354: * @param value An object to be accumulated under the key.
0355: * @return this.
0356: * @throws JSONException If the key is null or if the current value
0357: * associated with the key is not a JSONArray.
0358: */
0359: public JSONObject append(String key, Object value)
0360: throws JSONException {
0361: testValidity(value);
0362: Object o = opt(key);
0363: if (o == null) {
0364: put(key, new JSONArray().put(value));
0365: } else if (o instanceof JSONArray) {
0366: put(key, ((JSONArray) o).put(value));
0367: } else {
0368: throw new JSONException("JSONObject[" + key
0369: + "] is not a JSONArray.");
0370: }
0371: return this ;
0372: }
0373:
0374: /**
0375: * Produce a string from a double. The string "null" will be returned if
0376: * the number is not finite.
0377: * @param d A double.
0378: * @return A String.
0379: */
0380: static public String doubleToString(double d) {
0381: if (Double.isInfinite(d) || Double.isNaN(d)) {
0382: return "null";
0383: }
0384:
0385: // Shave off trailing zeros and decimal point, if possible.
0386:
0387: String s = Double.toString(d);
0388: if (s.indexOf('.') > 0 && s.indexOf('e') < 0
0389: && s.indexOf('E') < 0) {
0390: while (s.endsWith("0")) {
0391: s = s.substring(0, s.length() - 1);
0392: }
0393: if (s.endsWith(".")) {
0394: s = s.substring(0, s.length() - 1);
0395: }
0396: }
0397: return s;
0398: }
0399:
0400: /**
0401: * Get the value object associated with a key.
0402: *
0403: * @param key A key string.
0404: * @return The object associated with the key.
0405: * @throws JSONException if the key is not found.
0406: */
0407: public Object get(String key) throws JSONException {
0408: Object o = opt(key);
0409: if (o == null) {
0410: throw new JSONException("JSONObject[" + quote(key)
0411: + "] not found.");
0412: }
0413: return o;
0414: }
0415:
0416: /**
0417: * Get the boolean value associated with a key.
0418: *
0419: * @param key A key string.
0420: * @return The truth.
0421: * @throws JSONException
0422: * if the value is not a Boolean or the String "true" or "false".
0423: */
0424: public boolean getBoolean(String key) throws JSONException {
0425: Object o = get(key);
0426: if (o.equals(Boolean.FALSE)
0427: || (o instanceof String && ((String) o)
0428: .equalsIgnoreCase("false"))) {
0429: return false;
0430: } else if (o.equals(Boolean.TRUE)
0431: || (o instanceof String && ((String) o)
0432: .equalsIgnoreCase("true"))) {
0433: return true;
0434: }
0435: throw new JSONException("JSONObject[" + quote(key)
0436: + "] is not a Boolean.");
0437: }
0438:
0439: /**
0440: * Get the double value associated with a key.
0441: * @param key A key string.
0442: * @return The numeric value.
0443: * @throws JSONException if the key is not found or
0444: * if the value is not a Number object and cannot be converted to a number.
0445: */
0446: public double getDouble(String key) throws JSONException {
0447: Object o = get(key);
0448: try {
0449: return o instanceof Number ? ((Number) o).doubleValue()
0450: : Double.valueOf((String) o).doubleValue();
0451: } catch (Exception e) {
0452: throw new JSONException("JSONObject[" + quote(key)
0453: + "] is not a number.");
0454: }
0455: }
0456:
0457: /**
0458: * Get the int value associated with a key. If the number value is too
0459: * large for an int, it will be clipped.
0460: *
0461: * @param key A key string.
0462: * @return The integer value.
0463: * @throws JSONException if the key is not found or if the value cannot
0464: * be converted to an integer.
0465: */
0466: public int getInt(String key) throws JSONException {
0467: Object o = get(key);
0468: return o instanceof Number ? ((Number) o).intValue()
0469: : (int) getDouble(key);
0470: }
0471:
0472: /**
0473: * Get the JSONArray value associated with a key.
0474: *
0475: * @param key A key string.
0476: * @return A JSONArray which is the value.
0477: * @throws JSONException if the key is not found or
0478: * if the value is not a JSONArray.
0479: */
0480: public JSONArray getJSONArray(String key) throws JSONException {
0481: Object o = get(key);
0482: if (o instanceof JSONArray) {
0483: return (JSONArray) o;
0484: }
0485: throw new JSONException("JSONObject[" + quote(key)
0486: + "] is not a JSONArray.");
0487: }
0488:
0489: /**
0490: * Get the JSONObject value associated with a key.
0491: *
0492: * @param key A key string.
0493: * @return A JSONObject which is the value.
0494: * @throws JSONException if the key is not found or
0495: * if the value is not a JSONObject.
0496: */
0497: public JSONObject getJSONObject(String key) throws JSONException {
0498: Object o = get(key);
0499: if (o instanceof JSONObject) {
0500: return (JSONObject) o;
0501: }
0502: throw new JSONException("JSONObject[" + quote(key)
0503: + "] is not a JSONObject.");
0504: }
0505:
0506: /**
0507: * Get the long value associated with a key. If the number value is too
0508: * long for a long, it will be clipped.
0509: *
0510: * @param key A key string.
0511: * @return The long value.
0512: * @throws JSONException if the key is not found or if the value cannot
0513: * be converted to a long.
0514: */
0515: public long getLong(String key) throws JSONException {
0516: Object o = get(key);
0517: return o instanceof Number ? ((Number) o).longValue()
0518: : (long) getDouble(key);
0519: }
0520:
0521: /**
0522: * Get an array of field names from a JSONObject.
0523: *
0524: * @return An array of field names, or null if there are no names.
0525: */
0526: public static String[] getNames(JSONObject jo) {
0527: int length = jo.length();
0528: if (length == 0) {
0529: return null;
0530: }
0531: Iterator i = jo.keys();
0532: String[] names = new String[length];
0533: int j = 0;
0534: while (i.hasNext()) {
0535: names[j] = (String) i.next();
0536: j += 1;
0537: }
0538: return names;
0539: }
0540:
0541: /**
0542: * Get an array of field names from an Object.
0543: *
0544: * @return An array of field names, or null if there are no names.
0545: */
0546: public static String[] getNames(Object object) {
0547: if (object == null) {
0548: return null;
0549: }
0550: Class klass = object.getClass();
0551: Field[] fields = klass.getFields();
0552: int length = fields.length;
0553: if (length == 0) {
0554: return null;
0555: }
0556: String[] names = new String[length];
0557: for (int i = 0; i < length; i += 1) {
0558: names[i] = fields[i].getName();
0559: }
0560: return names;
0561: }
0562:
0563: /**
0564: * Get the string associated with a key.
0565: *
0566: * @param key A key string.
0567: * @return A string which is the value.
0568: * @throws JSONException if the key is not found.
0569: */
0570: public String getString(String key) throws JSONException {
0571: return get(key).toString();
0572: }
0573:
0574: /**
0575: * Determine if the JSONObject contains a specific key.
0576: * @param key A key string.
0577: * @return true if the key exists in the JSONObject.
0578: */
0579: public boolean has(String key) {
0580: return this .myHashMap.containsKey(key);
0581: }
0582:
0583: /**
0584: * Determine if the value associated with the key is null or if there is
0585: * no value.
0586: * @param key A key string.
0587: * @return true if there is no value associated with the key or if
0588: * the value is the JSONObject.NULL object.
0589: */
0590: public boolean isNull(String key) {
0591: return JSONObject.NULL.equals(opt(key));
0592: }
0593:
0594: /**
0595: * Get an enumeration of the keys of the JSONObject.
0596: *
0597: * @return An iterator of the keys.
0598: */
0599: public Iterator keys() {
0600: return this .myHashMap.keySet().iterator();
0601: }
0602:
0603: /**
0604: * Get the number of keys stored in the JSONObject.
0605: *
0606: * @return The number of keys in the JSONObject.
0607: */
0608: public int length() {
0609: return this .myHashMap.size();
0610: }
0611:
0612: /**
0613: * Produce a JSONArray containing the names of the elements of this
0614: * JSONObject.
0615: * @return A JSONArray containing the key strings, or null if the JSONObject
0616: * is empty.
0617: */
0618: public JSONArray names() {
0619: JSONArray ja = new JSONArray();
0620: Iterator keys = keys();
0621: while (keys.hasNext()) {
0622: ja.put(keys.next());
0623: }
0624: return ja.length() == 0 ? null : ja;
0625: }
0626:
0627: /**
0628: * Produce a string from a Number.
0629: * @param n A Number
0630: * @return A String.
0631: * @throws JSONException If n is a non-finite number.
0632: */
0633: static public String numberToString(Number n) throws JSONException {
0634: if (n == null) {
0635: throw new JSONException("Null pointer");
0636: }
0637: testValidity(n);
0638:
0639: // Shave off trailing zeros and decimal point, if possible.
0640:
0641: String s = n.toString();
0642: if (s.indexOf('.') > 0 && s.indexOf('e') < 0
0643: && s.indexOf('E') < 0) {
0644: while (s.endsWith("0")) {
0645: s = s.substring(0, s.length() - 1);
0646: }
0647: if (s.endsWith(".")) {
0648: s = s.substring(0, s.length() - 1);
0649: }
0650: }
0651: return s;
0652: }
0653:
0654: /**
0655: * Get an optional value associated with a key.
0656: * @param key A key string.
0657: * @return An object which is the value, or null if there is no value.
0658: */
0659: public Object opt(String key) {
0660: return key == null ? null : this .myHashMap.get(key);
0661: }
0662:
0663: /**
0664: * Get an optional boolean associated with a key.
0665: * It returns false if there is no such key, or if the value is not
0666: * Boolean.TRUE or the String "true".
0667: *
0668: * @param key A key string.
0669: * @return The truth.
0670: */
0671: public boolean optBoolean(String key) {
0672: return optBoolean(key, false);
0673: }
0674:
0675: /**
0676: * Get an optional boolean associated with a key.
0677: * It returns the defaultValue if there is no such key, or if it is not
0678: * a Boolean or the String "true" or "false" (case insensitive).
0679: *
0680: * @param key A key string.
0681: * @param defaultValue The default.
0682: * @return The truth.
0683: */
0684: public boolean optBoolean(String key, boolean defaultValue) {
0685: try {
0686: return getBoolean(key);
0687: } catch (Exception e) {
0688: return defaultValue;
0689: }
0690: }
0691:
0692: /**
0693: * Put a key/value pair in the JSONObject, where the value will be a
0694: * JSONArray which is produced from a Collection.
0695: * @param key A key string.
0696: * @param value A Collection value.
0697: * @return this.
0698: * @throws JSONException
0699: */
0700: public JSONObject put(String key, Collection value)
0701: throws JSONException {
0702: put(key, new JSONArray(value));
0703: return this ;
0704: }
0705:
0706: /**
0707: * Get an optional double associated with a key,
0708: * or NaN if there is no such key or if its value is not a number.
0709: * If the value is a string, an attempt will be made to evaluate it as
0710: * a number.
0711: *
0712: * @param key A string which is the key.
0713: * @return An object which is the value.
0714: */
0715: public double optDouble(String key) {
0716: return optDouble(key, Double.NaN);
0717: }
0718:
0719: /**
0720: * Get an optional double associated with a key, or the
0721: * defaultValue if there is no such key or if its value is not a number.
0722: * If the value is a string, an attempt will be made to evaluate it as
0723: * a number.
0724: *
0725: * @param key A key string.
0726: * @param defaultValue The default.
0727: * @return An object which is the value.
0728: */
0729: public double optDouble(String key, double defaultValue) {
0730: try {
0731: Object o = opt(key);
0732: return o instanceof Number ? ((Number) o).doubleValue()
0733: : new Double((String) o).doubleValue();
0734: } catch (Exception e) {
0735: return defaultValue;
0736: }
0737: }
0738:
0739: /**
0740: * Get an optional int value associated with a key,
0741: * or zero if there is no such key or if the value is not a number.
0742: * If the value is a string, an attempt will be made to evaluate it as
0743: * a number.
0744: *
0745: * @param key A key string.
0746: * @return An object which is the value.
0747: */
0748: public int optInt(String key) {
0749: return optInt(key, 0);
0750: }
0751:
0752: /**
0753: * Get an optional int value associated with a key,
0754: * or the default if there is no such key or if the value is not a number.
0755: * If the value is a string, an attempt will be made to evaluate it as
0756: * a number.
0757: *
0758: * @param key A key string.
0759: * @param defaultValue The default.
0760: * @return An object which is the value.
0761: */
0762: public int optInt(String key, int defaultValue) {
0763: try {
0764: return getInt(key);
0765: } catch (Exception e) {
0766: return defaultValue;
0767: }
0768: }
0769:
0770: /**
0771: * Get an optional JSONArray associated with a key.
0772: * It returns null if there is no such key, or if its value is not a
0773: * JSONArray.
0774: *
0775: * @param key A key string.
0776: * @return A JSONArray which is the value.
0777: */
0778: public JSONArray optJSONArray(String key) {
0779: Object o = opt(key);
0780: return o instanceof JSONArray ? (JSONArray) o : null;
0781: }
0782:
0783: /**
0784: * Get an optional JSONObject associated with a key.
0785: * It returns null if there is no such key, or if its value is not a
0786: * JSONObject.
0787: *
0788: * @param key A key string.
0789: * @return A JSONObject which is the value.
0790: */
0791: public JSONObject optJSONObject(String key) {
0792: Object o = opt(key);
0793: return o instanceof JSONObject ? (JSONObject) o : null;
0794: }
0795:
0796: /**
0797: * Get an optional long value associated with a key,
0798: * or zero if there is no such key or if the value is not a number.
0799: * If the value is a string, an attempt will be made to evaluate it as
0800: * a number.
0801: *
0802: * @param key A key string.
0803: * @return An object which is the value.
0804: */
0805: public long optLong(String key) {
0806: return optLong(key, 0);
0807: }
0808:
0809: /**
0810: * Get an optional long value associated with a key,
0811: * or the default if there is no such key or if the value is not a number.
0812: * If the value is a string, an attempt will be made to evaluate it as
0813: * a number.
0814: *
0815: * @param key A key string.
0816: * @param defaultValue The default.
0817: * @return An object which is the value.
0818: */
0819: public long optLong(String key, long defaultValue) {
0820: try {
0821: return getLong(key);
0822: } catch (Exception e) {
0823: return defaultValue;
0824: }
0825: }
0826:
0827: /**
0828: * Get an optional string associated with a key.
0829: * It returns an empty string if there is no such key. If the value is not
0830: * a string and is not null, then it is coverted to a string.
0831: *
0832: * @param key A key string.
0833: * @return A string which is the value.
0834: */
0835: public String optString(String key) {
0836: return optString(key, "");
0837: }
0838:
0839: /**
0840: * Get an optional string associated with a key.
0841: * It returns the defaultValue if there is no such key.
0842: *
0843: * @param key A key string.
0844: * @param defaultValue The default.
0845: * @return A string which is the value.
0846: */
0847: public String optString(String key, String defaultValue) {
0848: Object o = opt(key);
0849: return o != null ? o.toString() : defaultValue;
0850: }
0851:
0852: /**
0853: * Put a key/boolean pair in the JSONObject.
0854: *
0855: * @param key A key string.
0856: * @param value A boolean which is the value.
0857: * @return this.
0858: * @throws JSONException If the key is null.
0859: */
0860: public JSONObject put(String key, boolean value)
0861: throws JSONException {
0862: put(key, value ? Boolean.TRUE : Boolean.FALSE);
0863: return this ;
0864: }
0865:
0866: /**
0867: * Put a key/double pair in the JSONObject.
0868: *
0869: * @param key A key string.
0870: * @param value A double which is the value.
0871: * @return this.
0872: * @throws JSONException If the key is null or if the number is invalid.
0873: */
0874: public JSONObject put(String key, double value)
0875: throws JSONException {
0876: put(key, new Double(value));
0877: return this ;
0878: }
0879:
0880: /**
0881: * Put a key/int pair in the JSONObject.
0882: *
0883: * @param key A key string.
0884: * @param value An int which is the value.
0885: * @return this.
0886: * @throws JSONException If the key is null.
0887: */
0888: public JSONObject put(String key, int value) throws JSONException {
0889: put(key, new Integer(value));
0890: return this ;
0891: }
0892:
0893: /**
0894: * Put a key/long pair in the JSONObject.
0895: *
0896: * @param key A key string.
0897: * @param value A long which is the value.
0898: * @return this.
0899: * @throws JSONException If the key is null.
0900: */
0901: public JSONObject put(String key, long value) throws JSONException {
0902: put(key, new Long(value));
0903: return this ;
0904: }
0905:
0906: /**
0907: * Put a key/value pair in the JSONObject, where the value will be a
0908: * JSONObject which is produced from a Map.
0909: * @param key A key string.
0910: * @param value A Map value.
0911: * @return this.
0912: * @throws JSONException
0913: */
0914: public JSONObject put(String key, Map value) throws JSONException {
0915: put(key, new JSONObject(value));
0916: return this ;
0917: }
0918:
0919: /**
0920: * Put a key/value pair in the JSONObject. If the value is null,
0921: * then the key will be removed from the JSONObject if it is present.
0922: * @param key A key string.
0923: * @param value An object which is the value. It should be of one of these
0924: * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
0925: * or the JSONObject.NULL object.
0926: * @return this.
0927: * @throws JSONException If the value is non-finite number
0928: * or if the key is null.
0929: */
0930: public JSONObject put(String key, Object value)
0931: throws JSONException {
0932: if (key == null) {
0933: throw new JSONException("Null key.");
0934: }
0935: if (value != null) {
0936: testValidity(value);
0937: this .myHashMap.put(key, value);
0938: } else {
0939: remove(key);
0940: }
0941: return this ;
0942: }
0943:
0944: /**
0945: * Put a key/value pair in the JSONObject, but only if the
0946: * key and the value are both non-null.
0947: * @param key A key string.
0948: * @param value An object which is the value. It should be of one of these
0949: * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
0950: * or the JSONObject.NULL object.
0951: * @return this.
0952: * @throws JSONException If the value is a non-finite number.
0953: */
0954: public JSONObject putOpt(String key, Object value)
0955: throws JSONException {
0956: if (key != null && value != null) {
0957: put(key, value);
0958: }
0959: return this ;
0960: }
0961:
0962: /**
0963: * Produce a string in double quotes with backslash sequences in all the
0964: * right places. A backslash will be inserted within </, allowing JSON
0965: * text to be delivered in HTML. In JSON text, a string cannot contain a
0966: * control character or an unescaped quote or backslash.
0967: * @param string A String
0968: * @return A String correctly formatted for insertion in a JSON text.
0969: */
0970: public static String quote(String string) {
0971: if (string == null || string.length() == 0) {
0972: return "\"\"";
0973: }
0974:
0975: char b;
0976: char c = 0;
0977: int i;
0978: int len = string.length();
0979: StringBuffer sb = new StringBuffer(len + 4);
0980: String t;
0981:
0982: sb.append('"');
0983: for (i = 0; i < len; i += 1) {
0984: b = c;
0985: c = string.charAt(i);
0986: switch (c) {
0987: case '\\':
0988: case '"':
0989: sb.append('\\');
0990: sb.append(c);
0991: break;
0992: case '/':
0993: if (b == '<') {
0994: sb.append('\\');
0995: }
0996: sb.append(c);
0997: break;
0998: case '\b':
0999: sb.append("\\b");
1000: break;
1001: case '\t':
1002: sb.append("\\t");
1003: break;
1004: case '\n':
1005: sb.append("\\n");
1006: break;
1007: case '\f':
1008: sb.append("\\f");
1009: break;
1010: case '\r':
1011: sb.append("\\r");
1012: break;
1013: default:
1014: if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
1015: || (c >= '\u2000' && c < '\u2100')) {
1016: t = "000" + Integer.toHexString(c);
1017: sb.append("\\u" + t.substring(t.length() - 4));
1018: } else {
1019: sb.append(c);
1020: }
1021: }
1022: }
1023: sb.append('"');
1024: return sb.toString();
1025: }
1026:
1027: /**
1028: * Remove a name and its value, if present.
1029: * @param key The name to be removed.
1030: * @return The value that was associated with the name,
1031: * or null if there was no value.
1032: */
1033: public Object remove(String key) {
1034: return this .myHashMap.remove(key);
1035: }
1036:
1037: /**
1038: * Throw an exception if the object is an NaN or infinite number.
1039: * @param o The object to test.
1040: * @throws JSONException If o is a non-finite number.
1041: */
1042: static void testValidity(Object o) throws JSONException {
1043: if (o != null) {
1044: if (o instanceof Double) {
1045: if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
1046: throw new JSONException(
1047: "JSON does not allow non-finite numbers.");
1048: }
1049: } else if (o instanceof Float) {
1050: if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
1051: throw new JSONException(
1052: "JSON does not allow non-finite numbers.");
1053: }
1054: }
1055: }
1056: }
1057:
1058: /**
1059: * Produce a JSONArray containing the values of the members of this
1060: * JSONObject.
1061: * @param names A JSONArray containing a list of key strings. This
1062: * determines the sequence of the values in the result.
1063: * @return A JSONArray of values.
1064: * @throws JSONException If any of the values are non-finite numbers.
1065: */
1066: public JSONArray toJSONArray(JSONArray names) throws JSONException {
1067: if (names == null || names.length() == 0) {
1068: return null;
1069: }
1070: JSONArray ja = new JSONArray();
1071: for (int i = 0; i < names.length(); i += 1) {
1072: ja.put(this .opt(names.getString(i)));
1073: }
1074: return ja;
1075: }
1076:
1077: /**
1078: * Make a JSON text of this JSONObject. For compactness, no whitespace
1079: * is added. If this would not result in a syntactically correct JSON text,
1080: * then null will be returned instead.
1081: * <p>
1082: * Warning: This method assumes that the data structure is acyclical.
1083: *
1084: * @return a printable, displayable, portable, transmittable
1085: * representation of the object, beginning
1086: * with <code>{</code> <small>(left brace)</small> and ending
1087: * with <code>}</code> <small>(right brace)</small>.
1088: */
1089: public String toString() {
1090: try {
1091: Iterator keys = keys();
1092: StringBuffer sb = new StringBuffer("{");
1093:
1094: while (keys.hasNext()) {
1095: if (sb.length() > 1) {
1096: sb.append(',');
1097: }
1098: Object o = keys.next();
1099: sb.append(quote(o.toString()));
1100: sb.append(':');
1101: sb.append(valueToString(this .myHashMap.get(o)));
1102: }
1103: sb.append('}');
1104: return sb.toString();
1105: } catch (Exception e) {
1106: return null;
1107: }
1108: }
1109:
1110: /**
1111: * Make a prettyprinted JSON text of this JSONObject.
1112: * <p>
1113: * Warning: This method assumes that the data structure is acyclical.
1114: * @param indentFactor The number of spaces to add to each level of
1115: * indentation.
1116: * @return a printable, displayable, portable, transmittable
1117: * representation of the object, beginning
1118: * with <code>{</code> <small>(left brace)</small> and ending
1119: * with <code>}</code> <small>(right brace)</small>.
1120: * @throws JSONException If the object contains an invalid number.
1121: */
1122: public String toString(int indentFactor) throws JSONException {
1123: return toString(indentFactor, 0);
1124: }
1125:
1126: /**
1127: * Make a prettyprinted JSON text of this JSONObject.
1128: * <p>
1129: * Warning: This method assumes that the data structure is acyclical.
1130: * @param indentFactor The number of spaces to add to each level of
1131: * indentation.
1132: * @param indent The indentation of the top level.
1133: * @return a printable, displayable, transmittable
1134: * representation of the object, beginning
1135: * with <code>{</code> <small>(left brace)</small> and ending
1136: * with <code>}</code> <small>(right brace)</small>.
1137: * @throws JSONException If the object contains an invalid number.
1138: */
1139: String toString(int indentFactor, int indent) throws JSONException {
1140: int i;
1141: int n = length();
1142: if (n == 0) {
1143: return "{}";
1144: }
1145: Iterator keys = keys();
1146: StringBuffer sb = new StringBuffer("{");
1147: int newindent = indent + indentFactor;
1148: Object o;
1149: if (n == 1) {
1150: o = keys.next();
1151: sb.append(quote(o.toString()));
1152: sb.append(": ");
1153: sb.append(valueToString(this .myHashMap.get(o),
1154: indentFactor, indent));
1155: } else {
1156: while (keys.hasNext()) {
1157: o = keys.next();
1158: if (sb.length() > 1) {
1159: sb.append(",\n");
1160: } else {
1161: sb.append('\n');
1162: }
1163: for (i = 0; i < newindent; i += 1) {
1164: sb.append(' ');
1165: }
1166: sb.append(quote(o.toString()));
1167: sb.append(": ");
1168: sb.append(valueToString(this .myHashMap.get(o),
1169: indentFactor, newindent));
1170: }
1171: if (sb.length() > 1) {
1172: sb.append('\n');
1173: for (i = 0; i < indent; i += 1) {
1174: sb.append(' ');
1175: }
1176: }
1177: }
1178: sb.append('}');
1179: return sb.toString();
1180: }
1181:
1182: /**
1183: * Make a JSON text of an Object value. If the object has an
1184: * value.toJSONString() method, then that method will be used to produce
1185: * the JSON text. The method is required to produce a strictly
1186: * conforming text. If the object does not contain a toJSONString
1187: * method (which is the most common case), then a text will be
1188: * produced by other means. If the value is an array or Collection,
1189: * then a JSONArray will be made from it and its toJSONString method
1190: * will be called. If the value is a MAP, then a JSONObject will be made
1191: * from it and its toJSONString method will be called. Otherwise, the
1192: * value's toString method will be called, and the result will be quoted.
1193: *
1194: * <p>
1195: * Warning: This method assumes that the data structure is acyclical.
1196: * @param value The value to be serialized.
1197: * @return a printable, displayable, transmittable
1198: * representation of the object, beginning
1199: * with <code>{</code> <small>(left brace)</small> and ending
1200: * with <code>}</code> <small>(right brace)</small>.
1201: * @throws JSONException If the value is or contains an invalid number.
1202: */
1203: static String valueToString(Object value) throws JSONException {
1204: if (value == null || value.equals(null)) {
1205: return "null";
1206: }
1207: if (value instanceof JSONString) {
1208: Object o;
1209: try {
1210: o = ((JSONString) value).toJSONString();
1211: } catch (Exception e) {
1212: throw new JSONException(e);
1213: }
1214: if (o instanceof String) {
1215: return (String) o;
1216: }
1217: throw new JSONException("Bad value from toJSONString: " + o);
1218: }
1219: if (value instanceof Number) {
1220: return numberToString((Number) value);
1221: }
1222: if (value instanceof Boolean || value instanceof JSONObject
1223: || value instanceof JSONArray) {
1224: return value.toString();
1225: }
1226: if (value instanceof Map) {
1227: return new JSONObject((Map) value).toString();
1228: }
1229: if (value instanceof Collection) {
1230: return new JSONArray((Collection) value).toString();
1231: }
1232: if (value.getClass().isArray()) {
1233: return new JSONArray(value).toString();
1234: }
1235: return quote(value.toString());
1236: }
1237:
1238: /**
1239: * Make a prettyprinted JSON text of an object value.
1240: * <p>
1241: * Warning: This method assumes that the data structure is acyclical.
1242: * @param value The value to be serialized.
1243: * @param indentFactor The number of spaces to add to each level of
1244: * indentation.
1245: * @param indent The indentation of the top level.
1246: * @return a printable, displayable, transmittable
1247: * representation of the object, beginning
1248: * with <code>{</code> <small>(left brace)</small> and ending
1249: * with <code>}</code> <small>(right brace)</small>.
1250: * @throws JSONException If the object contains an invalid number.
1251: */
1252: static String valueToString(Object value, int indentFactor,
1253: int indent) throws JSONException {
1254: if (value == null || value.equals(null)) {
1255: return "null";
1256: }
1257: try {
1258: if (value instanceof JSONString) {
1259: Object o = ((JSONString) value).toJSONString();
1260: if (o instanceof String) {
1261: return (String) o;
1262: }
1263: }
1264: } catch (Exception e) {
1265: /* forget about it */
1266: }
1267: if (value instanceof Number) {
1268: return numberToString((Number) value);
1269: }
1270: if (value instanceof Boolean) {
1271: return value.toString();
1272: }
1273: if (value instanceof JSONObject) {
1274: return ((JSONObject) value).toString(indentFactor, indent);
1275: }
1276: if (value instanceof JSONArray) {
1277: return ((JSONArray) value).toString(indentFactor, indent);
1278: }
1279: if (value instanceof Map) {
1280: return new JSONObject((Map) value).toString(indentFactor,
1281: indent);
1282: }
1283: if (value instanceof Collection) {
1284: return new JSONArray((Collection) value).toString(
1285: indentFactor, indent);
1286: }
1287: if (value.getClass().isArray()) {
1288: return new JSONArray(value).toString(indentFactor, indent);
1289: }
1290: return quote(value.toString());
1291: }
1292:
1293: /**
1294: * Write the contents of the JSONObject as JSON text to a writer.
1295: * For compactness, no whitespace is added.
1296: * <p>
1297: * Warning: This method assumes that the data structure is acyclical.
1298: *
1299: * @return The writer.
1300: * @throws JSONException
1301: */
1302: public Writer write(Writer writer) throws JSONException {
1303: try {
1304: boolean b = false;
1305: Iterator keys = keys();
1306: writer.write('{');
1307:
1308: while (keys.hasNext()) {
1309: if (b) {
1310: writer.write(',');
1311: }
1312: Object k = keys.next();
1313: writer.write(quote(k.toString()));
1314: writer.write(':');
1315: Object v = this .myHashMap.get(k);
1316: if (v instanceof JSONObject) {
1317: ((JSONObject) v).write(writer);
1318: } else if (v instanceof JSONArray) {
1319: ((JSONArray) v).write(writer);
1320: } else {
1321: writer.write(valueToString(v));
1322: }
1323: b = true;
1324: }
1325: writer.write('}');
1326: return writer;
1327: } catch (IOException e) {
1328: throw new JSONException(e);
1329: }
1330: }
1331: }
|