From cd2c6c0ec87dd9bfefaecc6dad3386703c81739b Mon Sep 17 00:00:00 2001 From: terribleplan Date: Sun, 30 Jun 2013 17:53:01 -0500 Subject: [PATCH] v0.1.1 --- build/EvilBotX-0.1.1-final.jar | Bin 0 -> 57238 bytes src/be/xrg/evilbotx/EvilBotX.java | 2 +- src/be/xrg/evilbotx/Storage.java | 15 +- src/be/xrg/evilbotx/Utilities.java | 57 +- .../xrg/evilbotx/components/HackCommand.java | 16 +- .../xrg/evilbotx/components/UrbanCommand.java | 80 + src/org/json/JSONArray.java | 946 ++++++++++ src/org/json/JSONException.java | 41 + src/org/json/JSONObject.java | 1643 +++++++++++++++++ src/org/json/JSONString.java | 19 + src/org/json/JSONStringer.java | 83 + src/org/json/JSONTokener.java | 449 +++++ src/org/json/JSONWriter.java | 355 ++++ 13 files changed, 3670 insertions(+), 36 deletions(-) create mode 100644 build/EvilBotX-0.1.1-final.jar create mode 100644 src/be/xrg/evilbotx/components/UrbanCommand.java create mode 100644 src/org/json/JSONArray.java create mode 100644 src/org/json/JSONException.java create mode 100644 src/org/json/JSONObject.java create mode 100644 src/org/json/JSONString.java create mode 100644 src/org/json/JSONStringer.java create mode 100644 src/org/json/JSONTokener.java create mode 100644 src/org/json/JSONWriter.java diff --git a/build/EvilBotX-0.1.1-final.jar b/build/EvilBotX-0.1.1-final.jar new file mode 100644 index 0000000000000000000000000000000000000000..49fc9e592b07dfcaf13a7599af825a484facf3ec GIT binary patch literal 57238 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 X>7Fs b = new ArrayList(); diff --git a/src/be/xrg/evilbotx/Storage.java b/src/be/xrg/evilbotx/Storage.java index 5daf078..093a88d 100644 --- a/src/be/xrg/evilbotx/Storage.java +++ b/src/be/xrg/evilbotx/Storage.java @@ -13,18 +13,19 @@ import java.util.HashMap; import java.util.Map; public class Storage { - static String fileName = "data.ebx"; static String dfName = "dsssseRs"; private Map data; private boolean loaded = false; private final String location; + private final String fileName; - public Storage() { - this(System.getProperty("user.dir") + "/"); + public Storage(String filename) { + this(filename, System.getProperty("user.dir") + "/"); } - public Storage(String location) { + public Storage(String filename, String location) { this.location = location; + this.fileName = filename; this.read(); if (!this.loaded) { this.init(); @@ -34,7 +35,7 @@ public class Storage { @SuppressWarnings("unchecked") private void read() { File f = new File(this.location); - File ff = new File(f, Storage.fileName); + File ff = new File(f, this.fileName); if (ff.exists()) { try { FileInputStream fi = new FileInputStream(ff); @@ -54,7 +55,7 @@ public class Storage { private void write() { try { FileOutputStream f = new FileOutputStream(this.location - + Storage.fileName); + + this.fileName); f.write(Storage.toByteArr((Serializable) this.data)); f.close(); } catch (IOException e) { @@ -65,7 +66,7 @@ public class Storage { private void init() { boolean fail = false; File f = new File(this.location); - File ff = new File(f, Storage.fileName); + File ff = new File(f, this.fileName); if (!f.exists()) { if (!f.mkdirs()) { fail = true; diff --git a/src/be/xrg/evilbotx/Utilities.java b/src/be/xrg/evilbotx/Utilities.java index 27c0e2a..f94e31f 100644 --- a/src/be/xrg/evilbotx/Utilities.java +++ b/src/be/xrg/evilbotx/Utilities.java @@ -1,10 +1,14 @@ package be.xrg.evilbotx; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStreamReader; +import java.io.ObjectOutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -39,9 +43,21 @@ public class Utilities { } return ret; } + public static String decodeHTMLEntities(String encoded) { return StringEscapeUtils.unescapeHtml4(encoded); } + + public static String urlEncode(String plain) { + try { + return URLEncoder.encode(plain, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // should never happen + e.printStackTrace(); + return null; + } + } + public static String getPageTitle(String html) { int[] f = new int[2]; f[0] = html.indexOf("") + 7; @@ -55,7 +71,7 @@ public class Utilities { int amount = 86400; if (seconds > amount) { ret += (seconds / amount) + " day"; - if (seconds/amount > 1) { + if (seconds / amount > 1) { ret += "s"; } ret += ", "; @@ -65,7 +81,7 @@ public class Utilities { amount = 3600; if (seconds > amount || started) { ret += (seconds / amount) + " hour"; - if (seconds/amount > 1) { + if (seconds / amount > 1) { ret += "s"; } ret += ", "; @@ -75,7 +91,7 @@ public class Utilities { amount = 60; if (seconds > amount || started) { ret += (seconds / amount) + " minute"; - if (seconds/amount > 1) { + if (seconds / amount > 1) { ret += "s"; } ret += ", "; @@ -89,18 +105,6 @@ public class Utilities { return ret; } - public static void saveData(byte[] data, String modName, int modID) { - - } - - public static boolean hasData(String modName, int modID) { - return false; - } - - public static byte[] loadData(String modName, int modID) { - return null; - } - public static String[] formatString(String[] a, int lineLength) { String nextln = null; boolean done = false; @@ -169,6 +173,29 @@ public class Utilities { return new String(hexChars); } + public static byte[] serializeObject(Object o) { + byte[] a = null; + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + objOut.writeObject(o); + a = out.toByteArray(); + objOut.close(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return a; + } + + public static String stringArrayToString(String[] arr) { + String ret = ""; + for (String a : arr) { + ret += a; + } + return ret; + } + private static String padString(String ln, int lineLength) { for (int i = ln.length(); i < lineLength; i++) { ln += " "; diff --git a/src/be/xrg/evilbotx/components/HackCommand.java b/src/be/xrg/evilbotx/components/HackCommand.java index 6778e68..b934209 100644 --- a/src/be/xrg/evilbotx/components/HackCommand.java +++ b/src/be/xrg/evilbotx/components/HackCommand.java @@ -1,10 +1,8 @@ package be.xrg.evilbotx.components; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -204,17 +202,9 @@ public class HackCommand extends be.xrg.evilbotx.parents.EBXComponent { } private void save() { - byte[] a = null; - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ObjectOutputStream objOut = new ObjectOutputStream(out); - objOut.writeObject(this.d); - a = out.toByteArray(); - objOut.close(); - out.close(); - } catch (IOException e) { - e.printStackTrace(); + byte[] a = Utilities.serializeObject(this.d); + if (a != null) { + this.s.putData(this.getComponentID(), this.getComponentName(), a); } - this.s.putData(this.getComponentID(), this.getComponentName(), a); } } diff --git a/src/be/xrg/evilbotx/components/UrbanCommand.java b/src/be/xrg/evilbotx/components/UrbanCommand.java new file mode 100644 index 0000000..04563e7 --- /dev/null +++ b/src/be/xrg/evilbotx/components/UrbanCommand.java @@ -0,0 +1,80 @@ +package be.xrg.evilbotx.components; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.pircbotx.hooks.Event; +import org.pircbotx.hooks.events.MessageEvent; + +import be.xrg.evilbotx.Storage; +import be.xrg.evilbotx.Utilities; + +@SuppressWarnings("rawtypes") +public class UrbanCommand extends be.xrg.evilbotx.parents.EBXComponent { + + public UrbanCommand(Storage s) { + super(s, MessageEvent.class); + } + + public void handleEvent(Event e) { + MessageEvent t = (MessageEvent) e; + String b = t.getMessage(); + boolean response = false; + if (b.startsWith("!urban")) { + if (b.contains(" ")) { + String[] c = b.split(" ", 2); + System.out.println(c[0]); + System.out.println(c[1]); + String a = Utilities + .getHTMLPage("http://api.urbandictionary.com/v0/define?term=" + + Utilities.urlEncode(c[1]))[1]; + System.out.println(a); + JSONObject d = new JSONObject(a); + if (d.has("result_type")) { + Object g = d.get("result_type"); + if (g instanceof String) { + if (((String) g).equals("exact")) { + if (d.has("list")) { + JSONArray h = d.getJSONArray("list"); + d = h.getJSONObject(0); + if (d.has("word") && d.has("definition")) { + Object i = d.get("word"), j = d + .get("definition"); + if ((i instanceof String) + && (j instanceof String)) { + t.getBot().sendMessage( + t.getChannel(), + ((String) i) + ": " + + ((String) j)); + response = true; + } + } + } + } else { + t.respond("Unable to find exact match."); + response = true; + } + } + } + } + } + if (!response) { + t.respond("Unable to perform lookup."); + } + } + + protected boolean wantEventM(Event e) { + if (e instanceof MessageEvent) { + return ((MessageEvent) e).getMessage().startsWith("!urban"); + } + return false; + } + + public String getComponentName() { + return "UrbanCommand"; + } + + public int getComponentID() { + return 4823303; + } + +} diff --git a/src/org/json/JSONArray.java b/src/org/json/JSONArray.java new file mode 100644 index 0000000..7d3b41a --- /dev/null +++ b/src/org/json/JSONArray.java @@ -0,0 +1,946 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having <code>get</code> and <code>opt</code> + * methods for accessing the values by index, and <code>put</code> methods for + * adding or replacing values. The values can be any of these types: + * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, + * <code>Number</code>, <code>String</code>, or the + * <code>JSONObject.NULL object</code>. + * <p> + * The constructor can convert a JSON text into a Java object. The + * <code>toString</code> method converts to JSON text. + * <p> + * A <code>get</code> method returns a value if one can be found, and throws an + * exception if one cannot be found. An <code>opt</code> method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + * <p> + * The generic <code>get()</code> and <code>opt()</code> methods return an + * object which you can cast or query for type. There are also typed + * <code>get</code> and <code>opt</code> methods that do type checking and type + * coercion for you. + * <p> + * The texts produced by the <code>toString</code> methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + * <ul> + * <li>An extra <code>,</code> <small>(comma)</small> may appear just + * before the closing bracket.</li> + * <li>The <code>null</code> value will be inserted when there is <code>,</code> + *  <small>(comma)</small> elision.</li> + * <li>Strings may be quoted with <code>'</code> <small>(single + * quote)</small>.</li> + * <li>Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * <code>{ } [ ] / \ : , #</code> and if they do not look like numbers and if + * they are not the reserved words <code>true</code>, <code>false</code>, or + * <code>null</code>.</li> + * </ul> + * + * @author JSON.org + * @version 2013-04-18 + */ +public class JSONArray { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList<Object> myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList<Object>(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x + * A JSONTokener + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source + * A string that begins with <code>[</code> <small>(left + * bracket)</small> and ends with <code>]</code> + *  <small>(right bracket)</small>. + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection + * A Collection. + */ + public JSONArray(Collection<Object> collection) { + this.myArrayList = new ArrayList<Object>(); + if (collection != null) { + Iterator<Object> iter = collection.iterator(); + while (iter.hasNext()) { + this.myArrayList.add(JSONObject.wrap(iter.next())); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException + * If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException( + "JSONArray initial value should be a string or collection or array."); + } + } + + /** + * Get the object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException + * If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" + * and "false" are converted to boolean. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException + * If there is no value for the index or if the value is not + * convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value is not a number. + */ + public int getInt(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException + * If there is no value for the index. or if the value is not a + * JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index + * subscript + * @return A JSONObject value. + * @throws JSONException + * If there is no value for the index or if the value is not a + * JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public long getLong(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException + * If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index + * The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * <code>separator</code> string is inserted between each element. Warning: + * This method assumes that the data structure is acyclical. + * + * @param separator + * A string that will be inserted between the elements. + * @return a string. + * @throws JSONException + * If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.myArrayList + .get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return this.getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return this.getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index + * subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return this.getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is coverted to a string. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object + .toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value + * A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + this.put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value + * A Collection value. + * @return this. + */ + public JSONArray put(Collection<Object> value) { + this.put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value + * A double value. + * @throws JSONException + * if the value is not finite. + * @return this. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + this.put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value + * An int value. + * @return this. + */ + public JSONArray put(int value) { + this.put(new Integer(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value + * A long value. + * @return this. + */ + public JSONArray put(long value) { + this.put(new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value + * A Map value. + * @return this. + */ + public JSONArray put(Map<String, Object> value) { + this.put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value + * An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * A boolean value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index + * The subscript. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, Collection<Object> value) + throws JSONException { + this.put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A double value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, double value) throws JSONException { + this.put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * An int value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + this.put(index, new Integer(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A long value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + this.put(index, new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index + * The subscript. + * @param value + * The Map value. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Map<String, Object> value) + throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + this.myArrayList.set(index, value); + } else { + while (index != this.length()) { + this.put(JSONObject.NULL); + } + this.put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index + * The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + Object o = this.opt(index); + this.myArrayList.remove(index); + return o; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names + * A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException + * If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || this.length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary + * whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if + * the array contains an invalid number. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method + * assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with <code>[</code> <small>(left + * bracket)</small> and ending with <code>]</code> + *  <small>(right bracket)</small>. + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indention of the top level. + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + int length = this.length(); + writer.write('['); + + if (length == 1) { + JSONObject.writeValue(writer, this.myArrayList.get(0), + indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + JSONObject.writeValue(writer, this.myArrayList.get(i), + indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} \ No newline at end of file diff --git a/src/org/json/JSONException.java b/src/org/json/JSONException.java new file mode 100644 index 0000000..0bf6a84 --- /dev/null +++ b/src/org/json/JSONException.java @@ -0,0 +1,41 @@ +package org.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2013-02-10 + */ +public class JSONException extends RuntimeException { + private static final long serialVersionUID = 0; + private Throwable cause; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message + * Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } + + /** + * Constructs a new JSONException with the specified cause. + */ + public JSONException(Throwable cause) { + super(cause.getMessage()); + this.cause = cause; + } + + /** + * Returns the cause of this exception or null if the cause is nonexistent + * or unknown. + * + * @returns the cause of this exception or null if the cause is nonexistent + * or unknown. + */ + public Throwable getCause() { + return this.cause; + } +} \ No newline at end of file diff --git a/src/org/json/JSONObject.java b/src/org/json/JSONObject.java new file mode 100644 index 0000000..475e47b --- /dev/null +++ b/src/org/json/JSONObject.java @@ -0,0 +1,1643 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having <code>get</code> and <code>opt</code> methods for accessing the + * values by name, and <code>put</code> methods for adding or replacing values + * by name. The values can be any of these types: <code>Boolean</code>, + * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>, + * <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject + * constructor can be used to convert an external form JSON text into an + * internal form whose values can be retrieved with the <code>get</code> and + * <code>opt</code> methods, or to convert values into a JSON text using the + * <code>put</code> and <code>toString</code> methods. A <code>get</code> method + * returns a value if one can be found, and throws an exception if one cannot be + * found. An <code>opt</code> method returns a default value instead of throwing + * an exception, and so is useful for obtaining optional values. + * <p> + * The generic <code>get()</code> and <code>opt()</code> methods return an + * object, which you can cast or query for type. There are also typed + * <code>get</code> and <code>opt</code> methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they do + * not throw. Instead, they return a specified value, such as null. + * <p> + * The <code>put</code> methods add or replace values in an object. For example, + * + * <pre> + * myString = new JSONObject().put("JSON", "Hello, World!").toString(); + * </pre> + * + * produces the string <code>{"JSON": "Hello, World"}</code>. + * <p> + * The texts produced by the <code>toString</code> methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + * <ul> + * <li>An extra <code>,</code> <small>(comma)</small> may appear just + * before the closing brace.</li> + * <li>Strings may be quoted with <code>'</code> <small>(single + * quote)</small>.</li> + * <li>Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * <code>{ } [ ] / \ : , #</code> and if they do not look like numbers and if + * they are not the reserved words <code>true</code>, <code>false</code>, or + * <code>null</code>.</li> + * </ul> + * + * @author JSON.org + * @version 2013-06-17 + */ +public class JSONObject { + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, so + * the clone method returns itself. + * + * @return NULL. + */ + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object + * An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + public boolean equals(Object object) { + return object == null || object == this; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + public String toString() { + return "null"; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map<String, Object> map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * <code>NULL</code> object than to use Java's <code>null</code> value. + * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>. + * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>. + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new HashMap<String, Object>(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo + * A JSONObject. + * @param names + * An array of strings. + * @throws JSONException + * @exception JSONException + * If a value is a non-finite number or if a name is + * duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. + + c = x.nextClean(); + if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + this.putOnce(key, x.nextValue()); + + // Pairs are separated by ','. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map + * A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + */ + public JSONObject(Map<String, Object> map) { + this.map = new HashMap<String, Object>(); + if (map != null) { + Iterator<Entry<String, Object>> i = map.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry<String, Object> e = i.next(); + Object value = e.getValue(); + if (value != null) { + this.map.put(e.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with <code>"get"</code> or + * <code>"is"</code> followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + * + * The key is formed by removing the <code>"get"</code> or <code>"is"</code> + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + * + * For example, if an object has a method named <code>"getName"</code>, and + * if the result of calling <code>object.getName()</code> is + * <code>"Larry Fine"</code>, then the JSONObject will contain + * <code>"name": "Larry Fine"</code>. + * + * @param bean + * An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object + * An object that has fields that should be used to make a + * JSONObject. + * @param names + * An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class<? extends Object> c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> + *  <small>(right brace)</small>. + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName + * The ResourceBundle base name. + * @param locale + * The Locale to load the ResourceBundle for. + * @throws JSONException + * If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, + Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + + Enumeration<String> keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key instanceof String) { + + // Go through the path, ensuring that there is a nested + // JSONObject for each + // segment except the last. Add the value using the last + // segment's name into + // the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there + * is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is an invalid number or if the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, + value instanceof JSONArray ? new JSONArray().put(value) + : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the key is null or if the current value associated with + * the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + + "] is not a JSONArray."); + } + return this; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d + * A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + + // Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key + * A key string. + * @return The object associated with the key. + * @throws JSONException + * if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with a key. + * + * @param key + * A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or + * "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + /** + * Get the double value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number."); + } + } + + /** + * Get the int value associated with a key. + * + * @param key + * A key string. + * @return The integer value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not an int."); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key + * A key string. + * @return The long value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to a long. + */ + public long getLong(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a long."); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator<String> iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class<? extends Object> klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key + * A key string. + * @return A string which is the value. + * @throws JSONException + * if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key + * A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key + * A key string. + * @return this. + * @throws JSONException + * If there is already a property with this name that is not an + * Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value).longValue() + 1); + } else if (value instanceof Double) { + this.put(key, ((Double) value).doubleValue() + 1); + } else if (value instanceof Float) { + this.put(key, ((Float) value).floatValue() + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no + * value. + * + * @param key + * A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator<String> keys() { + return this.keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. + * + * @return A keySet. + */ + public Set<String> keySet() { + return this.map.keySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator<String> keys = this.keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * + * @param number + * A Number + * @return A String. + * @throws JSONException + * If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key + * A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key + * A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key + * A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return this.getDouble(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return this.getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return this.getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key + * A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + private void populateMap(Object bean) { + Class<? extends Object> klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass + .getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if ("getClass".equals(name) + || "getDeclaringClass".equals(name)) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 + && Character.isUpperCase(key.charAt(0)) + && method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + + key.substring(1); + } + + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + this.map.put(key, wrap(result)); + } + } + } + } catch (Exception ignore) { + } + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A boolean which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection<?> value) throws JSONException { + this.put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A double which is the value. + * @return this. + * @throws JSONException + * If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + this.put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * An int which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + this.put(key, new Integer(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A long which is the value. + * @return this. + * @throws JSONException + * If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + this.put(key, new Long(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map<String, Object> value) + throws JSONException { + this.put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the + * key will be removed from the JSONObject if it is present. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is non-finite number or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new NullPointerException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key + * @param value + * @return his. + * @throws JSONException + * if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + this.put(key, value); + } + return this; + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within </, producing <\/, + * allowing JSON text to be delivered in HTML. In JSON text, a string cannot + * contain a control character or an unescaped quote or backslash. + * + * @param string + * A String + * @return A String correctly formatted for insertion in a JSON text. + */ + public static String quote(String string) { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + try { + return quote(string, sw).toString(); + } catch (IOException ignored) { + // will never happen - we are writing to a string writer + return ""; + } + } + } + + public static Writer quote(String string, Writer w) throws IOException { + if (string == null || string.length() == 0) { + w.write("\"\""); + return w; + } + + char b; + char c = 0; + String hhhh; + int i; + int len = string.length(); + + w.write('"'); + for (i = 0; i < len; i += 1) { + b = c; + c = string.charAt(i); + switch (c) { + case '\\': + case '"': + w.write('\\'); + w.write(c); + break; + case '/': + if (b == '<') { + w.write('\\'); + } + w.write(c); + break; + case '\b': + w.write("\\b"); + break; + case '\t': + w.write("\\t"); + break; + case '\n': + w.write("\\n"); + break; + case '\f': + w.write("\\f"); + break; + case '\r': + w.write("\\r"); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key + * The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string + * A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + Double d; + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. + */ + + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '-') { + try { + if (string.indexOf('.') > -1 || string.indexOf('e') > -1 + || string.indexOf('E') > -1) { + d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = new Long(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } + } catch (Exception ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o + * The object to test. + * @throws JSONException + * If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names + * A JSONArray containing a list of key strings. This determines + * the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException + * If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + */ + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + * @throws JSONException + * If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. + * If the object does not contain a toJSONString method (which is the most + * common case), then a text will be produced by other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @param value + * The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with <code>{</code> <small>(left + * brace)</small> and ending with <code>}</code> <small>(right + * brace)</small>. + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject + || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map<?, ?>) { + return new JSONObject((Map<?, ?>) value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection<?>) value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object + * The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String) { + return object; + } + + if (object instanceof Collection) { + return new JSONArray((Collection<?>) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map<?, ?>) { + return new JSONObject((Map<?, ?>) object); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage + .getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + static final Writer writeValue(Writer writer, Object value, + int indentFactor, int indent) throws JSONException, IOException { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + new JSONObject((Map<?, ?>) value).write(writer, indentFactor, + indent); + } else if (value instanceof Collection) { + new JSONArray((Collection<?>) value).write(writer, indentFactor, + indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writer.write(numberToString((Number) value)); + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + * <p> + * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + final int length = this.length(); + Iterator<String> keys = this.keys(); + writer.write('{'); + + if (length == 1) { + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + while (keys.hasNext()) { + Object key = keys.next(); + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, + newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } +} \ No newline at end of file diff --git a/src/org/json/JSONString.java b/src/org/json/JSONString.java new file mode 100644 index 0000000..bfb520b --- /dev/null +++ b/src/org/json/JSONString.java @@ -0,0 +1,19 @@ +package org.json; + +/** + * The <code>JSONString</code> interface allows a <code>toJSONString()</code> + * method so that a class can change the behavior of + * <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>, and + * <code>JSONWriter.value(</code>Object<code>)</code>. The + * <code>toJSONString</code> method will be used instead of the default behavior + * of using the Object's <code>toString()</code> method and quoting the result. + */ +public interface JSONString { + /** + * The <code>toJSONString</code> method allows a class to produce its own + * JSON serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} \ No newline at end of file diff --git a/src/org/json/JSONStringer.java b/src/org/json/JSONStringer.java new file mode 100644 index 0000000..c1115a7 --- /dev/null +++ b/src/org/json/JSONStringer.java @@ -0,0 +1,83 @@ +package org.json; + +/* + Copyright (c) 2006 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.StringWriter; + +/** + * JSONStringer provides a quick and convenient way of producing JSON text. The + * texts produced strictly conform to JSON syntax rules. No whitespace is added, + * so the results are ready for transmission or storage. Each instance of + * JSONStringer can produce one JSON text. + * <p> + * A JSONStringer instance provides a <code>value</code> method for appending + * values to the text, and a <code>key</code> method for adding keys before + * values in objects. There are <code>array</code> and <code>endArray</code> + * methods that make and bound array values, and <code>object</code> and + * <code>endObject</code> methods which make and bound object values. All of + * these methods return the JSONWriter instance, permitting cascade style. For + * example, + * + * <pre> + * myString = new JSONStringer().object().key("JSON").value("Hello, World!") + * .endObject().toString(); + * </pre> + * + * which produces the string + * + * <pre> + * {"JSON":"Hello, World!"} + * </pre> + * <p> + * The first method called must be <code>array</code> or <code>object</code>. + * There are no methods for adding commas or colons. JSONStringer adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + * <p> + * This can sometimes be easier than using a JSONObject to build a string. + * + * @author JSON.org + * @version 2008-09-18 + */ +public class JSONStringer extends JSONWriter { + /** + * Make a fresh JSONStringer. It can be used to build one JSON text. + */ + public JSONStringer() { + super(new StringWriter()); + } + + /** + * Return the JSON text. This method is used to obtain the product of the + * JSONStringer instance. It will return <code>null</code> if there was a + * problem in the construction of the JSON text (such as the calls to + * <code>array</code> were not properly balanced with calls to + * <code>endArray</code>). + * + * @return The JSON text. + */ + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } +} \ No newline at end of file diff --git a/src/org/json/JSONTokener.java b/src/org/json/JSONTokener.java new file mode 100644 index 0000000..a444ce1 --- /dev/null +++ b/src/org/json/JSONTokener.java @@ -0,0 +1,449 @@ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse JSON + * source strings. + * + * @author JSON.org + * @version 2012-02-16 + */ +public class JSONTokener { + + private long character; + private boolean eof; + private long index; + private long line; + private char previous; + private Reader reader; + private boolean usePrevious; + + /** + * Construct a JSONTokener from a Reader. + * + * @param reader + * A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader( + reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.line = 1; + } + + /** + * Construct a JSONTokener from an InputStream. + */ + public JSONTokener(InputStream inputStream) throws JSONException { + this(new InputStreamReader(inputStream)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s + * A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + /** + * Back up one character. This provides a sort of lookahead capability, so + * that you can test for a digit or letter before attempting to parse the + * next number or identifier. + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.index -= 1; + this.character -= 1; + this.usePrevious = true; + this.eof = false; + } + + /** + * Get the hex value of a character (base16). + * + * @param c + * A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + public boolean end() { + return this.eof && !this.usePrevious; + } + + /** + * Determine if the source string still contains characters that next() can + * consume. + * + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + this.next(); + if (this.end()) { + return false; + } + this.back(); + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + + if (c <= 0) { // End of stream + this.eof = true; + c = 0; + } + } + this.index += 1; + if (this.previous == '\r') { + this.line += 1; + this.character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + this.line += 1; + this.character = 0; + } else { + this.character += 1; + } + this.previous = (char) c; + return this.previous; + } + + /** + * Consume the next character, and check that it matches a specified + * character. + * + * @param c + * The character to match. + * @return The character. + * @throws JSONException + * if the character does not match. + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + + "'"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n + * The number of characters to take. + * @return A string of n characters. + * @throws JSONException + * Substring bounds error if there are not n characters + * remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * Get the next char in the string, skipping whitespace. + * + * @throws JSONException + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = this.next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + /** + * Return the characters up to the next close quote character. Backslash + * processing is done. The formal JSON format does not allow strings in + * single quotes, but an implementation is allowed to accept them. + * + * @param quote + * The quoting character, either <code>"</code> + *  <small>(double quote)</small> or <code>'</code> + *  <small>(single quote)</small>. + * @return A String. + * @throws JSONException + * Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string"); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(this.next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the end of + * line, whichever comes first. + * + * @param delimiter + * A delimiter character. + * @return A string. + */ + public String nextTo(char delimiter) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (;;) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * + * @param delimiters + * A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (;;) { + c = this.next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * + * @throws JSONException + * If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = this.nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return this.nextString(c); + case '{': + this.back(); + return new JSONObject(this); + case '[': + this.back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or null, + * or it can be a number. An implementation (such as this one) is + * allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + + StringBuffer sb = new StringBuffer(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + this.back(); + + string = sb.toString().trim(); + if ("".equals(string)) { + throw this.syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If + * the requested character is not found, no characters are skipped. + * + * @param to + * A character to skip to. + * @return The requested character, or zero if the requested character is + * not found. + */ + public char skipTo(char to) throws JSONException { + char c; + try { + long startIndex = this.index; + long startCharacter = this.character; + long startLine = this.line; + this.reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return c; + } + } while (c != to); + } catch (IOException exc) { + throw new JSONException(exc); + } + + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message + * The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this.toString()); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + + this.line + "]"; + } +} \ No newline at end of file diff --git a/src/org/json/JSONWriter.java b/src/org/json/JSONWriter.java new file mode 100644 index 0000000..c8e726c --- /dev/null +++ b/src/org/json/JSONWriter.java @@ -0,0 +1,355 @@ +package org.json; + +import java.io.IOException; +import java.io.Writer; + +/* + Copyright (c) 2006 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +/** + * JSONWriter provides a quick and convenient way of producing JSON text. The + * texts produced strictly conform to JSON syntax rules. No whitespace is added, + * so the results are ready for transmission or storage. Each instance of + * JSONWriter can produce one JSON text. + * <p> + * A JSONWriter instance provides a <code>value</code> method for appending + * values to the text, and a <code>key</code> method for adding keys before + * values in objects. There are <code>array</code> and <code>endArray</code> + * methods that make and bound array values, and <code>object</code> and + * <code>endObject</code> methods which make and bound object values. All of + * these methods return the JSONWriter instance, permitting a cascade style. For + * example, + * + * <pre> + * new JSONWriter(myWriter).object().key("JSON").value("Hello, World!") + * .endObject(); + * </pre> + * + * which writes + * + * <pre> + * {"JSON":"Hello, World!"} + * </pre> + * <p> + * The first method called must be <code>array</code> or <code>object</code>. + * There are no methods for adding commas or colons. JSONWriter adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + * <p> + * This can sometimes be easier than using a JSONObject to build a string. + * + * @author JSON.org + * @version 2011-11-24 + */ +public class JSONWriter { + private static final int maxdepth = 200; + + /** + * The comma flag determines if a comma should be output before the next + * value. + */ + private boolean comma; + + /** + * The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' + * (key), 'o' (object). + */ + protected char mode; + + /** + * The object/array stack. + */ + private final JSONObject stack[]; + + /** + * The stack top index. A value of 0 indicates that the stack is empty. + */ + private int top; + + /** + * The writer that will receive the output. + */ + protected Writer writer; + + /** + * Make a fresh JSONWriter. It can be used to build one JSON text. + */ + public JSONWriter(Writer w) { + this.comma = false; + this.mode = 'i'; + this.stack = new JSONObject[maxdepth]; + this.top = 0; + this.writer = w; + } + + /** + * Append a value. + * + * @param string + * A string value. + * @return this + * @throws JSONException + * If the value is out of sequence. + */ + private JSONWriter append(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null pointer"); + } + if (this.mode == 'o' || this.mode == 'a') { + try { + if (this.comma && this.mode == 'a') { + this.writer.write(','); + } + this.writer.write(string); + } catch (IOException e) { + throw new JSONException(e); + } + if (this.mode == 'o') { + this.mode = 'k'; + } + this.comma = true; + return this; + } + throw new JSONException("Value out of sequence."); + } + + /** + * Begin appending a new array. All values until the balancing + * <code>endArray</code> will be appended to this array. The + * <code>endArray</code> method must be called to mark the array's end. + * + * @return this + * @throws JSONException + * If the nesting is too deep, or if the object is started in + * the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter array() throws JSONException { + if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { + this.push(null); + this.append("["); + this.comma = false; + return this; + } + throw new JSONException("Misplaced array."); + } + + /** + * End something. + * + * @param mode + * Mode + * @param c + * Closing character + * @return this + * @throws JSONException + * If unbalanced. + */ + private JSONWriter end(char mode, char c) throws JSONException { + if (this.mode != mode) { + throw new JSONException(mode == 'a' ? "Misplaced endArray." + : "Misplaced endObject."); + } + this.pop(mode); + try { + this.writer.write(c); + } catch (IOException e) { + throw new JSONException(e); + } + this.comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to + * <code>array</code>. + * + * @return this + * @throws JSONException + * If incorrectly nested. + */ + public JSONWriter endArray() throws JSONException { + return this.end('a', ']'); + } + + /** + * End an object. This method most be called to balance calls to + * <code>object</code>. + * + * @return this + * @throws JSONException + * If incorrectly nested. + */ + public JSONWriter endObject() throws JSONException { + return this.end('k', '}'); + } + + /** + * Append a key. The key will be associated with the next value. In an + * object, every value must be preceded by a key. + * + * @param string + * A key string. + * @return this + * @throws JSONException + * If the key is out of place. For example, keys do not belong + * in arrays or if the key is null. + */ + public JSONWriter key(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null key."); + } + if (this.mode == 'k') { + try { + this.stack[this.top - 1].putOnce(string, Boolean.TRUE); + if (this.comma) { + this.writer.write(','); + } + this.writer.write(JSONObject.quote(string)); + this.writer.write(':'); + this.comma = false; + this.mode = 'o'; + return this; + } catch (IOException e) { + throw new JSONException(e); + } + } + throw new JSONException("Misplaced key."); + } + + /** + * Begin appending a new object. All keys and values until the balancing + * <code>endObject</code> will be appended to this object. The + * <code>endObject</code> method must be called to mark the object's end. + * + * @return this + * @throws JSONException + * If the nesting is too deep, or if the object is started in + * the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter object() throws JSONException { + if (this.mode == 'i') { + this.mode = 'o'; + } + if (this.mode == 'o' || this.mode == 'a') { + this.append("{"); + this.push(new JSONObject()); + this.comma = false; + return this; + } + throw new JSONException("Misplaced object."); + + } + + /** + * Pop an array or object scope. + * + * @param c + * The scope to close. + * @throws JSONException + * If nesting is wrong. + */ + private void pop(char c) throws JSONException { + if (this.top <= 0) { + throw new JSONException("Nesting error."); + } + char m = this.stack[this.top - 1] == null ? 'a' : 'k'; + if (m != c) { + throw new JSONException("Nesting error."); + } + this.top -= 1; + this.mode = this.top == 0 ? 'd' + : this.stack[this.top - 1] == null ? 'a' : 'k'; + } + + /** + * Push an array or object scope. + * + * @param c + * The scope to open. + * @throws JSONException + * If nesting is too deep. + */ + private void push(JSONObject jo) throws JSONException { + if (this.top >= maxdepth) { + throw new JSONException("Nesting too deep."); + } + this.stack[this.top] = jo; + this.mode = jo == null ? 'a' : 'k'; + this.top += 1; + } + + /** + * Append either the value <code>true</code> or the value <code>false</code> + * . + * + * @param b + * A boolean. + * @return this + * @throws JSONException + */ + public JSONWriter value(boolean b) throws JSONException { + return this.append(b ? "true" : "false"); + } + + /** + * Append a double value. + * + * @param d + * A double. + * @return this + * @throws JSONException + * If the number is not finite. + */ + public JSONWriter value(double d) throws JSONException { + return this.value(new Double(d)); + } + + /** + * Append a long value. + * + * @param l + * A long. + * @return this + * @throws JSONException + */ + public JSONWriter value(long l) throws JSONException { + return this.append(Long.toString(l)); + } + + /** + * Append an object value. + * + * @param object + * The object to append. It can be null, or a Boolean, Number, + * String, JSONObject, or JSONArray, or an object that implements + * JSONString. + * @return this + * @throws JSONException + * If the value is out of sequence. + */ + public JSONWriter value(Object object) throws JSONException { + return this.append(JSONObject.valueToString(object)); + } +} \ No newline at end of file