From 72cde1ff04f77c4cebbcb39b887555f25a748d99 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 19 Apr 2013 10:50:05 -0700 Subject: [PATCH 01/31] zip --- None.java | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 None.java diff --git a/None.java b/None.java deleted file mode 100644 index 1dda8bc69..000000000 --- a/None.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.json; - -public interface None { - /** - * Negative One - */ - public static final int none = -1; - -} From a360c40b74201a092989998d85531a986c9e5146 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 19 Apr 2013 11:09:16 -0700 Subject: [PATCH 02/31] Disallow ; and => --- JSONArray.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index 7c71d8db4..673a91927 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -69,15 +69,13 @@ of this software and associated documentation files (the "Software"), to deal *
  • 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: - * { } [ ] / \ : , = ; # and if they do not look like numbers and + * { } [ ] / \ : , # and if they do not look like numbers and * if they are not the reserved words true, false, or * null.
  • - *
  • Values can be separated by ; (semicolon) as - * well as by , (comma).
  • * * * @author JSON.org - * @version 2012-11-13 + * @version 2013-04-18 */ public class JSONArray { @@ -117,7 +115,6 @@ public JSONArray(JSONTokener x) throws JSONException { this.myArrayList.add(x.nextValue()); } switch (x.nextClean()) { - case ';': case ',': if (x.nextClean() == ']') { return; From a73066f6bacd7c2fbb545dbbc2be3e569493b0b5 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 19 Apr 2013 11:10:02 -0700 Subject: [PATCH 03/31] Disallow ; and => --- JSONObject.java | 1116 ++++++++++++++++++++++++----------------------- 1 file changed, 576 insertions(+), 540 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index 399f093c1..9d2d5cee3 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -1,28 +1,28 @@ 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. -*/ + 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; @@ -43,29 +43,32 @@ of this software and associated documentation files (the "Software"), to deal * 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 get and opt methods for accessing the - * values by name, and put methods for adding or replacing values - * by name. The values can be any of these types: Boolean, + * object having get and opt methods for accessing + * the values by name, and put methods for adding or replacing + * values by name. The values can be any of these types: Boolean, * JSONArray, JSONObject, Number, - * String, or the JSONObject.NULL 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 get and - * opt methods, or to convert values into a JSON text using the - * put and toString methods. A get method - * returns a value if one can be found, and throws an exception if one cannot be - * found. An opt method returns a default value instead of throwing - * an exception, and so is useful for obtaining optional values. + * String, or the JSONObject.NULL 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 + * get and opt methods, or to convert values into a + * JSON text using the put and toString methods. A + * get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. *

    * The generic get() and opt() methods return an * object, which you can cast or query for type. There are also typed * get and opt 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. + * 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. *

    - * The put methods add or replace values in an object. For example, + * The put methods add or replace values in an object. For + * example, * *

    - * myString = new JSONObject().put("JSON", "Hello, World!").toString();
    + * myString = new JSONObject()
    + *         .put("JSON", "Hello, World!").toString();
      * 
    * * produces the string {"JSON": "Hello, World"}. @@ -78,46 +81,43 @@ of this software and associated documentation files (the "Software"), to deal * before the closing brace. *
  • Strings may be quoted with ' (single * quote).
  • - *
  • 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: - * { } [ ] / \ : , = ; # and if they do not look like numbers and - * if they are not the reserved words true, false, or - * null.
  • - *
  • Keys can be followed by = or => as well as by - * :.
  • - *
  • Values can be followed by ; (semicolon) as - * well as by , (comma).
  • + *
  • 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: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, + * or null.
  • * * * @author JSON.org - * @version 2012-12-01 + * @version 2013-04-18 */ public class JSONObject { /** * The maximum number of keys in the key pool. */ - private static final int keyPoolSize = 100; + private static final int keyPoolSize = 100; - /** + /** * Key pooling is like string interning, but without permanently tying up * memory. To help conserve memory, storage of duplicated key strings in * JSONObjects will be avoided by using a key pool to manage unique key * string objects. This is used by JSONObject.put(string, object). */ - private static HashMap keyPool = new HashMap(keyPoolSize); + private static HashMap keyPool = new HashMap(keyPoolSize); /** * 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 { + 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. + * + * @return NULL. */ protected final Object clone() { return this; @@ -125,9 +125,11 @@ protected final Object clone() { /** * 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. + * + * @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; @@ -135,6 +137,7 @@ public boolean equals(Object object) { /** * Get the "null" string value. + * * @return The string "null". */ public String toString() { @@ -142,13 +145,11 @@ public String toString() { } } - /** * The map where the JSONObject's properties are kept. */ private final Map map; - /** * It is sometimes more convenient and less ambiguous to have a * NULL object than to use Java's null value. @@ -157,7 +158,6 @@ public String toString() { */ public static final Object NULL = new Null(); - /** * Construct an empty JSONObject. */ @@ -165,15 +165,19 @@ public JSONObject() { this.map = new HashMap(); } - /** - * 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. + * 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. + * @exception JSONException + * If a value is a non-finite number or if a name is + * duplicated. */ public JSONObject(JSONObject jo, String[] names) { this(); @@ -185,12 +189,14 @@ public JSONObject(JSONObject jo, String[] names) { } } - /** * 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. + * + * @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(); @@ -212,19 +218,15 @@ public JSONObject(JSONTokener x) throws JSONException { key = x.nextValue().toString(); } -// The key is followed by ':'. We will also tolerate '=' or '=>'. +// The key is followed by ':'. c = x.nextClean(); - if (c == '=') { - if (x.next() != '>') { - x.back(); - } - } else if (c != ':') { + if (c != ':') { throw x.syntaxError("Expected a ':' after a key"); } this.putOnce(key, x.nextValue()); -// Pairs are separated by ','. We will also tolerate ';'. +// Pairs are separated by ','. switch (x.nextClean()) { case ';': @@ -242,12 +244,12 @@ public JSONObject(JSONTokener x) throws JSONException { } } - /** * Construct a JSONObject from a Map. * - * @param map A map object that can be used to initialize the contents of - * the JSONObject. + * @param map + * A map object that can be used to initialize the contents of + * the JSONObject. * @throws JSONException */ public JSONObject(Map map) { @@ -255,7 +257,7 @@ public JSONObject(Map map) { if (map != null) { Iterator i = map.entrySet().iterator(); while (i.hasNext()) { - Map.Entry e = (Map.Entry)i.next(); + Map.Entry e = (Map.Entry) i.next(); Object value = e.getValue(); if (value != null) { this.map.put(e.getKey(), wrap(value)); @@ -264,42 +266,45 @@ public JSONObject(Map map) { } } - /** - * 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 "get" or "is" 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. + * 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 "get" or + * "is" 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 "get" or "is" prefix. - * If the second remaining character is not upper case, then the first - * character is converted to lower case. + * The key is formed by removing the "get" or "is" + * 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 "getName", and - * if the result of calling object.getName() is "Larry Fine", - * then the JSONObject will contain "name": "Larry Fine". + * if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". * - * @param bean An object that has getter methods that should be used - * to make a JSONObject. + * @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 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(); @@ -313,26 +318,31 @@ public JSONObject(Object object, String names[]) { } } - /** - * Construct a JSONObject from a source JSON text string. - * This is the most commonly used JSONObject constructor. - * @param source A string beginning - * with { (left brace) and ending - * with } (right brace). - * @exception JSONException If there is a syntax error in the source - * string or a duplicated key. + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace). + * @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. + * + * @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(); @@ -350,7 +360,7 @@ public JSONObject(String baseName, Locale locale) throws JSONException { // segment except the last. Add the value using the last segment's name into // the deepest nested JSONObject. - String[] path = ((String)key).split("\\."); + String[] path = ((String) key).split("\\."); int last = path.length - 1; JSONObject target = this; for (int i = 0; i < last; i += 1) { @@ -362,57 +372,59 @@ public JSONObject(String baseName, Locale locale) throws JSONException { } target = nextTarget; } - target.put(path[last], bundle.getString((String)key)); + 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. + * 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. + * @throws JSONException + * If the value is an invalid number or if the key is null. */ - public JSONObject accumulate( - String key, - Object value - ) throws JSONException { + 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); + this.put(key, + value instanceof JSONArray ? new JSONArray().put(value) + : value); } else if (object instanceof JSONArray) { - ((JSONArray)object).put(value); + ((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. + * + * @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. + * @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); @@ -420,19 +432,20 @@ public JSONObject append(String key, Object value) throws JSONException { if (object == null) { this.put(key, new JSONArray().put(value)); } else if (object instanceof JSONArray) { - this.put(key, ((JSONArray)object).put(value)); + this.put(key, ((JSONArray) object).put(value)); } else { - throw new JSONException("JSONObject[" + key + - "] is not a JSONArray."); + 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. + * 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) { @@ -443,8 +456,8 @@ public static String doubleToString(double d) { // 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) { + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { while (string.endsWith("0")) { string = string.substring(0, string.length() - 1); } @@ -455,13 +468,14 @@ public static String doubleToString(double d) { 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. + * @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) { @@ -469,135 +483,135 @@ public Object get(String key) throws JSONException { } Object object = this.opt(key); if (object == null) { - throw new JSONException("JSONObject[" + quote(key) + - "] not found."); + 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". + * @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"))) { + 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"))) { + } 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."); + 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. + * + * @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); + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + - "] is not a number."); + 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. + * @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); + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + - "] is not an int."); + 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. + * @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; + return (JSONArray) object; } - throw new JSONException("JSONObject[" + quote(key) + - "] is not a JSONArray."); + 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. + * @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; + return (JSONObject) object; } - throw new JSONException("JSONObject[" + quote(key) + - "] is not a JSONObject."); + 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. + * @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); + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + - "] is not a long."); + throw new JSONException("JSONObject[" + quote(key) + + "] is not a long."); } } - /** * Get an array of field names from a JSONObject. * @@ -612,13 +626,12 @@ public static String[] getNames(JSONObject jo) { String[] names = new String[length]; int i = 0; while (iterator.hasNext()) { - names[i] = (String)iterator.next(); + names[i] = (String) iterator.next(); i += 1; } return names; } - /** * Get an array of field names from an Object. * @@ -641,74 +654,77 @@ public static String[] getNames(Object object) { 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. + * @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; + return (String) object; } - throw new JSONException("JSONObject[" + quote(key) + - "] not a string."); + 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. + * + * @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. + * 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. + * @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); + this.put(key, ((Integer) value).intValue() + 1); } else if (value instanceof Long) { - this.put(key, ((Long)value).longValue() + 1); + this.put(key, ((Long) value).longValue() + 1); } else if (value instanceof Double) { - this.put(key, ((Double)value).doubleValue() + 1); + this.put(key, ((Double) value).doubleValue() + 1); } else if (value instanceof Float) { - this.put(key, ((Float)value).floatValue() + 1); + 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. + * 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. * @@ -718,7 +734,6 @@ public Iterator keys() { return this.keySet().iterator(); } - /** * Get a set of keys of the JSONObject. * @@ -728,7 +743,6 @@ public Set keySet() { return this.map.keySet(); } - /** * Get the number of keys stored in the JSONObject. * @@ -738,16 +752,16 @@ 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. + * is empty. */ public JSONArray names() { JSONArray ja = new JSONArray(); - Iterator keys = this.keys(); + Iterator keys = this.keys(); while (keys.hasNext()) { ja.put(keys.next()); } @@ -756,12 +770,14 @@ public JSONArray names() { /** * Produce a string from a Number. - * @param number A Number + * + * @param number + * A Number * @return A String. - * @throws JSONException If n is a non-finite number. + * @throws JSONException + * If n is a non-finite number. */ - public static String numberToString(Number number) - throws JSONException { + public static String numberToString(Number number) throws JSONException { if (number == null) { throw new JSONException("Null pointer"); } @@ -770,8 +786,8 @@ public static String numberToString(Number 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) { + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { while (string.endsWith("0")) { string = string.substring(0, string.length() - 1); } @@ -782,38 +798,39 @@ public static String numberToString(Number number) 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. + * + * @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". + * 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. + * @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). + * 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. + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. */ public boolean optBoolean(String key, boolean defaultValue) { try { @@ -823,30 +840,29 @@ public boolean optBoolean(String key, boolean 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. + * 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. + * @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. + * 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. + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. */ public double optDouble(String key, double defaultValue) { try { @@ -856,30 +872,29 @@ public double optDouble(String key, double 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. + * 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. + * @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. + * 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. + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. */ public int optInt(String key, int defaultValue) { try { @@ -889,58 +904,55 @@ public int optInt(String key, int 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. + * 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. + * @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; + 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. + * 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. + * @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; + 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. + * 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. + * @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. + * 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. + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. */ public long optLong(String key, long defaultValue) { try { @@ -950,34 +962,34 @@ public long optLong(String key, long 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. + * 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. + * @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. + * 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. + * @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 klass = bean.getClass(); @@ -985,9 +997,8 @@ private void populateMap(Object bean) { boolean includeSuperClass = klass.getClassLoader() != null; - Method[] methods = includeSuperClass - ? klass.getMethods() - : klass.getDeclaredMethods(); + Method[] methods = includeSuperClass ? klass.getMethods() : klass + .getDeclaredMethods(); for (int i = 0; i < methods.length; i += 1) { try { Method method = methods[i]; @@ -995,8 +1006,8 @@ private void populateMap(Object bean) { String name = method.getName(); String key = ""; if (name.startsWith("get")) { - if ("getClass".equals(name) || - "getDeclaringClass".equals(name)) { + if ("getClass".equals(name) + || "getDeclaringClass".equals(name)) { key = ""; } else { key = name.substring(3); @@ -1004,17 +1015,17 @@ private void populateMap(Object bean) { } 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() > 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); + key = key.substring(0, 1).toLowerCase() + + key.substring(1); } - Object result = method.invoke(bean, (Object[])null); + Object result = method.invoke(bean, (Object[]) null); if (result != null) { this.map.put(key, wrap(result)); } @@ -1025,27 +1036,31 @@ private void populateMap(Object bean) { } } - /** * Put a key/boolean pair in the JSONObject. * - * @param key A key string. - * @param value A boolean which is the value. + * @param key + * A key string. + * @param value + * A boolean which is the value. * @return this. - * @throws JSONException If the key is null. + * @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. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. * @throws JSONException */ public JSONObject put(String key, Collection value) throws JSONException { @@ -1053,55 +1068,63 @@ public JSONObject put(String key, Collection value) throws JSONException { return this; } - /** * Put a key/double pair in the JSONObject. * - * @param key A key string. - * @param value A double which is the value. + * @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. + * @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. + * @param key + * A key string. + * @param value + * An int which is the value. * @return this. - * @throws JSONException If the key is null. + * @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. + * @param key + * A key string. + * @param value + * A long which is the value. * @return this. - * @throws JSONException If the key is null. + * @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. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. * @throws JSONException */ public JSONObject put(String key, Map value) throws JSONException { @@ -1109,26 +1132,28 @@ public JSONObject put(String key, Map value) throws JSONException { 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. + * 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. + * @throws JSONException + * If the value is non-finite number or if the key is null. */ public JSONObject put(String key, Object value) throws JSONException { String pooled; if (key == null) { - throw new JSONException("Null key."); + throw new NullPointerException("Null key."); } if (value != null) { testValidity(value); - pooled = (String)keyPool.get(key); + pooled = (String) keyPool.get(key); if (pooled == null) { if (keyPool.size() >= keyPoolSize) { keyPool = new HashMap(keyPoolSize); @@ -1144,15 +1169,16 @@ public JSONObject put(String key, Object value) throws JSONException { 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. + * 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 + * @throws JSONException + * if the key is a duplicate */ public JSONObject putOnce(String key, Object value) throws JSONException { if (key != null && value != null) { @@ -1164,16 +1190,19 @@ public JSONObject putOnce(String key, Object value) throws JSONException { 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. + * 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. + * @throws JSONException + * If the value is a non-finite number. */ public JSONObject putOpt(String key, Object value) throws JSONException { if (key != null && value != null) { @@ -1182,14 +1211,15 @@ public JSONObject putOpt(String key, Object value) throws JSONException { return this; } - /** * Produce a string in double quotes with backslash sequences in all the * right places. A backslash will be inserted within = '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + if ((b >= '0' && b <= '9') || b == '-') { try { - if (string.indexOf('.') > -1 || - string.indexOf('e') > -1 || string.indexOf('E') > -1) { + 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 (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); - } else { - return myLong; + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } } } - } catch (Exception ignore) { + } 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. + * + * @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()) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { throw new JSONException( - "JSON does not allow non-finite numbers."); + "JSON does not allow non-finite numbers."); } } else if (o instanceof Float) { - if (((Float)o).isInfinite() || ((Float)o).isNaN()) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { throw new JSONException( - "JSON does not allow non-finite numbers."); + "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. + * + * @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. + * @throws JSONException + * If any of the values are non-finite numbers. */ public JSONArray toJSONArray(JSONArray names) throws JSONException { if (names == null || names.length() == 0) { @@ -1367,16 +1404,16 @@ public JSONArray toJSONArray(JSONArray names) throws JSONException { } /** - * Make a JSON text of this JSONObject. For compactness, no whitespace - * is added. If this would not result in a syntactically correct JSON text, + * 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. *

    * Warning: This method assumes that the data structure is acyclical. * - * @return a printable, displayable, portable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). */ public String toString() { try { @@ -1386,18 +1423,19 @@ public String toString() { } } - /** * Make a prettyprinted JSON text of this JSONObject. *

    * 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 { (left brace) and ending - * with } (right brace). - * @throws JSONException If the object contains an invalid number. + * + * @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 { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the object contains an invalid number. */ public String toString(int indentFactor) throws JSONException { StringWriter w = new StringWriter(); @@ -1408,24 +1446,27 @@ public String toString(int indentFactor) throws JSONException { /** * 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. + * 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. * *

    * 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 { (left brace) and ending - * with } (right brace). - * @throws JSONException If the value is or contains an invalid number. + * + * @param value + * The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with { (left + * brace) and ending with } (right + * brace). + * @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)) { @@ -1434,27 +1475,27 @@ public static String valueToString(Object value) throws JSONException { if (value instanceof JSONString) { Object object; try { - object = ((JSONString)value).toJSONString(); + object = ((JSONString) value).toJSONString(); } catch (Exception e) { throw new JSONException(e); } if (object instanceof String) { - return (String)object; + 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) { + if (value instanceof Boolean || value instanceof JSONObject + || value instanceof JSONArray) { return value.toString(); } if (value instanceof Map) { - return new JSONObject((Map)value).toString(); + return new JSONObject((Map) value).toString(); } if (value instanceof Collection) { - return new JSONArray((Collection)value).toString(); + return new JSONArray((Collection) value).toString(); } if (value.getClass().isArray()) { return new JSONArray(value).toString(); @@ -1462,73 +1503,68 @@ public static String valueToString(Object value) throws JSONException { 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. - *

    - * 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); + /** + * 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. + *

    + * 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 { @@ -1624,5 +1660,5 @@ Writer write(Writer writer, int indentFactor, int indent) } catch (IOException exception) { throw new JSONException(exception); } - } + } } From 327e9a56fd2a0d9c16ed5711b209c5e679f43303 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 19 Apr 2013 11:10:37 -0700 Subject: [PATCH 04/31] Kim --- Kim.java | 374 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 Kim.java diff --git a/Kim.java b/Kim.java new file mode 100644 index 000000000..7f8a4754e --- /dev/null +++ b/Kim.java @@ -0,0 +1,374 @@ +package org.json; + + +/* + Copyright (c) 2013 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. + */ + +/** + * Kim makes immutable eight bit Unicode strings. If the MSB of a byte is set, + * then the next byte is a continuation byte. The last byte of a character + * never has the MSB reset. Every byte that is not the last byte has the MSB + * set. Kim stands for "Keep it minimal". A Unicode character is never longer + * than 3 bytes. Every byte contributes 7 bits to the character. ASCII is + * unmodified. + * + * Kim UTF-8 + * one byte U+007F U+007F + * two bytes U+3FFF U+07FF + * three bytes U+10FFF U+FFFF + * four bytes U+10FFFF + * + * Characters in the ranges U+0800..U+3FFF and U+10000..U+10FFFF will be one + * byte smaller when encoded in Kim compared to UTF-8. + * + * Kim is beneficial when using scripts such as Old South Arabian, Aramaic, + * Avestan, Balinese, Batak, Bopomofo, Buginese, Buhid, Carian, Cherokee, + * Coptic, Cyrillic, Deseret, Egyptian Hieroglyphs, Ethiopic, Georgian, + * Glagolitic, Gothic, Hangul Jamo, Hanunoo, Hiragana, Kanbun, Kaithi, + * Kannada, Katakana, Kharoshthi, Khmer, Lao, Lepcha, Limbu, Lycian, Lydian, + * Malayalam, Mandaic, Meroitic, Miao, Mongolian, Myanmar, New Tai Lue, + * Ol Chiki, Old Turkic, Oriya, Osmanya, Pahlavi, Parthian, Phags-Pa, + * Phoenician, Samaritan, Sharada, Sinhala, Sora Sompeng, Tagalog, Tagbanwa, + * Takri, Tai Le, Tai Tham, Tamil, Telugu, Thai, Tibetan, Tifinagh, UCAS. + * + * A kim object can be constructed from an ordinary UTF-16 string, or from a + * byte array. A kim object can produce a UTF-16 string. + * + * As with UTF-8, Kim can be sorted, and it is possible to detect character + * boundaries within a byte sequence. UTF-8 is one of the world's great + * inventions. While Kim is more efficient, it is not clear that it is worth + * the expense of transition. + * + * @version 2013-04-18 + */ +public class Kim { + + /** + * The byte array containing the kim's content. + */ + private byte[] bytes = null; + + /** + * The kim's hashcode, conforming to Java's hashcode recommendations. + */ + private int hashcode = 0; + + /** + * The number of bytes in the kim. The number of bytes can be as much as + * three times the number of characters. + */ + public int length = 0; + + /** + * The memoization of toString(). + */ + private String string = null; + + /** + * Make a kim from a portion of a byte array. + * + * @param bytes + * A byte array. + * @param from + * The index of the first byte. + * @param thru + * The index of the last byte plus one. + */ + public Kim(byte[] bytes, int from, int thru) { + +// As the bytes are copied into the new kim, a hashcode is computed using a +// modified Fletcher code. + + int sum = 1; + int value; + this.hashcode = 0; + this.length = thru - from; + if (this.length > 0) { + this.bytes = new byte[this.length]; + for (int at = 0; at < this.length; at += 1) { + value = (int) bytes[at + from] & 0xFF; + sum += value; + this.hashcode += sum; + this.bytes[at] = (byte) value; + } + this.hashcode += sum << 16; + } + } + + /** + * Make a kim from a byte array. + * + * @param bytes + * The byte array. + * @param length + * The number of bytes. + */ + public Kim(byte[] bytes, int length) { + this(bytes, 0, length); + } + + /** + * Make a new kim from a substring of an existing kim. The coordinates are + * in byte units, not character units. + * + * @param kim + * The source of bytes. + * @param from + * The point at which to take bytes. + * @param thru + * The point at which to stop taking bytes. + * @return the substring + */ + public Kim(Kim kim, int from, int thru) { + this(kim.bytes, from, thru); + } + + /** + * Make a kim from a string. + * + * @param string + * The string. + * @throws JSONException + * if surrogate pair mismatch. + */ + public Kim(String string) throws JSONException { + int stringLength = string.length(); + this.hashcode = 0; + this.length = 0; + +// First pass: Determine the length of the kim, allowing for the UTF-16 +// to UTF-32 conversion, and then the UTF-32 to kim conversion. + + if (stringLength > 0) { + for (int i = 0; i < stringLength; i += 1) { + int c = string.charAt(i); + if (c <= 0x7F) { + this.length += 1; + } else if (c <= 0x3FFF) { + this.length += 2; + } else { + if (c >= 0xD800 && c <= 0xDFFF) { + i += 1; + int d = string.charAt(i); + if (c > 0xDBFF || d < 0xDC00 || d > 0xDFFF) { + throw new JSONException("Bad UTF16"); + } + } + this.length += 3; + } + } + +// Second pass: Allocate a byte array and fill that array with the conversion +// while computing the hashcode. + + this.bytes = new byte[length]; + int at = 0; + int b; + int sum = 1; + for (int i = 0; i < stringLength; i += 1) { + int character = string.charAt(i); + if (character <= 0x7F) { + bytes[at] = (byte) character; + sum += character; + this.hashcode += sum; + at += 1; + } else if (character <= 0x3FFF) { + b = 0x80 | (character >>> 7); + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + b = character & 0x7F; + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + } else { + if (character >= 0xD800 && character <= 0xDBFF) { + i += 1; + character = (((character & 0x3FF) << 10) | (string + .charAt(i) & 0x3FF)) + 65536; + } + b = 0x80 | (character >>> 14); + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + b = 0x80 | ((character >>> 7) & 0xFF); + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + b = character & 0x7F; + bytes[at] = (byte) b; + sum += b; + this.hashcode += sum; + at += 1; + } + } + this.hashcode += sum << 16; + } + } + + /** + * Returns the character at the specified index. The index refers to byte + * values and ranges from 0 to length - 1. The index of the next character + * is at index + Kim.characterSize(kim.characterAt(index)). + * + * @param at + * the index of the char value. The first character is at 0. + * @returns a Unicode character between 0 and 0x10FFFF. + * @throws JSONException + * if at does not point to a valid character. + */ + public int characterAt(int at) throws JSONException { + int c = get(at); + if ((c & 0x80) == 0) { + return c; + } + int character; + int c1 = get(at + 1); + if ((c1 & 0x80) == 0) { + character = ((c & 0x7F) << 7) | c1; + if (character > 0x7F) { + return character; + } + } else { + int c2 = get(at + 2); + character = ((c & 0x7F) << 14) | ((c1 & 0x7F) << 7) | c2; + if ((c2 & 0x80) == 0 && character > 0x3FFF && character <= 0x10FFFF + && (character < 0xD800 || character > 0xDFFF)) { + return character; + } + } + throw new JSONException("Bad character at " + at); + } + + /** + * Returns the number of bytes needed to contain the character in Kim + * format. + * + * @param character + * a Unicode character between 0 and 0x10FFFF. + * @return 1, 2, or 3 + * @throws JSONException + * if the character is not representable in a kim. + */ + public static int characterSize(int character) throws JSONException { + if (character < 0 || character > 0x10FFFF) { + throw new JSONException("Bad character " + character); + } + return character <= 0x7F ? 1 : character <= 0x3FFF ? 2 : 3; + } + + /** + * Copy the contents of this kim to a byte array. + * + * @param bytes + * A byte array of sufficient size. + * @param at + * The position within the byte array to take the byes. + * @return The position immediately after the copy. + */ + public int copy(byte[] bytes, int at) { + System.arraycopy(this.bytes, 0, bytes, at, this.length); + return at + this.length; + } + + /** + * Two kim objects containing exactly the same bytes in the same order are + * equal to each other. + * + * @param obj + * the other kim with which to compare. + * @returns true if this and obj are both kim objects containing identical + * byte sequences. + */ + public boolean equals(Object obj) { + if (!(obj instanceof Kim)) { + return false; + } + Kim that = (Kim) obj; + if (this == that) { + return true; + } + if (this.hashcode != that.hashcode) { + return false; + } + return java.util.Arrays.equals(this.bytes, that.bytes); + } + + /** + * + * @param at + * The position of the byte. The first byte is at 0. + * @return The byte. + * @throws JSONException + * if there is no byte at that position. + */ + public int get(int at) throws JSONException { + if (at < 0 || at > this.length) { + throw new JSONException("Bad character at " + at); + } + return ((int) this.bytes[at]) & 0xFF; + } + + /** + * Returns a hash code value for the kim. + */ + public int hashCode() { + return this.hashcode; + } + + /** + * Produce a UTF-16 String from this kim. The number of codepoints in the + * string will not be greater than the number of bytes in the kim, although + * it could be less. + * + * @return The string. A kim memoizes its string representation. + * @throws JSONException + * if the kim is invalid. + */ + public String toString() throws JSONException { + if (this.string == null) { + int c; + int length = 0; + char chars[] = new char[this.length]; + for (int at = 0; at < this.length; at += characterSize(c)) { + c = this.characterAt(at); + if (c < 0x10000) { + chars[length] = (char) c; + length += 1; + } else { + chars[length] = (char) (0xD800 | ((c - 0x10000) >>> 10)); + length += 1; + chars[length] = (char) (0xDC00 | (c & 0x03FF)); + length += 1; + } + } + this.string = new String(chars, 0, length); + } + return this.string; + } +} From 0cdef7d816a6edc4c83124372847adf3f1b36567 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 19 Apr 2013 14:04:06 -0700 Subject: [PATCH 05/31] JSONzip --- zip/BitInputStream.java | 169 ++++++++++++ zip/BitOutputStream.java | 154 +++++++++++ zip/BitReader.java | 41 +++ zip/BitWriter.java | 51 ++++ zip/Compressor.java | 575 +++++++++++++++++++++++++++++++++++++++ zip/Decompressor.java | 325 ++++++++++++++++++++++ zip/Huff.java | 406 +++++++++++++++++++++++++++ zip/JSONzip.java | 281 +++++++++++++++++++ zip/Keep.java | 84 ++++++ zip/MapKeep.java | 160 +++++++++++ zip/None.java | 15 + zip/PostMortem.java | 47 ++++ zip/README | 2 + zip/TrieKeep.java | 396 +++++++++++++++++++++++++++ 14 files changed, 2706 insertions(+) create mode 100644 zip/BitInputStream.java create mode 100644 zip/BitOutputStream.java create mode 100644 zip/BitReader.java create mode 100644 zip/BitWriter.java create mode 100644 zip/Compressor.java create mode 100644 zip/Decompressor.java create mode 100644 zip/Huff.java create mode 100644 zip/JSONzip.java create mode 100644 zip/Keep.java create mode 100644 zip/MapKeep.java create mode 100644 zip/None.java create mode 100644 zip/PostMortem.java create mode 100644 zip/README create mode 100644 zip/TrieKeep.java diff --git a/zip/BitInputStream.java b/zip/BitInputStream.java new file mode 100644 index 000000000..7864ce150 --- /dev/null +++ b/zip/BitInputStream.java @@ -0,0 +1,169 @@ +package org.json.zip; + +import java.io.IOException; +import java.io.InputStream; + +/* + Copyright (c) 2013 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. + */ + +/** + * This is a big endian bit reader. It reads its bits from an InputStream. + * + * @version 2013-04-18 + * + */ +public class BitInputStream implements BitReader { + /** + * 2^n - 1 + */ + static final int[] mask = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; + + /** + * The number of bits remaining in the current byte. + */ + private int available = 0; + + /** + * Up to a byte's worth of unread bits. + */ + private int unread = 0; + + /** + * The source of the bits. + */ + private InputStream in; + + /** + * The number of bits read so far. This is used in padding. + */ + private long nrBits = 0; + + /** + * Make a BitReader from an InputStream. The BitReader will take bytes from + * the InputStream and unpack them into bits. + * + * @param in + * An InputStream. + */ + public BitInputStream(InputStream in) { + this.in = in; + } + + /** + * Make a BitReader. The first byte is passed in explicitly, the remaining + * bytes are obtained from the InputStream. This makes it possible to look + * at the first byte of a stream before deciding that it should be read as + * bits. + * + * @param in + * An InputStream + * @param firstByte + * The first byte, which was probably read from in. + */ + public BitInputStream(InputStream in, int firstByte) { + this.in = in; + this.unread = firstByte; + this.available = 8; + } + + /** + * Read one bit. + * + * @return true if it is a 1 bit. + */ + public boolean bit() throws IOException { + return read(1) != 0; + } + + /** + * Get the number of bits that have been read from this BitInputStream. + * This includes pad bits that have been skipped, but might not include + * bytes that have been read from the underlying InputStream that have not + * yet been delivered as bits. + * + * @return The number of bits read so far. + */ + public long nrBits() { + return this.nrBits; + } + + /** + * Check that the rest of the block has been padded with zeroes. + * + * @param factor + * The size of the block to pad. This will typically be 8, 16, + * 32, 64, 128, 256, etc. + * @return true if the block was zero padded, or false if the the padding + * contains any one bits. + * @throws IOException + */ + public boolean pad(int factor) throws IOException { + int padding = factor - (int) (this.nrBits % factor); + boolean result = true; + + for (int i = 0; i < padding; i += 1) { + if (bit()) { + result = false; + } + } + return result; + } + + /** + * Read some bits. + * + * @param width + * The number of bits to read. (0..32) + * @throws IOException + * @return the bits + */ + public int read(int width) throws IOException { + if (width == 0) { + return 0; + } + if (width < 0 || width > 32) { + throw new IOException("Bad read width."); + } + int result = 0; + while (width > 0) { + if (this.available == 0) { + this.unread = this.in.read(); + if (this.unread < 0) { + throw new IOException("Attempt to read past end."); + } + this.available = 8; + } + int take = width; + if (take > this.available) { + take = this.available; + } + result |= ((this.unread >>> (this.available - take)) & mask[take]) + << (width - take); + this.nrBits += take; + this.available -= take; + width -= take; + } + return result; + } +} diff --git a/zip/BitOutputStream.java b/zip/BitOutputStream.java new file mode 100644 index 000000000..526ad6111 --- /dev/null +++ b/zip/BitOutputStream.java @@ -0,0 +1,154 @@ +package org.json.zip; + +import java.io.IOException; +import java.io.OutputStream; + +/* + Copyright (c) 2013 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. + */ + +/** + * This is a big endian bit writer. It writes its bits to an OutputStream. + * + * @version 2013-04-18 + * + */ +public class BitOutputStream implements BitWriter { + + /** + * The number of bits written. + */ + private long nrBits = 0; + + /** + * The destination of the bits. + */ + private OutputStream out; + + /** + * Holder of bits not yet written. + */ + private int unwritten; + + /** + * The number of unused bits in this.unwritten. + */ + private int vacant = 8; + + /** + * Use an OutputStream to produce a BitWriter. The BitWriter will send its + * bits to the OutputStream as each byte is filled. + * + * @param out + * An Output Stream + */ + public BitOutputStream(OutputStream out) { + this.out = out; + } + + /** + * Returns the number of bits that have been written to this + * bitOutputStream. This may include bits that have not yet been written + * to the underlying outputStream. + */ + public long nrBits() { + return this.nrBits; + } + + /** + * Write a 1 bit. + * + * @throws IOException + */ + public void one() throws IOException { + write(1, 1); + } + + /** + * Pad the rest of the block with zeroes and flush. pad(8) flushes the last + * unfinished byte. The underlying OutputStream will be flushed. + * + * @param factor + * The size of the block to pad. This will typically be 8, 16, + * 32, 64, 128, 256, etc. + * @return this + * @throws IOException + */ + public void pad(int factor) throws IOException { + int padding = factor - (int) (nrBits % factor); + int excess = padding & 7; + if (excess > 0) { + this.write(0, excess); + padding -= excess; + } + while (padding > 0) { + this.write(0, 8); + padding -= 8; + } + this.out.flush(); + } + + /** + * Write some bits. Up to 32 bits can be written at a time. + * + * @param bits + * The bits to be written. + * @param width + * The number of bits to write. (0..32) + * @throws IOException + */ + public void write(int bits, int width) throws IOException { + if (bits == 0 && width == 0) { + return; + } + if (width <= 0 || width > 32) { + throw new IOException("Bad write width."); + } + while (width > 0) { + int actual = width; + if (actual > this.vacant) { + actual = this.vacant; + } + this.unwritten |= ((bits >>> (width - actual)) & + BitInputStream.mask[actual]) << (this.vacant - actual); + width -= actual; + nrBits += actual; + this.vacant -= actual; + if (this.vacant == 0) { + this.out.write(this.unwritten); + this.unwritten = 0; + this.vacant = 8; + } + } + } + + /** + * Write a 0 bit. + * + * @throws IOException + */ + public void zero() throws IOException { + write(0, 1); + + } +} diff --git a/zip/BitReader.java b/zip/BitReader.java new file mode 100644 index 000000000..1987729b8 --- /dev/null +++ b/zip/BitReader.java @@ -0,0 +1,41 @@ +package org.json.zip; + +import java.io.IOException; + +public interface BitReader { + /** + * Read one bit. + * + * @return true if it is a 1 bit. + */ + public boolean bit() throws IOException; + + /** + * Returns the number of bits that have been read from this bitreader. + * + * @return The number of bits read so far. + */ + public long nrBits(); + + /** + * Check that the rest of the block has been padded with zeroes. + * + * @param factor + * The size in bits of the block to pad. This will typically be + * 8, 16, 32, 64, 128, 256, etc. + * @return true if the block was zero padded, or false if the the padding + * contained any one bits. + * @throws IOException + */ + public boolean pad(int factor) throws IOException; + + /** + * Read some bits. + * + * @param width + * The number of bits to read. (0..32) + * @throws IOException + * @return the bits + */ + public int read(int width) throws IOException; +} diff --git a/zip/BitWriter.java b/zip/BitWriter.java new file mode 100644 index 000000000..83eb7e314 --- /dev/null +++ b/zip/BitWriter.java @@ -0,0 +1,51 @@ +package org.json.zip; + +import java.io.IOException; + +/** + * A bitwriter is a an interface that allows for doing output at the bit level. + * Most IO interfaces only allow for writing at the byte level or higher. + */ +public interface BitWriter { + /** + * Returns the number of bits that have been written to this bitwriter. + */ + public long nrBits(); + + /** + * Write a 1 bit. + * + * @throws IOException + */ + public void one() throws IOException; + + /** + * Pad the rest of the block with zeros and flush. + * + * @param factor + * The size in bits of the block to pad. This will typically be + * 8, 16, 32, 64, 128, 256, etc. + * @return true if the block was zero padded, or false if the the padding + * contains any one bits. + * @throws IOException + */ + public void pad(int factor) throws IOException; + + /** + * Write some bits. Up to 32 bits can be written at a time. + * + * @param bits + * The bits to be written. + * @param width + * The number of bits to write. (0..32) + * @throws IOException + */ + public void write(int bits, int width) throws IOException; + + /** + * Write a 0 bit. + * + * @throws IOException + */ + public void zero() throws IOException; +} diff --git a/zip/Compressor.java b/zip/Compressor.java new file mode 100644 index 000000000..6dddff420 --- /dev/null +++ b/zip/Compressor.java @@ -0,0 +1,575 @@ +package org.json.zip; + +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.Kim; + +/* + Copyright (c) 2013 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. + */ + +/** + * JSONzip is a compression scheme for JSON text. + * + * @author JSON.org + * @version 2013-04-18 + */ + +/** + * A compressor implements the compression behavior of JSONzip. It provides a + * zip method that takes a JSONObject or JSONArray and delivers a stream of + * bits to a BitWriter. + * + * FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY + * FOR PRODUCTION USE. + */ +public class Compressor extends JSONzip { + + /** + * A compressor outputs to a BitWriter. + */ + final BitWriter bitwriter; + + /** + * Create a new compressor. It may be used for an entire session or + * subsession. + * + * @param bitwriter + * The BitWriter this Compressor will output to. Don't forget to + * flush. + */ + public Compressor(BitWriter bitwriter) { + super(); + this.bitwriter = bitwriter; + } + + /** + * Return a 4 bit code for a character in a JSON number. The digits '0' to + * '9' get the codes 0 to 9. '.' is 10, '-' is 11, '+' is 12, and 'E' or + * 'e' is 13. + * + * @param digit + * An ASCII character from a JSIN number. + * @return + */ + private static int bcd(char digit) { + if (digit >= '0' && digit <= '9') { + return digit - '0'; + } + switch (digit) { + case '.': + return 10; + case '-': + return 11; + case '+': + return 12; + default: + return 13; + } + } + + /** + * Finish the final byte and flush the bitwriter. This does the same thing + * as pad(8). + * + * @throws JSONException + */ + public void flush() throws JSONException { + pad(8); + } + + /** + * Output a one bit. + * + * @throws IOException + */ + private void one() throws JSONException { + if (probe) { + log(1); + } + write(1, 1); + } + + /** + * Pad the output to fill an allotment of bits. + * + * @param factor + * The size of the bit allotment. A value of 8 will complete and + * flush the current byte. If you don't pad, then some of the + * last bits might not be sent to the Output Stream. + * @throws JSONException + */ + public void pad(int factor) throws JSONException { + try { + this.bitwriter.pad(factor); + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * Write a number, using the number of bits necessary to hold the number. + * + * @param integer + * The value to be encoded. + * @param width + * The number of bits to encode the value, between 0 and 32. + * @throws JSONException + */ + private void write(int integer, int width) throws JSONException { + try { + this.bitwriter.write(integer, width); + if (probe) { + log(integer, width); + } + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * Write an integer with Huffman encoding. The bit pattern that is written + * will be determined by the Huffman encoder. + * + * @param integer + * The value to be written. + * @param huff + * The Huffman encoder. + * @throws JSONException + */ + private void write(int integer, Huff huff) throws JSONException { + huff.write(integer, this.bitwriter); + } + + /** + * Write each of the bytes in a kim with Huffman encoding. + * + * @param kim + * A kim containing the bytes to be written. + * @param huff + * The Huffman encoder. + * @throws JSONException + */ + private void write(Kim kim, Huff huff) throws JSONException { + write(kim, 0, kim.length, huff); + } + + /** + * Write a range of bytes from a Kim with Huffman encoding. + * + * @param kim + * A Kim containing the bytes to be written. + * @param from + * The index of the first byte to write. + * @param thru + * The index after the last byte to write. + * @param huff + * The Huffman encoder. + * @throws JSONException + */ + private void write(Kim kim, int from, int thru, Huff huff) + throws JSONException { + for (int at = from; at < thru; at += 1) { + write(kim.get(at), huff); + } + } + + /** + * Write an integer, using the number of bits necessary to hold the number + * as determined by its keep, and increment its usage count in the keep. + * + * @param integer + * The value to be encoded. + * @param keep + * The Keep that the integer is one of. + * @throws JSONException + */ + private void writeAndTick(int integer, Keep keep) throws JSONException { + int width = keep.bitsize(); + keep.tick(integer); + if (probe) { + log("\"" + keep.value(integer) + "\""); + } + write(integer, width); + } + + /** + * Write a JSON Array. + * + * @param jsonarray + * @throws JSONException + */ + private void writeArray(JSONArray jsonarray) throws JSONException { + +// JSONzip has three encodings for arrays: +// The array is empty (zipEmptyArray). +// First value in the array is a string (zipArrayString). +// First value in the array is not a string (zipArrayValue). + + boolean stringy = false; + int length = jsonarray.length(); + if (length == 0) { + write(zipEmptyArray, 3); + } else { + Object value = jsonarray.get(0); + if (value == null) { + value = JSONObject.NULL; + } + if (value instanceof String) { + stringy = true; + write(zipArrayString, 3); + writeString((String) value); + } else { + write(zipArrayValue, 3); + writeValue(value); + } + for (int i = 1; i < length; i += 1) { + if (probe) { + log(); + } + value = jsonarray.get(i); + if (value == null) { + value = JSONObject.NULL; + } + if (value instanceof String != stringy) { + zero(); + } + one(); + if (value instanceof String) { + writeString((String) value); + } else { + writeValue(value); + } + } + zero(); + zero(); + + } + } + + /** + * Write a JSON value. + * + * @param value + * One of these types: JSONObject, JSONArray (or Map or + * Collection or array), Number (or Integer or Long or Double), + * or String, or Boolean, or JSONObject.NULL, or null. + * @throws JSONException + */ + private void writeJSON(Object value) throws JSONException { + if (JSONObject.NULL.equals(value)) { + write(zipNull, 3); + } else if (Boolean.FALSE.equals(value)) { + write(zipFalse, 3); + } else if (Boolean.TRUE.equals(value)) { + write(zipTrue, 3); + } else { + if (value instanceof Map) { + value = new JSONObject((Map) value); + } else if (value instanceof Collection) { + value = new JSONArray((Collection) value); + } else if (value.getClass().isArray()) { + value = new JSONArray(value); + } + if (value instanceof JSONObject) { + writeObject((JSONObject) value); + } else if (value instanceof JSONArray) { + writeArray((JSONArray) value); + } else { + throw new JSONException("Unrecognized object"); + } + } + } + + /** + * Write the name of an object property. Names have their own Keep and + * Huffman encoder because they are expected to be a more restricted set. + * + * @param name + * @throws JSONException + */ + private void writeName(String name) throws JSONException { + +// If this name has already been registered, then emit its integer and +// increment its usage count. + + Kim kim = new Kim(name); + int integer = this.namekeep.find(kim); + if (integer != none) { + one(); + writeAndTick(integer, this.namekeep); + } else { + +// Otherwise, emit the string with Huffman encoding, and register it. + + zero(); + write(kim, this.namehuff); + write(end, namehuff); + this.namekeep.register(kim); + } + } + + /** + * Write a JSON object. + * + * @param jsonobject + * @return + * @throws JSONException + */ + private void writeObject(JSONObject jsonobject) throws JSONException { + +// JSONzip has two encodings for objects: Empty Objects (zipEmptyObject) and +// non-empty objects (zipObject). + + boolean first = true; + Iterator keys = jsonobject.keys(); + while (keys.hasNext()) { + if (probe) { + log("\n"); + } + Object key = keys.next(); + if (key instanceof String) { + if (first) { + first = false; + write(zipObject, 3); + } else { + one(); + } + writeName((String) key); + Object value = jsonobject.get((String) key); + if (value instanceof String) { + zero(); + writeString((String) value); + } else { + one(); + writeValue(value); + } + } + } + if (first) { + write(zipEmptyObject, 3); + } else { + zero(); + } + } + + /** + * Write a string. + * + * @param string + * @throws JSONException + */ + private void writeString(String string) throws JSONException { + +// Special case for empty strings. + + if (string.length() == 0) { + zero(); + zero(); + write(end, this.substringhuff); + zero(); + } else { + Kim kim = new Kim(string); + +// Look for the string in the strings keep. If it is found, emit its +// integer and count that as a use. + + int integer = this.stringkeep.find(kim); + if (integer != none) { + one(); + writeAndTick(integer, this.stringkeep); + } else { + +// But if it is not found, emit the string's substrings. Register the string +// so that the next lookup will succeed. + + writeSubstring(kim); + this.stringkeep.register(kim); + } + } + } + + /** + * Write a string, attempting to match registered substrings. + * + * @param kim + * @throws JSONException + */ + private void writeSubstring(Kim kim) throws JSONException { + this.substringkeep.reserve(); + zero(); + int from = 0; + int thru = kim.length; + int until = thru - JSONzip.minSubstringLength; + int previousFrom = none; + int previousThru = 0; + +// Find a substring from the substring keep. + + while (true) { + int at; + int integer = none; + for (at = from; at <= until; at += 1) { + integer = this.substringkeep.match(kim, at, thru); + if (integer != none) { + break; + } + } + if (integer == none) { + break; + } + +// If a substring is found, emit any characters that were before the matched +// substring. Then emit the substring's integer and loop back to match the +// remainder with another substring. + + if (from != at) { + zero(); + write(kim, from, at, this.substringhuff); + write(end, this.substringhuff); + if (previousFrom != none) { + this.substringkeep.registerOne(kim, previousFrom, + previousThru); + previousFrom = none; + } + } + one(); + writeAndTick(integer, this.substringkeep); + from = at + this.substringkeep.length(integer); + if (previousFrom != none) { + this.substringkeep.registerOne(kim, previousFrom, + previousThru); + previousFrom = none; + } + previousFrom = at; + previousThru = from + 1; + } + +// If a substring is not found, then emit the remaining characters. + + zero(); + if (from < thru) { + write(kim, from, thru, this.substringhuff); + if (previousFrom != none) { + this.substringkeep.registerOne(kim, previousFrom, previousThru); + } + } + write(end, this.substringhuff); + zero(); + +// Register the string's substrings in the trie in hopes of future substring +// matching. + + substringkeep.registerMany(kim); + } + + /** + * Write a value. + * + * @param value + * One of these types: Boolean, Number, etc. + * @throws JSONException + */ + private void writeValue(Object value) throws JSONException { + if (value instanceof Number) { + String string = JSONObject.numberToString((Number) value); + int integer = this.values.find(string); + if (integer != none) { + write(2, 2); + writeAndTick(integer, this.values); + return; + } + if (value instanceof Integer || value instanceof Long) { + long longer = ((Number) value).longValue(); + if (longer >= 0 && longer < int14) { + write(0, 2); + if (longer < int4) { + zero(); + write((int) longer, 4); + return; + } + one(); + if (longer < int7) { + zero(); + write((int) longer, 7); + return; + } + one(); + write((int) longer, 14); + return; + } + } + write(1, 2); + for (int i = 0; i < string.length(); i += 1) { + write(bcd(string.charAt(i)), 4); + } + write(endOfNumber, 4); + this.values.register(string); + } else { + write(3, 2); + writeJSON(value); + } + } + + /** + * Output a zero bit. + * + * @throws JSONException + * + * @throws IOException + */ + private void zero() throws JSONException { + if (probe) { + log(0); + } + write(0, 1); + } + + /** + * Compress a JSONObject. + * + * @param jsonobject + * @throws JSONException + */ + public void zip(JSONObject jsonobject) throws JSONException { + begin(); + writeJSON(jsonobject); + } + + /** + * Compress a JSONArray. + * + * @param jsonarray + * @throws JSONException + */ + public void zip(JSONArray jsonarray) throws JSONException { + begin(); + writeJSON(jsonarray); + } +} diff --git a/zip/Decompressor.java b/zip/Decompressor.java new file mode 100644 index 000000000..108a2e2c1 --- /dev/null +++ b/zip/Decompressor.java @@ -0,0 +1,325 @@ +package org.json.zip; + +import java.io.UnsupportedEncodingException; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.Kim; + +/* + Copyright (c) 2012 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. + */ + +/** + * JSONzip is a compression scheme for JSON text. + * + * @author JSON.org + * @version 2013-04-18 + */ + +public class Decompressor extends JSONzip { + + /** + * A decompressor reads bits from a BitReader. + */ + BitReader bitreader; + + /** + * Create a new compressor. It may be used for an entire session or + * subsession. + * + * @param bitreader + * The bitreader that this decompressor will read from. + */ + public Decompressor(BitReader bitreader) { + super(); + this.bitreader = bitreader; + } + + /** + * Read one bit. + * + * @return true if 1, false if 0. + * @throws JSONException + */ + private boolean bit() throws JSONException { + boolean value; + try { + value = this.bitreader.bit(); + if (probe) { + log(value ? 1 : 0); + } + return value; + } catch (Throwable e) { + throw new JSONException(e); + } + + } + + /** + * Read enough bits to obtain an integer from the keep, and increase that + * integer's weight. + * + * @param keep + * @param bitreader + * @return + * @throws JSONException + */ + private Object getAndTick(Keep keep, BitReader bitreader) + throws JSONException { + try { + int width = keep.bitsize(); + int integer = bitreader.read(width); + Object value = keep.value(integer); + if (JSONzip.probe) { + JSONzip.log("\"" + value + "\""); + JSONzip.log(integer, width); + } + if (integer >= keep.length) { + throw new JSONException("Deep error."); + } + keep.tick(integer); + return value; + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * The pad method skips the bits that padded a stream to fit some + * allocation. pad(8) will skip over the remainder of a byte. + * + * @param factor + * @return true if all of the padding bits were zero. + * @throws JSONException + */ + public boolean pad(int factor) throws JSONException { + try { + return this.bitreader.pad(factor); + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * Read an integer, specifying its width in bits. + * + * @param width + * 0 to 32. + * @return An unsigned integer. + * @throws JSONException + */ + private int read(int width) throws JSONException { + try { + int value = this.bitreader.read(width); + if (probe) { + log(value, width); + } + return value; + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * Read a JSONArray. + * + * @param stringy + * true if the first element is a string. + * @return + * @throws JSONException + */ + private JSONArray readArray(boolean stringy) throws JSONException { + JSONArray jsonarray = new JSONArray(); + jsonarray.put(stringy ? readString() : readValue()); + while (true) { + if (probe) { + log("\n"); + } + if (!bit()) { + if (!bit()) { + return jsonarray; + } + jsonarray.put(stringy ? readValue() : readString()); + } else { + jsonarray.put(stringy ? readString() : readValue()); + } + } + } + + /** + * Read a JSON value. The type of value is determined by the next 3 bits. + * + * @return + * @throws JSONException + */ + private Object readJSON() throws JSONException { + switch (read(3)) { + case zipObject: + return readObject(); + case zipArrayString: + return readArray(true); + case zipArrayValue: + return readArray(false); + case zipEmptyObject: + return new JSONObject(); + case zipEmptyArray: + return new JSONArray(); + case zipTrue: + return Boolean.TRUE; + case zipFalse: + return Boolean.FALSE; + default: + return JSONObject.NULL; + } + } + + private String readName() throws JSONException { + byte[] bytes = new byte[65536]; + int length = 0; + if (!bit()) { + while (true) { + int c = this.namehuff.read(this.bitreader); + if (c == end) { + break; + } + bytes[length] = (byte) c; + length += 1; + } + if (length == 0) { + return ""; + } + Kim kim = new Kim(bytes, length); + this.namekeep.register(kim); + return kim.toString(); + } + return getAndTick(this.namekeep, this.bitreader).toString(); + } + + private JSONObject readObject() throws JSONException { + JSONObject jsonobject = new JSONObject(); + while (true) { + if (probe) { + log("\n"); + } + String name = readName(); + jsonobject.put(name, !bit() ? readString() : readValue()); + if (!bit()) { + return jsonobject; + } + } + } + + private String readString() throws JSONException { + Kim kim; + int from = 0; + int thru = 0; + int previousFrom = none; + int previousThru = 0; + if (bit()) { + return getAndTick(this.stringkeep, this.bitreader).toString(); + } + byte[] bytes = new byte[65536]; + boolean one = bit(); + this.substringkeep.reserve(); + while (true) { + if (one) { + from = thru; + kim = (Kim) getAndTick(this.substringkeep, this.bitreader); + thru = kim.copy(bytes, from); + if (previousFrom != none) { + this.substringkeep.registerOne(new Kim(bytes, previousFrom, + previousThru + 1)); + } + previousFrom = from; + previousThru = thru; + one = bit(); + } else { + from = none; + while (true) { + int c = this.substringhuff.read(this.bitreader); + if (c == end) { + break; + } + bytes[thru] = (byte) c; + thru += 1; + if (previousFrom != none) { + this.substringkeep.registerOne(new Kim(bytes, + previousFrom, previousThru + 1)); + previousFrom = none; + } + } + if (!bit()) { + break; + } + one = true; + } + } + if (thru == 0) { + return ""; + } + kim = new Kim(bytes, thru); + this.stringkeep.register(kim); + this.substringkeep.registerMany(kim); + return kim.toString(); + } + + private Object readValue() throws JSONException { + switch (read(2)) { + case 0: + return new Integer(read(!bit() ? 4 : !bit() ? 7 : 14)); + case 1: + byte[] bytes = new byte[256]; + int length = 0; + while (true) { + int c = read(4); + if (c == endOfNumber) { + break; + } + bytes[length] = bcd[c]; + length += 1; + } + Object value; + try { + value = JSONObject.stringToValue(new String(bytes, 0, length, + "US-ASCII")); + } catch (UnsupportedEncodingException e) { + throw new JSONException(e); + } + this.values.register(value); + return value; + case 2: + return getAndTick(this.values, this.bitreader); + case 3: + return readJSON(); + default: + throw new JSONException("Impossible."); + } + } + + public Object unzip() throws JSONException { + begin(); + return readJSON(); + } +} diff --git a/zip/Huff.java b/zip/Huff.java new file mode 100644 index 000000000..2e1d1c925 --- /dev/null +++ b/zip/Huff.java @@ -0,0 +1,406 @@ +package org.json.zip; + +import org.json.JSONException; + +/* + Copyright (c) 2013 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. + */ + +/** + * JSONzip is a compression scheme for JSON text. + * @author JSON.org + * @version 2013-04-18 + */ + +/** + * A Huffman encoder/decoder. It operates over a domain of integers, which may + * map to characters or other symbols. Symbols that are used frequently are + * given shorter codes than symbols that are used infrequently. This usually + * produces shorter messages. + * + * Initially, all of the symbols are given the same weight. The weight of a + * symbol is incremented by the tick method. The generate method is used to + * generate the encoding table. The table must be generated before encoding or + * decoding. You may regenerate the table with the latest weights at any time. + */ +public class Huff implements None, PostMortem { + + /** + * The number of symbols known to the encoder. + */ + private final int domain; + + /** + * An array that maps symbol values to symbols. + */ + private final Symbol[] symbols; + + /** + * The root of the decoding table, and the terminal of the encoding table. + */ + private Symbol table; + + /** + * Have any weights changed since the table was last generated? + */ + private boolean upToDate = false; + + /** + * The number of bits in the last symbol. This is used in tracing. + */ + private int width; + + private static class Symbol implements PostMortem { + public Symbol back; + public Symbol next; + public Symbol zero; + public Symbol one; + public final int integer; + public long weight; + + /** + * Make a symbol representing a character or other value. + * + * @param integer + * The symbol's number + */ + public Symbol(int integer) { + this.integer = integer; + this.weight = 0; + this.next = null; + this.back = null; + this.one = null; + this.zero = null; + } + + public boolean postMortem(PostMortem pm) { + boolean result = true; + Symbol that = (Symbol) pm; + + if (this.integer != that.integer || this.weight != that.weight) { + return false; + } + if ((this.back != null) != (that.back != null)) { + return false; + } + Symbol zero = this.zero; + Symbol one = this.one; + if (zero == null) { + if (that.zero != null) { + return false; + } + } else { + result = zero.postMortem(that.zero); + } + if (one == null) { + if (that.one != null) { + return false; + } + } else { + result = one.postMortem(that.one); + } + return result; + } + + } + + /** + * Construct a Huffman encoder/decoder. + * + * @param domain + * The number of values known to the object. + */ + public Huff(int domain) { + this.domain = domain; + int length = domain * 2 - 1; + this.symbols = new Symbol[length]; + +// Make the leaf symbols. + + for (int i = 0; i < domain; i += 1) { + symbols[i] = new Symbol(i); + } + +// SMake the links. + + for (int i = domain; i < length; i += 1) { + symbols[i] = new Symbol(none); + } + } + + /** + * Generate the encoding/decoding table. The table determines the bit + * sequences used by the read and write methods. + * + * @return this + */ + public void generate() { + if (!this.upToDate) { + +// Phase One: Sort the symbols by weight into a linked list. + + Symbol head = this.symbols[0]; + Symbol next; + Symbol previous = head; + Symbol symbol; + + this.table = null; + head.next = null; + for (int i = 1; i < this.domain; i += 1) { + symbol = symbols[i]; + +// If this symbol weights less than the head, then it becomes the new head. + + if (symbol.weight < head.weight) { + symbol.next = head; + head = symbol; + } else { + +// To save time, we will start the search from the previous symbol instead +// of the head unless the current symbol weights less than the previous symbol. + + if (symbol.weight < previous.weight) { + previous = head; + } + +// Find a connected pair (previous and next) where the symbol weighs the same +// or more than previous but less than the next. Link the symbol between them. + + while (true) { + next = previous.next; + if (next == null || symbol.weight < next.weight) { + break; + } + previous = next; + } + symbol.next = next; + previous.next = symbol; + previous = symbol; + } + } + +// Phase Two: Make new symbols from the two lightest symbols until only one +// symbol remains. The final symbol becomes the root of the table binary tree. + + int avail = this.domain; + Symbol first; + Symbol second; + previous = head; + while (true) { + first = head; + second = first.next; + head = second.next; + symbol = this.symbols[avail]; + avail += 1; + symbol.weight = first.weight + second.weight; + symbol.zero = first; + symbol.one = second; + symbol.back = null; + first.back = symbol; + second.back = symbol; + if (head == null) { + break; + } + +// Insert the new symbol back into the sorted list. + + if (symbol.weight < head.weight) { + symbol.next = head; + head = symbol; + previous = head; + } else { + while (true) { + next = previous.next; + if (next == null || symbol.weight < next.weight) { + break; + } + previous = next; + } + symbol.next = next; + previous.next = symbol; + previous = symbol; + } + + } + +// The last remaining symbol is the root of the table. + + this.table = symbol; + this.upToDate = true; + } + } + + private boolean postMortem(int integer) { + int[] bits = new int[this.domain]; + Symbol symbol = this.symbols[integer]; + if (symbol.integer != integer) { + return false; + } + int i = 0; + while (true) { + Symbol back = symbol.back; + if (back == null) { + break; + } + if (back.zero == symbol) { + bits[i] = 0; + } else if (back.one == symbol) { + bits[i] = 1; + } else { + return false; + } + i += 1; + symbol = back; + } + if (symbol != this.table) { + return false; + } + this.width = 0; + symbol = this.table; + while (symbol.integer == none) { + i -= 1; + symbol = bits[i] != 0 ? symbol.one : symbol.zero; + } + return symbol.integer == integer && i == 0; + } + + /** + * Compare two Huffman tables. + */ + public boolean postMortem(PostMortem pm) { + +// Go through every integer in the domain, generating its bit sequence, and +// then proving that that bit sequence produces the same integer. + + for (int integer = 0; integer < this.domain; integer += 1) { + if (!postMortem(integer)) { + JSONzip.log("\nBad huff "); + JSONzip.logchar(integer, integer); + return false; + } + } + return this.table.postMortem(((Huff) pm).table); + } + + /** + * Read bits until a symbol can be identified. The weight of the read + * symbol will be incremented. + * + * @param bitreader + * The source of bits. + * @return The integer value of the symbol. + * @throws JSONException + */ + public int read(BitReader bitreader) throws JSONException { + try { + this.width = 0; + Symbol symbol = this.table; + while (symbol.integer == none) { + this.width += 1; + symbol = bitreader.bit() ? symbol.one : symbol.zero; + } + tick(symbol.integer); + if (JSONzip.probe) { + JSONzip.logchar(symbol.integer, this.width); + } + return symbol.integer; + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * Increase by 1 the weight associated with a value. + * + * @param value + * The number of the symbol to tick + * @return this + */ + public void tick(int value) { + this.symbols[value].weight += 1; + this.upToDate = false; + } + + /** + * Increase by 1 the weight associated with a range of values. + * + * @param from + * The first symbol to tick + * @param to + * The last symbol to tick + * @return this + */ + public void tick(int from, int to) { + for (int value = from; value <= to; value += 1) { + tick(value); + } + } + + /** + * Recur from a symbol back, emitting bits. We recur before emitting to + * make the bits come out in the right order. + * + * @param symbol + * The symbol to write. + * @param bitwriter + * The bitwriter to write it to. + * @throws JSONException + */ + private void write(Symbol symbol, BitWriter bitwriter) + throws JSONException { + try { + Symbol back = symbol.back; + if (back != null) { + this.width += 1; + write(back, bitwriter); + if (back.zero == symbol) { + bitwriter.zero(); + } else { + bitwriter.one(); + } + } + } catch (Throwable e) { + throw new JSONException(e); + } + } + + /** + * Write the bits corresponding to a symbol. The weight of the symbol will + * be incremented. + * + * @param value + * The number of the symbol to write + * @param bitwriter + * The destination of the bits. + * @return this + * @throws JSONException + */ + public void write(int value, BitWriter bitwriter) throws JSONException { + this.width = 0; + write(this.symbols[value], bitwriter); + tick(value); + if (JSONzip.probe) { + JSONzip.logchar(value, this.width); + } + } +} diff --git a/zip/JSONzip.java b/zip/JSONzip.java new file mode 100644 index 000000000..2128742c2 --- /dev/null +++ b/zip/JSONzip.java @@ -0,0 +1,281 @@ +package org.json.zip; + + +/* + Copyright (c) 2013 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. + */ + +/** + * JSONzip is a binary-encoded JSON dialect. It is designed to compress the + * messages in a session. It is adaptive, so with each message seen, it should + * improve its compression. It minimizes JSON's overhead, reducing punctuation + * to a small number of bits. It uses Huffman encoding to reduce the average + * size of characters. It uses caches (or Keeps) to keep recently seen strings + * and values, so repetitive content (such as object keys) can be + * substantially reduced. It uses a character encoding called Kim (Keep it + * minimal) that is smaller than UTF-8 for most East European, African, and + * Asian scripts. + * + * JSONzip tends to reduce most content by about half. If there is a lot of + * recurring information, the reduction can be much more dramatic. + * + * FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT YET BEEN TESTED + * ADEQUATELY FOR PRODUCTION USE. + * + * @author JSON.org + * @version 2013-04-18 + */ +public abstract class JSONzip implements None, PostMortem { + /** + * Powers of 2. + */ + public static final int[] twos = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, + 1024, 2048, 4096, 8192, 16384, 32768, 65536 + }; + + /** + * The characters in JSON numbers can be reduced to 4 bits each. + */ + public static final byte[] bcd = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '+', 'E' + }; + + /** + * The number of integers that can be encoded in 4 bits. + */ + public static final long int4 = 16; + + /** + * The number of integers that can be encoded in 7 bits. + */ + public static final long int7 = 128; + + /** + * The number of integers that can be encoded in 14 bits. + */ + public static final long int14 = 16384; + + /** + * The end of string code. + */ + public static final int end = 256; + + /** + * The end of number code. + */ + public static final int endOfNumber = bcd.length; + + /** + * The maximum substring length when registering many. The registration of + * one substring may be longer. + */ + public static final int maxSubstringLength = 10; + + /** + * The minimum substring length. + */ + public static final int minSubstringLength = 3; + + /** + * The package supports tracing for debugging. + */ + public static final boolean probe = false; + + /** + * The maximum number of substrings added to the substrings keep per + * string. + */ + public static final int substringLimit = 40; + + /** + * The value code for an empty object. + */ + public static final int zipEmptyObject = 0; + + /** + * The value code for an empty array. + */ + public static final int zipEmptyArray = 1; + + /** + * The value code for true. + */ + public static final int zipTrue = 2; + + /** + * The value code for false. + */ + public static final int zipFalse = 3; + + /** + * The value code for null. + */ + public static final int zipNull = 4; + + /** + * The value code for a non-empty object. + */ + public static final int zipObject = 5; + + /** + * The value code for an array with a string as its first element. + */ + public static final int zipArrayString = 6; + + /** + * The value code for an array with a non-string value as its first element. + */ + public static final int zipArrayValue = 7; + + /** + * A Huffman encoder for names. + */ + protected final Huff namehuff; + + /** + * A place to keep the names (keys). + */ + protected final MapKeep namekeep; + + /** + * A place to keep the strings. + */ + protected final MapKeep stringkeep; + + /** + * A Huffman encoder for string values. + */ + protected final Huff substringhuff; + + /** + * A place to keep the strings. + */ + protected final TrieKeep substringkeep; + + /** + * A place to keep the values. + */ + protected final MapKeep values; + + /** + * Initialize the data structures. + */ + protected JSONzip() { + this.namehuff = new Huff(end + 1); + this.namekeep = new MapKeep(9); + this.stringkeep = new MapKeep(11); + this.substringhuff = new Huff(end + 1); + this.substringkeep = new TrieKeep(12); + this.values = new MapKeep(10); + +// Increase the weights of the ASCII letters, digits, and special characters +// because they are highly likely to occur more frequently. The weight of each +// character will increase as it is used. The Huffman encoder will tend to +// use fewer bits to encode heavier characters. + + this.namehuff.tick(' ', '}'); + this.namehuff.tick('a', 'z'); + this.namehuff.tick(end); + this.namehuff.tick(end); + this.substringhuff.tick(' ', '}'); + this.substringhuff.tick('a', 'z'); + this.substringhuff.tick(end); + this.substringhuff.tick(end); + } + + /** + * + */ + protected void begin() { + this.namehuff.generate(); + this.substringhuff.generate(); + } + + /** + * Write an end-of-line to the console. + */ + static void log() { + log("\n"); + } + + /** + * Write an integer to the console. + * + * @param integer + */ + static void log(int integer) { + log(integer + " "); + } + + /** + * Write two integers, separated by ':' to the console. + * + * @param integer + * @param width + */ + static void log(int integer, int width) { + log(integer + ":" + width + " "); + } + + /** + * Write a string to the console. + * + * @param string + */ + static void log(String string) { + System.out.print(string); + } + + /** + * Write a character or its code to the console. + * + * @param integer + * @param width + */ + static void logchar(int integer, int width) { + if (integer > ' ' && integer <= '}') { + log("'" + (char) integer + "':" + width + " "); + } else { + log(integer, width); + } + } + + /** + * This method is used for testing the implementation of JSONzip. It is not + * suitable for any other purpose. It is used to compare a Compressor and a + * Decompressor, verifying that the data structures that were built during + * zipping and unzipping were the same. + * + * @return true if the structures match. + */ + public boolean postMortem(PostMortem pm) { + JSONzip that = (JSONzip) pm; + return this.namehuff.postMortem(that.namehuff) + && this.namekeep.postMortem(that.namekeep) + && this.stringkeep.postMortem(that.stringkeep) + && this.substringhuff.postMortem(that.substringhuff) + && this.substringkeep.postMortem(that.substringkeep) + && this.values.postMortem(that.values); + } +} diff --git a/zip/Keep.java b/zip/Keep.java new file mode 100644 index 000000000..377e344e2 --- /dev/null +++ b/zip/Keep.java @@ -0,0 +1,84 @@ +package org.json.zip; + + +/* + Copyright (c) 2013 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 keep is a data structure that associates strings (or substrings) with + * numbers. This allows the sending of small integers instead of strings. + * + * @author JSON.org + * @version 2013-04-18 + */ +abstract class Keep implements None, PostMortem { + protected int capacity; + protected int length; + protected int power; + protected long[] uses; + + public Keep(int bits) { + this.capacity = JSONzip.twos[bits]; + this.length = 0; + this.power = 0; + this.uses = new long[this.capacity]; + } + + /** + * When an item ages, its use count is reduced by at least half. + * + * @param use + * The current use count of an item. + * @return The new use count for that item. + */ + public static long age(long use) { + return use >= 32 ? 16 : use / 2; + } + + /** + * Return the number of bits required to contain an integer based on the + * current length of the keep. As the keep fills up, the number of bits + * required to identify one of its items goes up. + */ + public int bitsize() { + while (JSONzip.twos[this.power] < this.length) { + this.power += 1; + } + return this.power; + } + + /** + * Increase the usage count on an integer value. + */ + public void tick(int integer) { + this.uses[integer] += 1; + } + + /** + * Get the value associated with an integer. + * @param integer The number of an item in the keep. + * @return The value. + */ + abstract public Object value(int integer); +} diff --git a/zip/MapKeep.java b/zip/MapKeep.java new file mode 100644 index 000000000..1374e08d3 --- /dev/null +++ b/zip/MapKeep.java @@ -0,0 +1,160 @@ +package org.json.zip; + +import java.util.HashMap; + +import org.json.Kim; + +/* + Copyright (c) 2013 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 keep is an associative data structure that maintains usage counts of each + * of the associations in its keeping. When the keep becomes full, it purges + * little used associations, and ages the survivors. Each key is assigned an + * integer value. When the keep is compacted, each key can be given a new + * value. + */ +class MapKeep extends Keep { + private Object[] list; + private HashMap map; + + /** + * Create a new Keep. + * @param bits + * The capacity of the keep expressed in the number of bits + * required to hold an integer. + */ + public MapKeep(int bits) { + super(bits); + this.list = new Object[this.capacity]; + this.map = new HashMap(this.capacity); + } + + /** + * Compact the keep. A keep may contain at most this.capacity elements. + * The keep contents can be reduced by deleting all elements with low use + * counts, and by reducing the use counts of the survivors. + */ + private void compact() { + int from = 0; + int to = 0; + while (from < this.capacity) { + Object key = this.list[from]; + long usage = age(this.uses[from]); + if (usage > 0) { + this.uses[to] = usage; + this.list[to] = key; + this.map.put(key, new Integer(to)); + to += 1; + } else { + this.map.remove(key); + } + from += 1; + } + if (to < this.capacity) { + this.length = to; + } else { + this.map.clear(); + this.length = 0; + } + this.power = 0; + } + + /** + * Find the integer value associated with this key, or nothing if this key + * is not in the keep. + * + * @param key + * An object. + * @return An integer + */ + public int find(Object key) { + Object o = this.map.get(key); + return o instanceof Integer ? ((Integer) o).intValue() : none; + } + + public boolean postMortem(PostMortem pm) { + MapKeep that = (MapKeep) pm; + if (this.length != that.length) { + JSONzip.log(this.length + " <> " + that.length); + return false; + } + for (int i = 0; i < this.length; i += 1) { + boolean b; + if (this.list[i] instanceof Kim) { + b = ((Kim) this.list[i]).equals(that.list[i]); + } else { + Object o = this.list[i]; + Object q = that.list[i]; + if (o instanceof Number) { + o = o.toString(); + } + if (q instanceof Number) { + q = q.toString(); + } + b = o.equals(q); + } + if (!b) { + JSONzip.log("\n[" + i + "]\n " + this.list[i] + "\n " + + that.list[i] + "\n " + this.uses[i] + "\n " + + that.uses[i]); + return false; + } + } + return true; + } + + /** + * Register a value in the keep. Compact the keep if it is full. The next + * time this value is encountered, its integer can be sent instead. + * @param value A value. + */ + public void register(Object value) { + if (JSONzip.probe) { + int integer = find(value); + if (integer >= 0) { + JSONzip.log("\nDuplicate key " + value); + } + } + if (this.length >= this.capacity) { + compact(); + } + this.list[this.length] = value; + this.map.put(value, new Integer(this.length)); + this.uses[this.length] = 1; + if (JSONzip.probe) { + JSONzip.log("<" + this.length + " " + value + "> "); + } + this.length += 1; + } + + /** + * Return the value associated with the integer. + * @param integer The number of an item in the keep. + * @return The value. + */ + public Object value(int integer) { + return this.list[integer]; + } +} diff --git a/zip/None.java b/zip/None.java new file mode 100644 index 000000000..818e68d8f --- /dev/null +++ b/zip/None.java @@ -0,0 +1,15 @@ +package org.json.zip; + +/** + * None is an interface that makes the constant none (short for + * negative one or long for -1) available to any class that implements it. + * The none value is used to stand for an integer that is not an integer, + * such as the negative result of a search. + */ +public interface None { + /** + * Negative One. + */ + public static final int none = -1; + +} diff --git a/zip/PostMortem.java b/zip/PostMortem.java new file mode 100644 index 000000000..22416d700 --- /dev/null +++ b/zip/PostMortem.java @@ -0,0 +1,47 @@ +package org.json.zip; + +/* + Copyright (c) 2013 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. + */ + +/** + * The PostMortem interface allows for testing the internal state of JSONzip + * processors. Testing that JSONzip can compress an object and reproduce a + * corresponding object is not sufficient. Complete testing requires that the + * same internal data structures were constructed on both ends. If those + * structures are not equivalent, then it is likely that the implementations + * are not correct, even if convention tests are passed. + * + * PostMortem allows for testing of deep structures without breaking + * encapsulation. + */ +public interface PostMortem { + /** + * Determine if two objects are equivalent. + * + * @param pm + * Another object of the same type. + * @return true if they match. + */ + public boolean postMortem(PostMortem pm); +} diff --git a/zip/README b/zip/README new file mode 100644 index 000000000..93e6470b7 --- /dev/null +++ b/zip/README @@ -0,0 +1,2 @@ +FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY FOR +PRODUCTION USE. diff --git a/zip/TrieKeep.java b/zip/TrieKeep.java new file mode 100644 index 000000000..dcb13c7a0 --- /dev/null +++ b/zip/TrieKeep.java @@ -0,0 +1,396 @@ +package org.json.zip; + +import org.json.Kim; + +/* + Copyright (c) 2013 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 TrieKeep is a Keep that implements a Trie. + */ +class TrieKeep extends Keep { + + /** + * The trie is made of nodes. + */ + class Node implements PostMortem { + private int integer; + private Node[] next; + + /** + * Each non-leaf node contains links to up to 256 next nodes. Each node + * has an integer value. + */ + public Node() { + this.integer = none; + this.next = null; + } + + /** + * Get one of a node's 256 links. If it is a leaf node, it returns + * null. + * + * @param cell + * A integer between 0 and 255. + * @return + */ + public Node get(int cell) { + return this.next == null ? null : this.next[cell]; + } + + /** + * Get one of a node's 256 links. If it is a leap node, it returns + * null. The argument is treated as an unsigned integer. + * + * @param cell + * A byte. + * @return + */ + public Node get(byte cell) { + return get(((int) cell) & 0xFF); + } + + /** + * Compare two nodes. Their lengths must be equal. Their links must + * also compare. + */ + public boolean postMortem(PostMortem pm) { + Node that = (Node) pm; + if (that == null) { + JSONzip.log("\nMisalign"); + return false; + } + if (this.integer != that.integer) { + JSONzip.log("\nInteger " + this.integer + " <> " + + that.integer); + return false; + } + if (this.next == null) { + if (that.next == null) { + return true; + } + JSONzip.log("\nNext is null " + this.integer); + return false; + } + for (int i = 0; i < 256; i += 1) { + Node node = this.next[i]; + if (node != null) { + if (!node.postMortem(that.next[i])) { + return false; + } + } else if (that.next[i] != null) { + JSONzip.log("\nMisalign " + i); + return false; + } + } + return true; + } + + /** + * Set a node's link to another node. + * + * @param cell + * An integer between 0 and 255. + * @param node + * The new value for the cell. + */ + public void set(int cell, Node node) { + if (this.next == null) { + this.next = new Node[256]; + } + if (JSONzip.probe) { + if (node == null || this.next[cell] != null) { + JSONzip.log("\nUnexpected set.\n"); + } + } + this.next[cell] = node; + } + + /** + * Set a node's link to another node. + * + * @param cell + * A byte. + * @param node + * The new value for the cell. + */ + public void set(byte cell, Node node) { + set(((int) cell) & 0xFF, node); + } + + /** + * Get one of a node's 256 links. It will not return null. If there is + * no link, then a link is manufactured. + * + * @param cell + * A integer between 0 and 255. + * @return + */ + public Node vet(int cell) { + Node node = get(cell); + if (node == null) { + node = new Node(); + set(cell, node); + } + return node; + } + + /** + * Get one of a node's 256 links. It will not return null. If there is + * no link, then a link is manufactured. + * + * @param cell + * A byte. + * @return + */ + public Node vet(byte cell) { + return vet(((int) cell) & 0xFF); + } + } + + private int[] froms; + private int[] thrus; + private Node root; + private Kim[] kims; + + /** + * Create a new Keep of kims. + * + * @param bits + * The log2 of the capacity of the Keep. For example, if bits is + * 12, then the keep's capacity will be 4096. + */ + public TrieKeep(int bits) { + super(bits); + this.froms = new int[this.capacity]; + this.thrus = new int[this.capacity]; + this.kims = new Kim[this.capacity]; + this.root = new Node(); + } + + /** + * Get the kim associated with an integer. + * + * @param integer + * @return + */ + public Kim kim(int integer) { + Kim kim = this.kims[integer]; + int from = this.froms[integer]; + int thru = this.thrus[integer]; + if (from != 0 || thru != kim.length) { + kim = new Kim(kim, from, thru); + this.froms[integer] = 0; + this.thrus[integer] = kim.length; + this.kims[integer] = kim; + } + return kim; + } + + /** + * Get the length of the Kim associated with an integer. This is sometimes + * much faster than get(integer).length. + * + * @param integer + * @return + */ + public int length(int integer) { + return this.thrus[integer] - this.froms[integer]; + } + + /** + * Find the integer value associated with this key, or nothing if this key + * is not in the keep. + * + * @param key + * An object. + * @return An integer + */ + public int match(Kim kim, int from, int thru) { + Node node = this.root; + int best = none; + for (int at = from; at < thru; at += 1) { + node = node.get(kim.get(at)); + if (node == null) { + break; + } + if (node.integer != none) { + best = node.integer; + } + from += 1; + } + return best; + } + + public boolean postMortem(PostMortem pm) { + boolean result = true; + TrieKeep that = (TrieKeep) pm; + if (this.length != that.length) { + JSONzip.log("\nLength " + this.length + " <> " + that.length); + return false; + } + if (this.capacity != that.capacity) { + JSONzip.log("\nCapacity " + this.capacity + " <> " + + that.capacity); + return false; + } + for (int i = 0; i < this.length; i += 1) { + Kim thiskim = this.kim(i); + Kim thatkim = that.kim(i); + if (!thiskim.equals(thatkim)) { + JSONzip.log("\n[" + i + "] " + thiskim + " <> " + thatkim); + result = false; + } + } + return result && this.root.postMortem(that.root); + } + + public void registerMany(Kim kim) { + int length = kim.length; + int limit = this.capacity - this.length; + if (limit > JSONzip.substringLimit) { + limit = JSONzip.substringLimit; + } + int until = length - (JSONzip.minSubstringLength - 1); + for (int from = 0; from < until; from += 1) { + int len = length - from; + if (len > JSONzip.maxSubstringLength) { + len = JSONzip.maxSubstringLength; + } + len += from; + Node node = this.root; + for (int at = from; at < len; at += 1) { + Node next = node.vet(kim.get(at)); + if (next.integer == none + && at - from >= (JSONzip.minSubstringLength - 1)) { + next.integer = this.length; + this.uses[this.length] = 1; + this.kims[this.length] = kim; + this.froms[this.length] = from; + this.thrus[this.length] = at + 1; + if (JSONzip.probe) { + try { + JSONzip.log("<<" + this.length + " " + + new Kim(kim, from, at + 1) + ">> "); + } catch (Throwable ignore) { + } + } + this.length += 1; + limit -= 1; + if (limit <= 0) { + return; + } + } + node = next; + } + } + } + + public void registerOne(Kim kim) { + int integer = registerOne(kim, 0, kim.length); + if (integer != none) { + this.kims[integer] = kim; + } + } + + public int registerOne(Kim kim, int from, int thru) { + if (this.length < this.capacity) { + Node node = this.root; + for (int at = from; at < thru; at += 1) { + node = node.vet(kim.get(at)); + } + if (node.integer == none) { + int integer = this.length; + node.integer = integer; + this.uses[integer] = 1; + this.kims[integer] = kim; + this.froms[integer] = from; + this.thrus[integer] = thru; + if (JSONzip.probe) { + try { + JSONzip.log("<<" + integer + " " + new Kim(kim, from, thru) + ">> "); + } catch (Throwable ignore) { + } + } + this.length += 1; + return integer; + } + } + return none; + } + + /** + * Reserve space in the keep, compacting if necessary. A keep may contain + * at most -capacity- elements. The keep contents can be reduced by + * deleting all elements with low use counts, rebuilding the trie with the + * survivors. + */ + public void reserve() { + if (this.capacity - this.length < JSONzip.substringLimit) { + int from = 0; + int to = 0; + this.root = new Node(); + while (from < this.capacity) { + if (this.uses[from] > 1) { + Kim kim = this.kims[from]; + int thru = this.thrus[from]; + Node node = this.root; + for (int at = this.froms[from]; at < thru; at += 1) { + Node next = node.vet(kim.get(at)); + node = next; + } + node.integer = to; + this.uses[to] = age(this.uses[from]); + this.froms[to] = this.froms[from]; + this.thrus[to] = thru; + this.kims[to] = kim; + to += 1; + } + from += 1; + } + +// It is possible, but highly unlikely, that too many items survive. +// If that happens, clear the keep. + + if (this.capacity - to < JSONzip.substringLimit) { + this.power = 0; + this.root = new Node(); + to = 0; + } + this.length = to; + while (to < this.capacity) { + this.uses[to] = 0; + this.kims[to] = null; + this.froms[to] = 0; + this.thrus[to] = 0; + to += 1; + + } + } + } + + public Object value(int integer) { + return kim(integer); + } +} From 1a85fe7f57d3452e401da435caaf0b8325efe32d Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Wed, 24 Apr 2013 11:34:50 -0700 Subject: [PATCH 06/31] comments --- Kim.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Kim.java b/Kim.java index 7f8a4754e..d4770b566 100644 --- a/Kim.java +++ b/Kim.java @@ -55,10 +55,9 @@ of this software and associated documentation files (the "Software"), to deal * A kim object can be constructed from an ordinary UTF-16 string, or from a * byte array. A kim object can produce a UTF-16 string. * - * As with UTF-8, Kim can be sorted, and it is possible to detect character - * boundaries within a byte sequence. UTF-8 is one of the world's great - * inventions. While Kim is more efficient, it is not clear that it is worth - * the expense of transition. + * As with UTF-8, it is possible to detect character boundaries within a byte + * sequence. UTF-8 is one of the world's great inventions. While Kim is more + * efficient, it is not clear that it is worth the expense of transition. * * @version 2013-04-18 */ @@ -70,7 +69,7 @@ public class Kim { private byte[] bytes = null; /** - * The kim's hashcode, conforming to Java's hashcode recommendations. + * The kim's hashcode, conforming to Java's hashcode conventions. */ private int hashcode = 0; @@ -158,7 +157,7 @@ public Kim(String string) throws JSONException { this.length = 0; // First pass: Determine the length of the kim, allowing for the UTF-16 -// to UTF-32 conversion, and then the UTF-32 to kim conversion. +// to UTF-32 conversion, and then the UTF-32 to Kim conversion. if (stringLength > 0) { for (int i = 0; i < stringLength; i += 1) { @@ -320,7 +319,7 @@ public boolean equals(Object obj) { } /** - * + * Get a byte from a kim. * @param at * The position of the byte. The first byte is at 0. * @return The byte. @@ -348,7 +347,7 @@ public int hashCode() { * * @return The string. A kim memoizes its string representation. * @throws JSONException - * if the kim is invalid. + * if the kim is not valid. */ public String toString() throws JSONException { if (this.string == null) { From 1f7056edc2835df2fdc9434edf77d3739d93e91d Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 26 May 2013 00:25:41 -0700 Subject: [PATCH 07/31] kensenjohn --- Property.java | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Property.java diff --git a/Property.java b/Property.java new file mode 100644 index 000000000..dbbd7ef7e --- /dev/null +++ b/Property.java @@ -0,0 +1,74 @@ +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.util.Enumeration; +import java.util.Iterator; +import java.util.Properties; + +/** + * Converts a Property file data into JSONObject and back. + * @author JSON.org + * @version 2013-05-23 + */ +public class Property { + /** + * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. + * @param properties java.util.Properties + * @return JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { + JSONObject jo = new JSONObject(); + if (properties != null && !properties.isEmpty()) { + Enumeration enumProperties = properties.propertyNames(); + while(enumProperties.hasMoreElements()) { + String name = (String)enumProperties.nextElement(); + jo.put(name, properties.getProperty(name)); + } + } + return jo; + + } + + /** + * Converts the JSONObject into a property file object. + * @param jo JSONObject + * @return java.util.Properties + * @throws JSONException + */ + public static Properties toProperties(JSONObject jo) throws JSONException { + Properties properties = new Properties(); + if (jo != null) { + Iterator keys = jo.keys(); + + while (keys.hasNext()) { + String name = keys.next().toString(); + properties.put(name, jo.getString(name)); + } + } + return properties; + } +} \ No newline at end of file From 34f327e6d070568256b314479be158589d391891 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 17 Jun 2013 17:10:27 -0700 Subject: [PATCH 08/31] out of the pool --- JSONObject.java | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index 9d2d5cee3..5ca5a45bc 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -90,22 +90,9 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2013-04-18 + * @version 2013-06-17 */ public class JSONObject { - /** - * The maximum number of keys in the key pool. - */ - private static final int keyPoolSize = 100; - - /** - * Key pooling is like string interning, but without permanently tying up - * memory. To help conserve memory, storage of duplicated key strings in - * JSONObjects will be avoided by using a key pool to manage unique key - * string objects. This is used by JSONObject.put(string, object). - */ - private static HashMap keyPool = new HashMap(keyPoolSize); - /** * JSONObject.NULL is equivalent to the value that JavaScript calls null, * whilst Java's null is equivalent to the value that JavaScript calls @@ -1147,21 +1134,11 @@ public JSONObject put(String key, Map value) throws JSONException { * If the value is non-finite number or if the key is null. */ public JSONObject put(String key, Object value) throws JSONException { - String pooled; if (key == null) { throw new NullPointerException("Null key."); } if (value != null) { testValidity(value); - pooled = (String) keyPool.get(key); - if (pooled == null) { - if (keyPool.size() >= keyPoolSize) { - keyPool = new HashMap(keyPoolSize); - } - keyPool.put(key, key); - } else { - key = pooled; - } this.map.put(key, value); } else { this.remove(key); From 4d86b05d3c6a72e88c476430d60676f9ae2fafab Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Thu, 14 Nov 2013 11:18:16 -0800 Subject: [PATCH 09/31] stringToValue --- XML.java | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/XML.java b/XML.java index d49784d6d..cea3abe7b 100755 --- a/XML.java +++ b/XML.java @@ -31,7 +31,7 @@ of this software and associated documentation files (the "Software"), to deal * This provides static methods to convert an XML text into a JSONObject, * and to covert a JSONObject into an XML text. * @author JSON.org - * @version 2012-10-26 + * @version 2013-11-12 */ public class XML { @@ -301,9 +301,6 @@ private static boolean parse(XMLTokener x, JSONObject context, * @return A simple JSON value. */ public static Object stringToValue(String string) { - if ("".equals(string)) { - return string; - } if ("true".equalsIgnoreCase(string)) { return Boolean.TRUE; } @@ -313,36 +310,26 @@ public static Object stringToValue(String string) { if ("null".equalsIgnoreCase(string)) { return JSONObject.NULL; } - if ("0".equals(string)) { - return new Integer(0); - } -// If it might be a number, try converting it. If that doesn't work, -// return the string. +// If it might be a number, try converting it, first as a Long, and then as a +// Double. If that doesn't work, return the string. try { char initial = string.charAt(0); - boolean negative = false; - if (initial == '-') { - initial = string.charAt(1); - negative = true; - } - if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') { - return string; - } - if ((initial >= '0' && initial <= '9')) { - if (string.indexOf('.') >= 0) { - return Double.valueOf(string); - } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) { - Long myLong = new Long(string); - if (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); - } else { - return myLong; - } + if (initial == '-' || (initial >= '0' && initial <= '9')) { + Long value = new Long(string); + if (value.toString().equals(string)) { + return value; } } } catch (Exception ignore) { + try { + Double value = new Double(string); + if (value.toString().equals(string)) { + return value; + } + } catch (Exception ignoreAlso) { + } } return string; } From cdaaf12557f7086657d5a0c34e565b1773c9834a Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 18 Apr 2014 16:16:03 -0700 Subject: [PATCH 10/31] JSONArray.remove --- JSONArray.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index 673a91927..e0af8fa7a 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -75,7 +75,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2013-04-18 + * @version 2014-04-18 */ public class JSONArray { @@ -814,7 +814,9 @@ public JSONArray put(int index, Object value) throws JSONException { */ public Object remove(int index) { Object o = this.opt(index); - this.myArrayList.remove(index); + if (index >= 0 && index < this.length()) { + this.myArrayList.remove(index); + } return o; } From 7ff3fa4e40cfd5b44570297080810aed5cce6086 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 21 Apr 2014 16:11:51 -0700 Subject: [PATCH 11/31] similar --- JSONArray.java | 41 ++++++++++++++++++++++++++++++++++++----- JSONObject.java | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index e0af8fa7a..e864a1e49 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -75,7 +75,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2014-04-18 + * @version 2014-04-21 */ public class JSONArray { @@ -813,11 +813,42 @@ public JSONArray put(int index, Object value) throws JSONException { * was no value. */ public Object remove(int index) { - Object o = this.opt(index); - if (index >= 0 && index < this.length()) { - this.myArrayList.remove(index); + return index >= 0 && index < this.length() + ? this.myArrayList.remove(index) + : null; + } + + /** + * Determine if two JSONArrays are similar. + * They must contain similar sequences. + * + * @param other The other JSONArray + * @return true if they are equal + */ + public boolean similar(Object other) { + if (!(other instanceof JSONArray)) { + return false; + } + int len = this.length(); + if (len != ((JSONArray)other).length()) { + return false; + } + for (int i = 0; i < len; i += 1) { + Object valueThis = this.get(i); + Object valueOther = ((JSONArray)other).get(i); + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } } - return o; + return true; } /** diff --git a/JSONObject.java b/JSONObject.java index 5ca5a45bc..20ba42f84 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -90,7 +90,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2013-06-17 + * @version 2014-04-21 */ public class JSONObject { /** @@ -1281,6 +1281,46 @@ public Object remove(String key) { return this.map.remove(key); } + /** + * Determine if two JSONObjects are similar. + * They must contain the same set of names which must be associated with + * similar values. + * + * @param other The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) { + try { + if (!(other instanceof JSONObject)) { + return false; + } + Set set = this.keySet(); + if (!set.equals(((JSONObject)other).keySet())) { + return false; + } + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + String name = (String)iterator.next(); + Object valueThis = this.get(name); + Object valueOther = ((JSONObject)other).get(name); + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } catch (Throwable exception) { + return false; + } + } + /** * Try to convert a string into a number, boolean, or null. If the string * can't be converted, return the string. From b7a1aee4e161cce361c20a8f797329e63f1ba84e Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 21 Apr 2014 16:13:10 -0700 Subject: [PATCH 12/31] log --- zip/Compressor.java | 10 ++-------- zip/Decompressor.java | 4 ++-- zip/JSONzip.java | 14 ++++++++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/zip/Compressor.java b/zip/Compressor.java index 6dddff420..07fb69197 100644 --- a/zip/Compressor.java +++ b/zip/Compressor.java @@ -38,7 +38,7 @@ of this software and associated documentation files (the "Software"), to deal * JSONzip is a compression scheme for JSON text. * * @author JSON.org - * @version 2013-04-18 + * @version 2014-04-21 */ /** @@ -110,9 +110,6 @@ public void flush() throws JSONException { * @throws IOException */ private void one() throws JSONException { - if (probe) { - log(1); - } write(1, 1); } @@ -351,7 +348,7 @@ private void writeObject(JSONObject jsonobject) throws JSONException { Iterator keys = jsonobject.keys(); while (keys.hasNext()) { if (probe) { - log("\n"); + log(); } Object key = keys.next(); if (key instanceof String) { @@ -545,9 +542,6 @@ private void writeValue(Object value) throws JSONException { * @throws IOException */ private void zero() throws JSONException { - if (probe) { - log(0); - } write(0, 1); } diff --git a/zip/Decompressor.java b/zip/Decompressor.java index 108a2e2c1..df130aece 100644 --- a/zip/Decompressor.java +++ b/zip/Decompressor.java @@ -35,7 +35,7 @@ of this software and associated documentation files (the "Software"), to deal * JSONzip is a compression scheme for JSON text. * * @author JSON.org - * @version 2013-04-18 + * @version 2014-04-21 */ public class Decompressor extends JSONzip { @@ -221,7 +221,7 @@ private JSONObject readObject() throws JSONException { JSONObject jsonobject = new JSONObject(); while (true) { if (probe) { - log("\n"); + log(); } String name = readName(); jsonobject.put(name, !bit() ? readString() : readValue()); diff --git a/zip/JSONzip.java b/zip/JSONzip.java index 2128742c2..10cb1819e 100644 --- a/zip/JSONzip.java +++ b/zip/JSONzip.java @@ -1,6 +1,5 @@ package org.json.zip; - /* Copyright (c) 2013 JSON.org @@ -27,7 +26,9 @@ of this software and associated documentation files (the "Software"), to deal /** * JSONzip is a binary-encoded JSON dialect. It is designed to compress the - * messages in a session. It is adaptive, so with each message seen, it should + * messages in a session in bandwidth constrained applications, such as mobile. + * + * JSONzip is adaptive, so with each message seen, it should * improve its compression. It minimizes JSON's overhead, reducing punctuation * to a small number of bits. It uses Huffman encoding to reduce the average * size of characters. It uses caches (or Keeps) to keep recently seen strings @@ -43,7 +44,7 @@ of this software and associated documentation files (the "Software"), to deal * ADEQUATELY FOR PRODUCTION USE. * * @author JSON.org - * @version 2013-04-18 + * @version 2014-04-21 */ public abstract class JSONzip implements None, PostMortem { /** @@ -230,12 +231,17 @@ static void log(int integer) { /** * Write two integers, separated by ':' to the console. + * The second integer is suppressed if it is 1. * * @param integer * @param width */ static void log(int integer, int width) { - log(integer + ":" + width + " "); + if (width == 1) { + log(integer); + } else { + log(integer + ":" + width + " "); + } } /** From 48d31b7f5c8e43321e4b2143a8a795c366ace6d9 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 28 Apr 2014 13:22:05 -0700 Subject: [PATCH 13/31] JSONzip value --- zip/Compressor.java | 8 ++++---- zip/Decompressor.java | 14 ++++++++++++-- zip/JSONzip.java | 12 ++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/zip/Compressor.java b/zip/Compressor.java index 07fb69197..5937d6612 100644 --- a/zip/Compressor.java +++ b/zip/Compressor.java @@ -38,7 +38,7 @@ of this software and associated documentation files (the "Software"), to deal * JSONzip is a compression scheme for JSON text. * * @author JSON.org - * @version 2014-04-21 + * @version 2014-04-28 */ /** @@ -75,7 +75,7 @@ public Compressor(BitWriter bitwriter) { * 'e' is 13. * * @param digit - * An ASCII character from a JSIN number. + * An ASCII character from a JSON number. * @return */ private static int bcd(char digit) { @@ -514,11 +514,11 @@ private void writeValue(Object value) throws JSONException { one(); if (longer < int7) { zero(); - write((int) longer, 7); + write((int)(longer - int4), 7); return; } one(); - write((int) longer, 14); + write((int)(longer - int7), 14); return; } } diff --git a/zip/Decompressor.java b/zip/Decompressor.java index df130aece..091ec54fc 100644 --- a/zip/Decompressor.java +++ b/zip/Decompressor.java @@ -35,7 +35,7 @@ of this software and associated documentation files (the "Software"), to deal * JSONzip is a compression scheme for JSON text. * * @author JSON.org - * @version 2014-04-21 + * @version 2014-04-28 */ public class Decompressor extends JSONzip { @@ -288,7 +288,17 @@ private String readString() throws JSONException { private Object readValue() throws JSONException { switch (read(2)) { case 0: - return new Integer(read(!bit() ? 4 : !bit() ? 7 : 14)); + int nr_bits = !bit() ? 4 : !bit() ? 7 : 14; + int integer = read(nr_bits); + switch (nr_bits) { + case 7: + integer += int4; + break; + case 14: + integer += int7; + break; + } + return new Integer(integer); case 1: byte[] bytes = new byte[256]; int length = 0; diff --git a/zip/JSONzip.java b/zip/JSONzip.java index 10cb1819e..a8b837fa3 100644 --- a/zip/JSONzip.java +++ b/zip/JSONzip.java @@ -44,7 +44,7 @@ of this software and associated documentation files (the "Software"), to deal * ADEQUATELY FOR PRODUCTION USE. * * @author JSON.org - * @version 2014-04-21 + * @version 2014-04-28 */ public abstract class JSONzip implements None, PostMortem { /** @@ -63,19 +63,19 @@ public abstract class JSONzip implements None, PostMortem { }; /** - * The number of integers that can be encoded in 4 bits. + * The first positive integer than cannot be encoded in 4 bits. */ public static final long int4 = 16; /** - * The number of integers that can be encoded in 7 bits. + * The first positive integer than cannot be encoded in 7 bits. */ - public static final long int7 = 128; + public static final long int7 = 144; /** - * The number of integers that can be encoded in 14 bits. + * The first positive integer than cannot be encoded in 14 bits. */ - public static final long int14 = 16384; + public static final long int14 = 16528; /** * The end of string code. From a9a07623834b6da2c2c3d47b440e002dedfd9e17 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 5 May 2014 15:09:32 -0700 Subject: [PATCH 14/31] Java 1.8. --- CDL.java | 4 +- Cookie.java | 338 +++++++++++------------ CookieList.java | 179 ++++++------ HTTP.java | 10 +- HTTPTokener.java | 4 +- JSONArray.java | 22 +- JSONException.java | 6 +- JSONML.java | 48 ++-- JSONObject.java | 80 +++--- JSONTokener.java | 16 +- JSONWriter.java | 2 +- Kim.java | 1 - Property.java | 10 +- README | 2 +- XML.java | 51 ++-- XMLTokener.java | 20 +- zip/BitInputStream.java | 52 ++-- zip/BitOutputStream.java | 32 +-- zip/BitReader.java | 7 +- zip/BitWriter.java | 10 +- zip/Huff.java | 47 ++-- zip/JSONzip.java | 99 +++---- zip/Keep.java | 137 ++++++++- zip/PostMortem.java | 4 +- zip/{Decompressor.java => Unzipper.java} | 185 ++++++------- zip/{Compressor.java => Zipper.java} | 200 ++++---------- 26 files changed, 747 insertions(+), 819 deletions(-) rename zip/{Decompressor.java => Unzipper.java} (64%) rename zip/{Compressor.java => Zipper.java} (70%) diff --git a/CDL.java b/CDL.java index 0fc3cf828..995b1d478 100755 --- a/CDL.java +++ b/CDL.java @@ -41,7 +41,7 @@ of this software and associated documentation files (the "Software"), to deal * The names for the elements in the JSONObjects can be taken from the names * in the first row. * @author JSON.org - * @version 2012-11-13 + * @version 2014-05-03 */ public class CDL { @@ -142,7 +142,7 @@ public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) * @return A string ending in NEWLINE. */ public static String rowToString(JSONArray ja) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < ja.length(); i += 1) { if (i > 0) { sb.append(','); diff --git a/Cookie.java b/Cookie.java index 9cf5ce2c5..1867dbd74 100755 --- a/Cookie.java +++ b/Cookie.java @@ -1,169 +1,169 @@ -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. -*/ - -/** - * Convert a web browser cookie specification to a JSONObject and back. - * JSON and Cookies are both notations for name/value pairs. - * @author JSON.org - * @version 2010-12-24 - */ -public class Cookie { - - /** - * Produce a copy of a string in which the characters '+', '%', '=', ';' - * and control characters are replaced with "%hh". This is a gentle form - * of URL encoding, attempting to cause as little distortion to the - * string as possible. The characters '=' and ';' are meta characters in - * cookies. By convention, they are escaped using the URL-encoding. This is - * only a convention, not a standard. Often, cookies are expected to have - * encoded values. We encode '=' and ';' because we must. We encode '%' and - * '+' because they are meta characters in URL encoding. - * @param string The source string. - * @return The escaped result. - */ - public static String escape(String string) { - char c; - String s = string.trim(); - StringBuffer sb = new StringBuffer(); - int length = s.length(); - for (int i = 0; i < length; i += 1) { - c = s.charAt(i); - if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { - sb.append('%'); - sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); - sb.append(Character.forDigit((char)(c & 0x0f), 16)); - } else { - sb.append(c); - } - } - return sb.toString(); - } - - - /** - * Convert a cookie specification string into a JSONObject. The string - * will contain a name value pair separated by '='. The name and the value - * will be unescaped, possibly converting '+' and '%' sequences. The - * cookie properties may follow, separated by ';', also represented as - * name=value (except the secure property, which does not have a value). - * The name will be stored under the key "name", and the value will be - * stored under the key "value". This method does not do checking or - * validation of the parameters. It only converts the cookie string into - * a JSONObject. - * @param string The cookie specification string. - * @return A JSONObject containing "name", "value", and possibly other - * members. - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - String name; - JSONObject jo = new JSONObject(); - Object value; - JSONTokener x = new JSONTokener(string); - jo.put("name", x.nextTo('=')); - x.next('='); - jo.put("value", x.nextTo(';')); - x.next(); - while (x.more()) { - name = unescape(x.nextTo("=;")); - if (x.next() != '=') { - if (name.equals("secure")) { - value = Boolean.TRUE; - } else { - throw x.syntaxError("Missing '=' in cookie parameter."); - } - } else { - value = unescape(x.nextTo(';')); - x.next(); - } - jo.put(name, value); - } - return jo; - } - - - /** - * Convert a JSONObject into a cookie specification string. The JSONObject - * must contain "name" and "value" members. - * If the JSONObject contains "expires", "domain", "path", or "secure" - * members, they will be appended to the cookie specification string. - * All other members are ignored. - * @param jo A JSONObject - * @return A cookie specification string - * @throws JSONException - */ - public static String toString(JSONObject jo) throws JSONException { - StringBuffer sb = new StringBuffer(); - - sb.append(escape(jo.getString("name"))); - sb.append("="); - sb.append(escape(jo.getString("value"))); - if (jo.has("expires")) { - sb.append(";expires="); - sb.append(jo.getString("expires")); - } - if (jo.has("domain")) { - sb.append(";domain="); - sb.append(escape(jo.getString("domain"))); - } - if (jo.has("path")) { - sb.append(";path="); - sb.append(escape(jo.getString("path"))); - } - if (jo.optBoolean("secure")) { - sb.append(";secure"); - } - return sb.toString(); - } - - /** - * Convert %hh sequences to single characters, and - * convert plus to space. - * @param string A string that may contain - * + (plus) and - * %hh sequences. - * @return The unescaped string. - */ - public static String unescape(String string) { - int length = string.length(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < length; ++i) { - char c = string.charAt(i); - if (c == '+') { - c = ' '; - } else if (c == '%' && i + 2 < length) { - int d = JSONTokener.dehexchar(string.charAt(i + 1)); - int e = JSONTokener.dehexchar(string.charAt(i + 2)); - if (d >= 0 && e >= 0) { - c = (char)(d * 16 + e); - i += 2; - } - } - sb.append(c); - } - return sb.toString(); - } -} +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. +*/ + +/** + * Convert a web browser cookie specification to a JSONObject and back. + * JSON and Cookies are both notations for name/value pairs. + * @author JSON.org + * @version 2014-05-03 + */ +public class Cookie { + + /** + * Produce a copy of a string in which the characters '+', '%', '=', ';' + * and control characters are replaced with "%hh". This is a gentle form + * of URL encoding, attempting to cause as little distortion to the + * string as possible. The characters '=' and ';' are meta characters in + * cookies. By convention, they are escaped using the URL-encoding. This is + * only a convention, not a standard. Often, cookies are expected to have + * encoded values. We encode '=' and ';' because we must. We encode '%' and + * '+' because they are meta characters in URL encoding. + * @param string The source string. + * @return The escaped result. + */ + public static String escape(String string) { + char c; + String s = string.trim(); + int length = s.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i += 1) { + c = s.charAt(i); + if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { + sb.append('%'); + sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); + sb.append(Character.forDigit((char)(c & 0x0f), 16)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + + /** + * Convert a cookie specification string into a JSONObject. The string + * will contain a name value pair separated by '='. The name and the value + * will be unescaped, possibly converting '+' and '%' sequences. The + * cookie properties may follow, separated by ';', also represented as + * name=value (except the secure property, which does not have a value). + * The name will be stored under the key "name", and the value will be + * stored under the key "value". This method does not do checking or + * validation of the parameters. It only converts the cookie string into + * a JSONObject. + * @param string The cookie specification string. + * @return A JSONObject containing "name", "value", and possibly other + * members. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + String name; + JSONObject jo = new JSONObject(); + Object value; + JSONTokener x = new JSONTokener(string); + jo.put("name", x.nextTo('=')); + x.next('='); + jo.put("value", x.nextTo(';')); + x.next(); + while (x.more()) { + name = unescape(x.nextTo("=;")); + if (x.next() != '=') { + if (name.equals("secure")) { + value = Boolean.TRUE; + } else { + throw x.syntaxError("Missing '=' in cookie parameter."); + } + } else { + value = unescape(x.nextTo(';')); + x.next(); + } + jo.put(name, value); + } + return jo; + } + + + /** + * Convert a JSONObject into a cookie specification string. The JSONObject + * must contain "name" and "value" members. + * If the JSONObject contains "expires", "domain", "path", or "secure" + * members, they will be appended to the cookie specification string. + * All other members are ignored. + * @param jo A JSONObject + * @return A cookie specification string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + + sb.append(escape(jo.getString("name"))); + sb.append("="); + sb.append(escape(jo.getString("value"))); + if (jo.has("expires")) { + sb.append(";expires="); + sb.append(jo.getString("expires")); + } + if (jo.has("domain")) { + sb.append(";domain="); + sb.append(escape(jo.getString("domain"))); + } + if (jo.has("path")) { + sb.append(";path="); + sb.append(escape(jo.getString("path"))); + } + if (jo.optBoolean("secure")) { + sb.append(";secure"); + } + return sb.toString(); + } + + /** + * Convert %hh sequences to single characters, and + * convert plus to space. + * @param string A string that may contain + * + (plus) and + * %hh sequences. + * @return The unescaped string. + */ + public static String unescape(String string) { + int length = string.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + char c = string.charAt(i); + if (c == '+') { + c = ' '; + } else if (c == '%' && i + 2 < length) { + int d = JSONTokener.dehexchar(string.charAt(i + 1)); + int e = JSONTokener.dehexchar(string.charAt(i + 2)); + if (d >= 0 && e >= 0) { + c = (char)(d * 16 + e); + i += 2; + } + } + sb.append(c); + } + return sb.toString(); + } +} diff --git a/CookieList.java b/CookieList.java index 7f4fe0751..b716fd7e3 100755 --- a/CookieList.java +++ b/CookieList.java @@ -1,90 +1,89 @@ -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.util.Iterator; - -/** - * Convert a web browser cookie list string to a JSONObject and back. - * @author JSON.org - * @version 2010-12-24 - */ -public class CookieList { - - /** - * Convert a cookie list into a JSONObject. A cookie list is a sequence - * of name/value pairs. The names are separated from the values by '='. - * The pairs are separated by ';'. The names and the values - * will be unescaped, possibly converting '+' and '%' sequences. - * - * To add a cookie to a cooklist, - * cookielistJSONObject.put(cookieJSONObject.getString("name"), - * cookieJSONObject.getString("value")); - * @param string A cookie list string - * @return A JSONObject - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - JSONObject jo = new JSONObject(); - JSONTokener x = new JSONTokener(string); - while (x.more()) { - String name = Cookie.unescape(x.nextTo('=')); - x.next('='); - jo.put(name, Cookie.unescape(x.nextTo(';'))); - x.next(); - } - return jo; - } - - - /** - * Convert a JSONObject into a cookie list. A cookie list is a sequence - * of name/value pairs. The names are separated from the values by '='. - * The pairs are separated by ';'. The characters '%', '+', '=', and ';' - * in the names and values are replaced by "%hh". - * @param jo A JSONObject - * @return A cookie list string - * @throws JSONException - */ - public static String toString(JSONObject jo) throws JSONException { - boolean b = false; - Iterator keys = jo.keys(); - String string; - StringBuffer sb = new StringBuffer(); - while (keys.hasNext()) { - string = keys.next().toString(); - if (!jo.isNull(string)) { - if (b) { - sb.append(';'); - } - sb.append(Cookie.escape(string)); - sb.append("="); - sb.append(Cookie.escape(jo.getString(string))); - b = true; - } - } - return sb.toString(); - } -} +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.util.Iterator; + +/** + * Convert a web browser cookie list string to a JSONObject and back. + * @author JSON.org + * @version 2014-05-03 + */ +public class CookieList { + + /** + * Convert a cookie list into a JSONObject. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The names and the values + * will be unescaped, possibly converting '+' and '%' sequences. + * + * To add a cookie to a cooklist, + * cookielistJSONObject.put(cookieJSONObject.getString("name"), + * cookieJSONObject.getString("value")); + * @param string A cookie list string + * @return A JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + JSONTokener x = new JSONTokener(string); + while (x.more()) { + String name = Cookie.unescape(x.nextTo('=')); + x.next('='); + jo.put(name, Cookie.unescape(x.nextTo(';'))); + x.next(); + } + return jo; + } + + /** + * Convert a JSONObject into a cookie list. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The characters '%', '+', '=', and ';' + * in the names and values are replaced by "%hh". + * @param jo A JSONObject + * @return A cookie list string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + boolean b = false; + Iterator keys = jo.keys(); + String string; + StringBuilder sb = new StringBuilder(); + while (keys.hasNext()) { + string = keys.next(); + if (!jo.isNull(string)) { + if (b) { + sb.append(';'); + } + sb.append(Cookie.escape(string)); + sb.append("="); + sb.append(Cookie.escape(jo.getString(string))); + b = true; + } + } + return sb.toString(); + } +} diff --git a/HTTP.java b/HTTP.java index 43d04a804..648f4dad7 100755 --- a/HTTP.java +++ b/HTTP.java @@ -29,7 +29,7 @@ of this software and associated documentation files (the "Software"), to deal /** * Convert an HTTP header to a JSONObject and back. * @author JSON.org - * @version 2010-12-24 + * @version 2014-05-03 */ public class HTTP { @@ -125,9 +125,9 @@ public static JSONObject toJSONObject(String string) throws JSONException { * information. */ public static String toString(JSONObject jo) throws JSONException { - Iterator keys = jo.keys(); - String string; - StringBuffer sb = new StringBuffer(); + Iterator keys = jo.keys(); + String string; + StringBuilder sb = new StringBuilder(); if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { sb.append(jo.getString("HTTP-Version")); sb.append(' '); @@ -147,7 +147,7 @@ public static String toString(JSONObject jo) throws JSONException { } sb.append(CRLF); while (keys.hasNext()) { - string = keys.next().toString(); + string = keys.next(); if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) && !"Reason-Phrase".equals(string) && !"Method".equals(string) && !"Request-URI".equals(string) && !jo.isNull(string)) { diff --git a/HTTPTokener.java b/HTTPTokener.java index ed41744a0..b2489b68d 100755 --- a/HTTPTokener.java +++ b/HTTPTokener.java @@ -28,7 +28,7 @@ of this software and associated documentation files (the "Software"), to deal * The HTTPTokener extends the JSONTokener to provide additional methods * for the parsing of HTTP headers. * @author JSON.org - * @version 2012-11-13 + * @version 2014-05-03 */ public class HTTPTokener extends JSONTokener { @@ -49,7 +49,7 @@ public HTTPTokener(String string) { public String nextToken() throws JSONException { char c; char q; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); do { c = next(); } while (Character.isWhitespace(c)); diff --git a/JSONArray.java b/JSONArray.java index e864a1e49..3f05548d5 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -75,20 +75,20 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2014-04-21 + * @version 2014-05-03 */ public class JSONArray { /** * The arrayList where the JSONArray's properties are kept. */ - private final ArrayList myArrayList; + private final ArrayList myArrayList; /** * Construct an empty JSONArray. */ public JSONArray() { - this.myArrayList = new ArrayList(); + this.myArrayList = new ArrayList(); } /** @@ -150,10 +150,10 @@ public JSONArray(String source) throws JSONException { * @param collection * A Collection. */ - public JSONArray(Collection collection) { - this.myArrayList = new ArrayList(); + public JSONArray(Collection collection) { + this.myArrayList = new ArrayList(); if (collection != null) { - Iterator iter = collection.iterator(); + Iterator iter = collection.iterator(); while (iter.hasNext()) { this.myArrayList.add(JSONObject.wrap(iter.next())); } @@ -357,7 +357,7 @@ public boolean isNull(int index) { */ public String join(String separator) throws JSONException { int len = this.length(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i += 1) { if (i > 0) { @@ -593,7 +593,7 @@ public JSONArray put(boolean value) { * A Collection value. * @return this. */ - public JSONArray put(Collection value) { + public JSONArray put(Collection value) { this.put(new JSONArray(value)); return this; } @@ -646,7 +646,7 @@ public JSONArray put(long value) { * A Map value. * @return this. */ - public JSONArray put(Map value) { + public JSONArray put(Map value) { this.put(new JSONObject(value)); return this; } @@ -695,7 +695,7 @@ public JSONArray put(int index, boolean value) throws JSONException { * @throws JSONException * If the index is negative or if the value is not finite. */ - public JSONArray put(int index, Collection value) throws JSONException { + public JSONArray put(int index, Collection value) throws JSONException { this.put(index, new JSONArray(value)); return this; } @@ -767,7 +767,7 @@ public JSONArray put(int index, long value) throws JSONException { * If the index is negative or if the the value is an invalid * number. */ - public JSONArray put(int index, Map value) throws JSONException { + public JSONArray put(int index, Map value) throws JSONException { this.put(index, new JSONObject(value)); return this; } diff --git a/JSONException.java b/JSONException.java index 971547e63..6fef51943 100755 --- a/JSONException.java +++ b/JSONException.java @@ -4,7 +4,7 @@ * The JSONException is thrown by the JSON.org classes when things are amiss. * * @author JSON.org - * @version 2013-02-10 + * @version 2014-05-03 */ public class JSONException extends RuntimeException { private static final long serialVersionUID = 0; @@ -22,6 +22,7 @@ public JSONException(String message) { /** * Constructs a new JSONException with the specified cause. + * @param cause The cause. */ public JSONException(Throwable cause) { super(cause.getMessage()); @@ -32,9 +33,10 @@ public JSONException(Throwable 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 + * @return the cause of this exception or null if the cause is nonexistent * or unknown. */ + @Override public Throwable getCause() { return this.cause; } diff --git a/JSONML.java b/JSONML.java index 4be686351..20e0be5fa 100755 --- a/JSONML.java +++ b/JSONML.java @@ -33,7 +33,7 @@ of this software and associated documentation files (the "Software"), to deal * the JsonML transform. * * @author JSON.org - * @version 2012-03-28 + * @version 2014-05-03 */ public class JSONML { @@ -53,12 +53,12 @@ private static Object parse( ) throws JSONException { String attribute; char c; - String closeTag = null; + String closeTag = null; int i; JSONArray newja = null; JSONObject newjo = null; Object token; - String tagName = null; + String tagName = null; // Test for and skip past these forms: // @@ -312,15 +312,15 @@ public static JSONObject toJSONObject(String string) throws JSONException { * @throws JSONException */ public static String toString(JSONArray ja) throws JSONException { - int i; - JSONObject jo; - String key; - Iterator keys; - int length; - Object object; - StringBuffer sb = new StringBuffer(); - String tagName; - String value; + int i; + JSONObject jo; + String key; + Iterator keys; + int length; + Object object; + StringBuilder sb = new StringBuilder(); + String tagName; + String value; // Emit = length) { @@ -394,15 +394,15 @@ public static String toString(JSONArray ja) throws JSONException { * @throws JSONException */ public static String toString(JSONObject jo) throws JSONException { - StringBuffer sb = new StringBuffer(); - int i; - JSONArray ja; - String key; - Iterator keys; - int length; - Object object; - String tagName; - String value; + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + String key; + Iterator keys; + int length; + Object object; + String tagName; + String value; //Emit * * @author JSON.org - * @version 2014-04-21 + * @version 2014-05-03 */ public class JSONObject { /** @@ -106,6 +107,7 @@ private static final class Null { * * @return NULL. */ + @Override protected final Object clone() { return this; } @@ -118,6 +120,7 @@ protected final Object clone() { * @return true if the object parameter is the JSONObject.NULL object or * null. */ + @Override public boolean equals(Object object) { return object == null || object == this; } @@ -135,7 +138,7 @@ public String toString() { /** * The map where the JSONObject's properties are kept. */ - private final Map map; + private final Map map; /** * It is sometimes more convenient and less ambiguous to have a @@ -149,7 +152,7 @@ public String toString() { * Construct an empty JSONObject. */ public JSONObject() { - this.map = new HashMap(); + this.map = new HashMap(); } /** @@ -239,15 +242,15 @@ public JSONObject(JSONTokener x) throws JSONException { * the JSONObject. * @throws JSONException */ - public JSONObject(Map map) { - this.map = new HashMap(); + public JSONObject(Map map) { + this.map = new HashMap(); if (map != null) { - Iterator i = map.entrySet().iterator(); + Iterator> i = map.entrySet().iterator(); while (i.hasNext()) { - Map.Entry e = (Map.Entry) i.next(); - Object value = e.getValue(); + Entry entry = i.next(); + Object value = entry.getValue(); if (value != null) { - this.map.put(e.getKey(), wrap(value)); + this.map.put(entry.getKey(), wrap(value)); } } } @@ -338,10 +341,10 @@ public JSONObject(String baseName, Locale locale) throws JSONException { // Iterate through the keys in the bundle. - Enumeration keys = bundle.getKeys(); + Enumeration keys = bundle.getKeys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); - if (key instanceof String) { + if (key != null) { // 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 @@ -609,11 +612,11 @@ public static String[] getNames(JSONObject jo) { if (length == 0) { return null; } - Iterator iterator = jo.keys(); + Iterator iterator = jo.keys(); String[] names = new String[length]; int i = 0; while (iterator.hasNext()) { - names[i] = (String) iterator.next(); + names[i] = iterator.next(); i += 1; } return names; @@ -686,13 +689,13 @@ public JSONObject increment(String key) throws JSONException { if (value == null) { this.put(key, 1); } else if (value instanceof Integer) { - this.put(key, ((Integer) value).intValue() + 1); + this.put(key, (Integer) value + 1); } else if (value instanceof Long) { - this.put(key, ((Long) value).longValue() + 1); + this.put(key, (Long) value + 1); } else if (value instanceof Double) { - this.put(key, ((Double) value).doubleValue() + 1); + this.put(key, (Double) value + 1); } else if (value instanceof Float) { - this.put(key, ((Float) value).floatValue() + 1); + this.put(key, (Float) value + 1); } else { throw new JSONException("Unable to increment [" + quote(key) + "]."); } @@ -717,7 +720,7 @@ public boolean isNull(String key) { * * @return An iterator of the keys. */ - public Iterator keys() { + public Iterator keys() { return this.keySet().iterator(); } @@ -726,7 +729,7 @@ public Iterator keys() { * * @return A keySet. */ - public Set keySet() { + public Set keySet() { return this.map.keySet(); } @@ -748,7 +751,7 @@ public int length() { */ public JSONArray names() { JSONArray ja = new JSONArray(); - Iterator keys = this.keys(); + Iterator keys = this.keys(); while (keys.hasNext()) { ja.put(keys.next()); } @@ -1050,7 +1053,7 @@ public JSONObject put(String key, boolean value) throws JSONException { * @return this. * @throws JSONException */ - public JSONObject put(String key, Collection value) throws JSONException { + public JSONObject put(String key, Collection value) throws JSONException { this.put(key, new JSONArray(value)); return this; } @@ -1114,7 +1117,7 @@ public JSONObject put(String key, long value) throws JSONException { * @return this. * @throws JSONException */ - public JSONObject put(String key, Map value) throws JSONException { + public JSONObject put(String key, Map value) throws JSONException { this.put(key, new JSONObject(value)); return this; } @@ -1151,9 +1154,9 @@ public JSONObject put(String key, Object value) throws JSONException { * are both non-null, and only if there is not already a member with that * name. * - * @param key - * @param value - * @return his. + * @param key string + * @param value object + * @return this. * @throws JSONException * if the key is a duplicate */ @@ -1294,13 +1297,13 @@ public boolean similar(Object other) { if (!(other instanceof JSONObject)) { return false; } - Set set = this.keySet(); + Set set = this.keySet(); if (!set.equals(((JSONObject)other).keySet())) { return false; } - Iterator iterator = set.iterator(); + Iterator iterator = set.iterator(); while (iterator.hasNext()) { - String name = (String)iterator.next(); + String name = iterator.next(); Object valueThis = this.get(name); Object valueOther = ((JSONObject)other).get(name); if (valueThis instanceof JSONObject) { @@ -1361,8 +1364,8 @@ public static Object stringToValue(String string) { } else { Long myLong = new Long(string); if (string.equals(myLong.toString())) { - if (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); + if (myLong == myLong.intValue()) { + return myLong.intValue(); } else { return myLong; } @@ -1509,10 +1512,10 @@ public static String valueToString(Object value) throws JSONException { return value.toString(); } if (value instanceof Map) { - return new JSONObject((Map) value).toString(); + return new JSONObject((Map)value).toString(); } if (value instanceof Collection) { - return new JSONArray((Collection) value).toString(); + return new JSONArray((Collection) value).toString(); } if (value.getClass().isArray()) { return new JSONArray(value).toString(); @@ -1548,13 +1551,13 @@ public static Object wrap(Object object) { } if (object instanceof Collection) { - return new JSONArray((Collection) object); + return new JSONArray((Collection) object); } if (object.getClass().isArray()) { return new JSONArray(object); } if (object instanceof Map) { - return new JSONObject((Map) object); + return new JSONObject((Map) object); } Package objectPackage = object.getClass().getPackage(); String objectPackageName = objectPackage != null ? objectPackage @@ -1592,9 +1595,9 @@ static final Writer writeValue(Writer writer, Object value, } else if (value instanceof JSONArray) { ((JSONArray) value).write(writer, indentFactor, indent); } else if (value instanceof Map) { - new JSONObject((Map) value).write(writer, indentFactor, indent); + new JSONObject((Map) value).write(writer, indentFactor, indent); } else if (value instanceof Collection) { - new JSONArray((Collection) value).write(writer, indentFactor, + new JSONArray((Collection) value).write(writer, indentFactor, indent); } else if (value.getClass().isArray()) { new JSONArray(value).write(writer, indentFactor, indent); @@ -1636,7 +1639,7 @@ Writer write(Writer writer, int indentFactor, int indent) try { boolean commanate = false; final int length = this.length(); - Iterator keys = this.keys(); + Iterator keys = this.keys(); writer.write('{'); if (length == 1) { @@ -1663,8 +1666,7 @@ Writer write(Writer writer, int indentFactor, int indent) if (indentFactor > 0) { writer.write(' '); } - writeValue(writer, this.map.get(key), indentFactor, - newindent); + writeValue(writer, this.map.get(key), indentFactor, newindent); commanate = true; } if (indentFactor > 0) { diff --git a/JSONTokener.java b/JSONTokener.java index 13c84f1f5..32548ed9f 100644 --- a/JSONTokener.java +++ b/JSONTokener.java @@ -36,7 +36,7 @@ of this software and associated documentation files (the "Software"), to deal * it. It is used by the JSONObject and JSONArray constructors to parse * JSON source strings. * @author JSON.org - * @version 2012-02-16 + * @version 2014-05-03 */ public class JSONTokener { @@ -69,6 +69,7 @@ public JSONTokener(Reader reader) { /** * Construct a JSONTokener from an InputStream. + * @param inputStream The source. */ public JSONTokener(InputStream inputStream) throws JSONException { this(new InputStreamReader(inputStream)); @@ -250,7 +251,7 @@ public char nextClean() throws JSONException { */ public String nextString(char quote) throws JSONException { char c; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { c = this.next(); switch (c) { @@ -306,7 +307,7 @@ public String nextString(char quote) throws JSONException { * @return A string. */ public String nextTo(char delimiter) throws JSONException { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { char c = this.next(); if (c == delimiter || c == 0 || c == '\n' || c == '\r') { @@ -328,7 +329,7 @@ public String nextTo(char delimiter) throws JSONException { */ public String nextTo(String delimiters) throws JSONException { char c; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { c = this.next(); if (delimiters.indexOf(c) >= 0 || c == 0 || @@ -375,7 +376,7 @@ public Object nextValue() throws JSONException { * formatting character. */ - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { sb.append(c); c = this.next(); @@ -414,10 +415,9 @@ public char skipTo(char to) throws JSONException { return c; } } while (c != to); - } catch (IOException exc) { - throw new JSONException(exc); + } catch (IOException exception) { + throw new JSONException(exception); } - this.back(); return c; } diff --git a/JSONWriter.java b/JSONWriter.java index 855b2bdb1..07bbc8cfa 100755 --- a/JSONWriter.java +++ b/JSONWriter.java @@ -269,7 +269,7 @@ private void pop(char c) throws JSONException { /** * Push an array or object scope. - * @param c The scope to open. + * @param jo The scope to open. * @throws JSONException If nesting is too deep. */ private void push(JSONObject jo) throws JSONException { diff --git a/Kim.java b/Kim.java index d4770b566..9f7af92d0 100644 --- a/Kim.java +++ b/Kim.java @@ -137,7 +137,6 @@ public Kim(byte[] bytes, int length) { * The point at which to take bytes. * @param thru * The point at which to stop taking bytes. - * @return the substring */ public Kim(Kim kim, int from, int thru) { this(kim.bytes, from, thru); diff --git a/Property.java b/Property.java index dbbd7ef7e..8122241e9 100644 --- a/Property.java +++ b/Property.java @@ -31,7 +31,7 @@ of this software and associated documentation files (the "Software"), to deal /** * Converts a Property file data into JSONObject and back. * @author JSON.org - * @version 2013-05-23 + * @version 2014-05-03 */ public class Property { /** @@ -50,7 +50,6 @@ public static JSONObject toJSONObject(java.util.Properties properties) throws JS } } return jo; - } /** @@ -62,13 +61,12 @@ public static JSONObject toJSONObject(java.util.Properties properties) throws JS public static Properties toProperties(JSONObject jo) throws JSONException { Properties properties = new Properties(); if (jo != null) { - Iterator keys = jo.keys(); - + Iterator keys = jo.keys(); while (keys.hasNext()) { - String name = keys.next().toString(); + String name = keys.next(); properties.put(name, jo.getString(name)); } } return properties; } -} \ No newline at end of file +} diff --git a/README b/README index b77c71a21..6afe0c691 100755 --- a/README +++ b/README @@ -21,7 +21,7 @@ The license includes this restriction: "The software shall be used for good, not evil." If your conscience cannot live with that, then choose a different package. -The package compiles on Java 1.2 thru Java 1.4. +The package compiles on Java 1.8. JSONObject.java: The JSONObject can parse text from a String or a JSONTokener diff --git a/XML.java b/XML.java index cea3abe7b..07090abe3 100755 --- a/XML.java +++ b/XML.java @@ -26,41 +26,40 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Iterator; - /** * This provides static methods to convert an XML text into a JSONObject, * and to covert a JSONObject into an XML text. * @author JSON.org - * @version 2013-11-12 + * @version 2014-05-03 */ public class XML { /** The Character '&'. */ - public static final Character AMP = new Character('&'); + public static final Character AMP = '&'; /** The Character '''. */ - public static final Character APOS = new Character('\''); + public static final Character APOS = '\''; /** The Character '!'. */ - public static final Character BANG = new Character('!'); + public static final Character BANG = '!'; /** The Character '='. */ - public static final Character EQ = new Character('='); + public static final Character EQ = '='; /** The Character '>'. */ - public static final Character GT = new Character('>'); + public static final Character GT = '>'; /** The Character '<'. */ - public static final Character LT = new Character('<'); + public static final Character LT = '<'; /** The Character '?'. */ - public static final Character QUEST = new Character('?'); + public static final Character QUEST = '?'; /** The Character '"'. */ - public static final Character QUOT = new Character('"'); + public static final Character QUOT = '"'; /** The Character '/'. */ - public static final Character SLASH = new Character('/'); + public static final Character SLASH = '/'; /** * Replace special characters with XML escapes: @@ -74,7 +73,7 @@ public class XML { * @return The escaped string. */ public static String escape(String string) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(string.length()); for (int i = 0, length = string.length(); i < length; i++) { char c = string.charAt(i); switch (c) { @@ -103,7 +102,7 @@ public static String escape(String string) { /** * Throw an exception if the string contains whitespace. * Whitespace is not allowed in tagNames and attributes. - * @param string + * @param string A string. * @throws JSONException */ public static void noSpace(String string) throws JSONException { @@ -379,15 +378,15 @@ public static String toString(Object object) throws JSONException { */ public static String toString(Object object, String tagName) throws JSONException { - StringBuffer sb = new StringBuffer(); - int i; - JSONArray ja; - JSONObject jo; - String key; - Iterator keys; - int length; - String string; - Object value; + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + JSONObject jo; + String key; + Iterator keys; + int length; + String string; + Object value; if (object instanceof JSONObject) { // Emit @@ -403,16 +402,12 @@ public static String toString(Object object, String tagName) jo = (JSONObject)object; keys = jo.keys(); while (keys.hasNext()) { - key = keys.next().toString(); + key = keys.next(); value = jo.opt(key); if (value == null) { value = ""; } - if (value instanceof String) { - string = (String)value; - } else { - string = null; - } + string = value instanceof String ? (String)value : null; // Emit content in body diff --git a/XMLTokener.java b/XMLTokener.java index be15ebeba..d3197653c 100755 --- a/XMLTokener.java +++ b/XMLTokener.java @@ -28,7 +28,7 @@ of this software and associated documentation files (the "Software"), to deal * The XMLTokener extends the JSONTokener to provide additional methods * for the parsing of XML texts. * @author JSON.org - * @version 2012-11-13 + * @version 2014-05-03 */ public class XMLTokener extends JSONTokener { @@ -36,10 +36,10 @@ public class XMLTokener extends JSONTokener { /** The table of entity values. It initially contains Character values for * amp, apos, gt, lt, quot. */ - public static final java.util.HashMap entity; + public static final java.util.HashMap entity; static { - entity = new java.util.HashMap(8); + entity = new java.util.HashMap(8); entity.put("amp", XML.AMP); entity.put("apos", XML.APOS); entity.put("gt", XML.GT); @@ -63,7 +63,7 @@ public XMLTokener(String s) { public String nextCDATA() throws JSONException { char c; int i; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { c = next(); if (end()) { @@ -91,7 +91,7 @@ public String nextCDATA() throws JSONException { */ public Object nextContent() throws JSONException { char c; - StringBuffer sb; + StringBuilder sb; do { c = next(); } while (Character.isWhitespace(c)); @@ -101,7 +101,7 @@ public Object nextContent() throws JSONException { if (c == '<') { return XML.LT; } - sb = new StringBuffer(); + sb = new StringBuilder(); for (;;) { if (c == '<' || c == 0) { back(); @@ -125,7 +125,7 @@ public Object nextContent() throws JSONException { * @throws JSONException If missing ';' in XML entity. */ public Object nextEntity(char ampersand) throws JSONException { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { char c = next(); if (Character.isLetterOrDigit(c) || c == '#') { @@ -219,7 +219,7 @@ public Object nextMeta() throws JSONException { public Object nextToken() throws JSONException { char c; char q; - StringBuffer sb; + StringBuilder sb; do { c = next(); } while (Character.isWhitespace(c)); @@ -244,7 +244,7 @@ public Object nextToken() throws JSONException { case '"': case '\'': q = c; - sb = new StringBuffer(); + sb = new StringBuilder(); for (;;) { c = next(); if (c == 0) { @@ -263,7 +263,7 @@ public Object nextToken() throws JSONException { // Name - sb = new StringBuffer(); + sb = new StringBuilder(); for (;;) { sb.append(c); c = next(); diff --git a/zip/BitInputStream.java b/zip/BitInputStream.java index 7864ce150..2282d30c0 100644 --- a/zip/BitInputStream.java +++ b/zip/BitInputStream.java @@ -30,15 +30,10 @@ of this software and associated documentation files (the "Software"), to deal /** * This is a big endian bit reader. It reads its bits from an InputStream. * - * @version 2013-04-18 + * @version 2013-05-03 * */ public class BitInputStream implements BitReader { - /** - * 2^n - 1 - */ - static final int[] mask = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; - /** * The number of bits remaining in the current byte. */ @@ -70,23 +65,6 @@ public BitInputStream(InputStream in) { this.in = in; } - /** - * Make a BitReader. The first byte is passed in explicitly, the remaining - * bytes are obtained from the InputStream. This makes it possible to look - * at the first byte of a stream before deciding that it should be read as - * bits. - * - * @param in - * An InputStream - * @param firstByte - * The first byte, which was probably read from in. - */ - public BitInputStream(InputStream in, int firstByte) { - this.in = in; - this.unread = firstByte; - this.available = 8; - } - /** * Read one bit. * @@ -111,20 +89,26 @@ public long nrBits() { /** * Check that the rest of the block has been padded with zeroes. * - * @param factor - * The size of the block to pad. This will typically be 8, 16, - * 32, 64, 128, 256, etc. + * @param width + * The size of the block to pad in bits. + * This will typically be 8, 16, 32, 64, 128, 256, etc. * @return true if the block was zero padded, or false if the the padding * contains any one bits. * @throws IOException */ - public boolean pad(int factor) throws IOException { - int padding = factor - (int) (this.nrBits % factor); + public boolean pad(int width) throws IOException { boolean result = true; - - for (int i = 0; i < padding; i += 1) { - if (bit()) { - result = false; + int gap = (int)this.nrBits % width; + if (gap < 0) { + gap += width; + } + if (gap != 0) { + int padding = width - gap; + while (padding > 0) { + if (bit()) { + result = false; + } + padding -= 1; } } return result; @@ -158,8 +142,8 @@ public int read(int width) throws IOException { if (take > this.available) { take = this.available; } - result |= ((this.unread >>> (this.available - take)) & mask[take]) - << (width - take); + result |= ((this.unread >>> (this.available - take)) & + ((1 << take) - 1)) << (width - take); this.nrBits += take; this.available -= take; width -= take; diff --git a/zip/BitOutputStream.java b/zip/BitOutputStream.java index 526ad6111..da47301cf 100644 --- a/zip/BitOutputStream.java +++ b/zip/BitOutputStream.java @@ -30,7 +30,7 @@ of this software and associated documentation files (the "Software"), to deal /** * This is a big endian bit writer. It writes its bits to an OutputStream. * - * @version 2013-04-18 + * @version 2013-05-03 * */ public class BitOutputStream implements BitWriter { @@ -85,25 +85,25 @@ public void one() throws IOException { } /** - * Pad the rest of the block with zeroes and flush. pad(8) flushes the last + * Pad the rest of the block with zeros and flush. pad(8) flushes the last * unfinished byte. The underlying OutputStream will be flushed. * - * @param factor - * The size of the block to pad. This will typically be 8, 16, - * 32, 64, 128, 256, etc. - * @return this + * @param width + * The size of the block to pad in bits. + * This will typically be 8, 16, 32, 64, 128, 256, etc. * @throws IOException */ - public void pad(int factor) throws IOException { - int padding = factor - (int) (nrBits % factor); - int excess = padding & 7; - if (excess > 0) { - this.write(0, excess); - padding -= excess; + public void pad(int width) throws IOException { + int gap = (int)this.nrBits % width; + if (gap < 0) { + gap += width; } - while (padding > 0) { - this.write(0, 8); - padding -= 8; + if (gap != 0) { + int padding = width - gap; + while (padding > 0) { + this.zero(); + padding -= 1; + } } this.out.flush(); } @@ -130,7 +130,7 @@ public void write(int bits, int width) throws IOException { actual = this.vacant; } this.unwritten |= ((bits >>> (width - actual)) & - BitInputStream.mask[actual]) << (this.vacant - actual); + ((1 << actual) - 1)) << (this.vacant - actual); width -= actual; nrBits += actual; this.vacant -= actual; diff --git a/zip/BitReader.java b/zip/BitReader.java index 1987729b8..4fd99dbbf 100644 --- a/zip/BitReader.java +++ b/zip/BitReader.java @@ -3,6 +3,7 @@ import java.io.IOException; public interface BitReader { + /** * Read one bit. * @@ -18,16 +19,16 @@ public interface BitReader { public long nrBits(); /** - * Check that the rest of the block has been padded with zeroes. + * Check that the rest of the block has been padded with zeros. * - * @param factor + * @param width * The size in bits of the block to pad. This will typically be * 8, 16, 32, 64, 128, 256, etc. * @return true if the block was zero padded, or false if the the padding * contained any one bits. * @throws IOException */ - public boolean pad(int factor) throws IOException; + public boolean pad(int width) throws IOException; /** * Read some bits. diff --git a/zip/BitWriter.java b/zip/BitWriter.java index 83eb7e314..ba8a109c6 100644 --- a/zip/BitWriter.java +++ b/zip/BitWriter.java @@ -7,10 +7,6 @@ * Most IO interfaces only allow for writing at the byte level or higher. */ public interface BitWriter { - /** - * Returns the number of bits that have been written to this bitwriter. - */ - public long nrBits(); /** * Write a 1 bit. @@ -22,14 +18,12 @@ public interface BitWriter { /** * Pad the rest of the block with zeros and flush. * - * @param factor + * @param width * The size in bits of the block to pad. This will typically be * 8, 16, 32, 64, 128, 256, etc. - * @return true if the block was zero padded, or false if the the padding - * contains any one bits. * @throws IOException */ - public void pad(int factor) throws IOException; + public void pad(int width) throws IOException; /** * Write some bits. Up to 32 bits can be written at a time. diff --git a/zip/Huff.java b/zip/Huff.java index 2e1d1c925..98c650e69 100644 --- a/zip/Huff.java +++ b/zip/Huff.java @@ -29,7 +29,7 @@ of this software and associated documentation files (the "Software"), to deal /** * JSONzip is a compression scheme for JSON text. * @author JSON.org - * @version 2013-04-18 + * @version 2014-05-03 */ /** @@ -42,6 +42,9 @@ of this software and associated documentation files (the "Software"), to deal * symbol is incremented by the tick method. The generate method is used to * generate the encoding table. The table must be generated before encoding or * decoding. You may regenerate the table with the latest weights at any time. + * + * After a million ticks, it is assumed that the distribution is well + * understood and that no more regeneration will be required. */ public class Huff implements None, PostMortem { @@ -60,6 +63,11 @@ public class Huff implements None, PostMortem { */ private Symbol table; + /** + * The number of characters left to learn to adapt the coding table. + */ + private int toLearn; + /** * Have any weights changed since the table was last generated? */ @@ -100,7 +108,7 @@ public boolean postMortem(PostMortem pm) { if (this.integer != that.integer || this.weight != that.weight) { return false; } - if ((this.back != null) != (that.back != null)) { + if ((this.back == null) != (that.back == null)) { return false; } Symbol zero = this.zero; @@ -132,6 +140,7 @@ public boolean postMortem(PostMortem pm) { */ public Huff(int domain) { this.domain = domain; + this.toLearn = 1000000; int length = domain * 2 - 1; this.symbols = new Symbol[length]; @@ -141,7 +150,7 @@ public Huff(int domain) { symbols[i] = new Symbol(i); } -// SMake the links. +// Make the links. for (int i = domain; i < length; i += 1) { symbols[i] = new Symbol(none); @@ -151,8 +160,6 @@ public Huff(int domain) { /** * Generate the encoding/decoding table. The table determines the bit * sequences used by the read and write methods. - * - * @return this */ public void generate() { if (!this.upToDate) { @@ -176,8 +183,8 @@ public void generate() { head = symbol; } else { -// To save time, we will start the search from the previous symbol instead -// of the head unless the current symbol weights less than the previous symbol. +// We will start the search from the previous symbol instead of the head unless +// the current symbol weights less than the previous symbol. if (symbol.weight < previous.weight) { previous = head; @@ -290,7 +297,7 @@ private boolean postMortem(int integer) { public boolean postMortem(PostMortem pm) { // Go through every integer in the domain, generating its bit sequence, and -// then proving that that bit sequence produces the same integer. +// then prove that that bit sequence produces the same integer. for (int integer = 0; integer < this.domain; integer += 1) { if (!postMortem(integer)) { @@ -330,29 +337,16 @@ public int read(BitReader bitreader) throws JSONException { } /** - * Increase by 1 the weight associated with a value. + * Increase the weight associated with a value by 1. * * @param value * The number of the symbol to tick - * @return this */ public void tick(int value) { - this.symbols[value].weight += 1; - this.upToDate = false; - } - - /** - * Increase by 1 the weight associated with a range of values. - * - * @param from - * The first symbol to tick - * @param to - * The last symbol to tick - * @return this - */ - public void tick(int from, int to) { - for (int value = from; value <= to; value += 1) { - tick(value); + if (this.toLearn > 0) { + this.toLearn -= 1; + this.symbols[value].weight += 1; + this.upToDate = false; } } @@ -392,7 +386,6 @@ private void write(Symbol symbol, BitWriter bitwriter) * The number of the symbol to write * @param bitwriter * The destination of the bits. - * @return this * @throws JSONException */ public void write(int value, BitWriter bitwriter) throws JSONException { diff --git a/zip/JSONzip.java b/zip/JSONzip.java index a8b837fa3..220686de3 100644 --- a/zip/JSONzip.java +++ b/zip/JSONzip.java @@ -28,8 +28,8 @@ of this software and associated documentation files (the "Software"), to deal * JSONzip is a binary-encoded JSON dialect. It is designed to compress the * messages in a session in bandwidth constrained applications, such as mobile. * - * JSONzip is adaptive, so with each message seen, it should - * improve its compression. It minimizes JSON's overhead, reducing punctuation + * JSONzip is adaptive, so with each message seen, it should improve its + * compression. It minimizes JSON's overhead, reducing punctuation * to a small number of bits. It uses Huffman encoding to reduce the average * size of characters. It uses caches (or Keeps) to keep recently seen strings * and values, so repetitive content (such as object keys) can be @@ -44,17 +44,9 @@ of this software and associated documentation files (the "Software"), to deal * ADEQUATELY FOR PRODUCTION USE. * * @author JSON.org - * @version 2014-04-28 + * @version 2014-05-03 */ public abstract class JSONzip implements None, PostMortem { - /** - * Powers of 2. - */ - public static final int[] twos = { - 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, - 1024, 2048, 4096, 8192, 16384, 32768, 65536 - }; - /** * The characters in JSON numbers can be reduced to 4 bits each. */ @@ -87,28 +79,11 @@ public abstract class JSONzip implements None, PostMortem { */ public static final int endOfNumber = bcd.length; - /** - * The maximum substring length when registering many. The registration of - * one substring may be longer. - */ - public static final int maxSubstringLength = 10; - - /** - * The minimum substring length. - */ - public static final int minSubstringLength = 3; - /** * The package supports tracing for debugging. */ public static final boolean probe = false; - /** - * The maximum number of substrings added to the substrings keep per - * string. - */ - public static final int substringLimit = 40; - /** * The value code for an empty object. */ @@ -155,62 +130,55 @@ public abstract class JSONzip implements None, PostMortem { protected final Huff namehuff; /** - * A place to keep the names (keys). + * A Huffman encoder for names extended bytes. */ - protected final MapKeep namekeep; + protected final Huff namehuffext; /** - * A place to keep the strings. + * A place to keep the names (keys). */ - protected final MapKeep stringkeep; + protected final Keep namekeep; /** * A Huffman encoder for string values. */ - protected final Huff substringhuff; + protected final Huff stringhuff; + + /** + * A Huffman encoder for string values extended bytes. + */ + protected final Huff stringhuffext; /** * A place to keep the strings. */ - protected final TrieKeep substringkeep; + protected final Keep stringkeep; /** * A place to keep the values. */ - protected final MapKeep values; + protected final Keep valuekeep; /** * Initialize the data structures. */ protected JSONzip() { this.namehuff = new Huff(end + 1); - this.namekeep = new MapKeep(9); - this.stringkeep = new MapKeep(11); - this.substringhuff = new Huff(end + 1); - this.substringkeep = new TrieKeep(12); - this.values = new MapKeep(10); - -// Increase the weights of the ASCII letters, digits, and special characters -// because they are highly likely to occur more frequently. The weight of each -// character will increase as it is used. The Huffman encoder will tend to -// use fewer bits to encode heavier characters. - - this.namehuff.tick(' ', '}'); - this.namehuff.tick('a', 'z'); - this.namehuff.tick(end); - this.namehuff.tick(end); - this.substringhuff.tick(' ', '}'); - this.substringhuff.tick('a', 'z'); - this.substringhuff.tick(end); - this.substringhuff.tick(end); + this.namehuffext = new Huff(end + 1); + this.namekeep = new Keep(9); + this.stringhuff = new Huff(end + 1); + this.stringhuffext = new Huff(end + 1); + this.stringkeep = new Keep(11); + this.valuekeep = new Keep(10); } /** - * + * Generate the Huffman tables. */ - protected void begin() { + protected void generate() { this.namehuff.generate(); - this.substringhuff.generate(); + this.stringhuff.generate(); + this.stringhuffext.generate(); } /** @@ -223,7 +191,7 @@ static void log() { /** * Write an integer to the console. * - * @param integer + * @param integer The integer to write to the log. */ static void log(int integer) { log(integer + " "); @@ -233,8 +201,8 @@ static void log(int integer) { * Write two integers, separated by ':' to the console. * The second integer is suppressed if it is 1. * - * @param integer - * @param width + * @param integer The integer to write to the log. + * @param width The width of the integer in bits. */ static void log(int integer, int width) { if (width == 1) { @@ -247,7 +215,7 @@ static void log(int integer, int width) { /** * Write a string to the console. * - * @param string + * @param string The string to be written to the log. */ static void log(String string) { System.out.print(string); @@ -256,8 +224,8 @@ static void log(String string) { /** * Write a character or its code to the console. * - * @param integer - * @param width + * @param integer The charcode to be written to the log. + * @param width The width of the charcode in bits. */ static void logchar(int integer, int width) { if (integer > ' ' && integer <= '}') { @@ -280,8 +248,7 @@ public boolean postMortem(PostMortem pm) { return this.namehuff.postMortem(that.namehuff) && this.namekeep.postMortem(that.namekeep) && this.stringkeep.postMortem(that.stringkeep) - && this.substringhuff.postMortem(that.substringhuff) - && this.substringkeep.postMortem(that.substringkeep) - && this.values.postMortem(that.values); + && this.stringhuff.postMortem(that.stringhuff) + && this.valuekeep.postMortem(that.valuekeep); } } diff --git a/zip/Keep.java b/zip/Keep.java index 377e344e2..bc647b6a0 100644 --- a/zip/Keep.java +++ b/zip/Keep.java @@ -1,5 +1,8 @@ package org.json.zip; +import java.util.HashMap; + +import org.json.Kim; /* Copyright (c) 2013 JSON.org @@ -30,30 +33,34 @@ of this software and associated documentation files (the "Software"), to deal * numbers. This allows the sending of small integers instead of strings. * * @author JSON.org - * @version 2013-04-18 + * @version 2013-05-03 */ -abstract class Keep implements None, PostMortem { - protected int capacity; +class Keep implements None, PostMortem { + private int capacity; protected int length; - protected int power; - protected long[] uses; + private Object[] list; + private HashMap map; + private int power; + private long[] ticks; public Keep(int bits) { - this.capacity = JSONzip.twos[bits]; + this.capacity = 1 << bits; this.length = 0; this.power = 0; - this.uses = new long[this.capacity]; - } + this.ticks = new long[this.capacity]; + this.list = new Object[this.capacity]; + this.map = new HashMap(this.capacity); + } /** * When an item ages, its use count is reduced by at least half. * - * @param use + * @param ticks * The current use count of an item. * @return The new use count for that item. */ - public static long age(long use) { - return use >= 32 ? 16 : use / 2; + public static long age(long ticks) { + return ticks >= 32 ? 16 : ticks / 2; } /** @@ -62,7 +69,7 @@ public static long age(long use) { * required to identify one of its items goes up. */ public int bitsize() { - while (JSONzip.twos[this.power] < this.length) { + while (1 << this.power < this.length) { this.power += 1; } return this.power; @@ -72,13 +79,113 @@ public int bitsize() { * Increase the usage count on an integer value. */ public void tick(int integer) { - this.uses[integer] += 1; + this.ticks[integer] += 1; + } + + /** + * Compact the keep. A keep may contain at most this.capacity elements. + * The keep contents can be reduced by deleting all elements with low use + * counts, and by reducing the use counts of the survivors. + */ + private void compact() { + int from = 0; + int to = 0; + while (from < this.capacity) { + Object key = this.list[from]; + long usage = age(this.ticks[from]); + if (usage > 0) { + this.ticks[to] = usage; + this.list[to] = key; + this.map.put(key, to); + to += 1; + } else { + this.map.remove(key); + } + from += 1; + } + if (to < this.capacity) { + this.length = to; + } else { + this.map.clear(); + this.length = 0; + } + this.power = 0; + } + + /** + * Find the integer value associated with this key, or nothing if this key + * is not in the keep. + * + * @param key + * An object. + * @return An integer + */ + public int find(Object key) { + Object o = this.map.get(key); + return o instanceof Integer ? ((Integer) o).intValue() : none; + } + + public boolean postMortem(PostMortem pm) { + Keep that = (Keep) pm; + if (this.length != that.length) { + JSONzip.log(this.length + " <> " + that.length); + return false; + } + for (int i = 0; i < this.length; i += 1) { + boolean b; + if (this.list[i] instanceof Kim) { + b = this.list[i].equals(that.list[i]); + } else { + Object o = this.list[i]; + Object q = that.list[i]; + if (o instanceof Number) { + o = o.toString(); + } + if (q instanceof Number) { + q = q.toString(); + } + b = o.equals(q); + } + if (!b) { + JSONzip.log("\n[" + i + "]\n " + this.list[i] + "\n " + + that.list[i] + "\n " + this.ticks[i] + "\n " + + that.ticks[i]); + return false; + } + } + return true; } /** - * Get the value associated with an integer. + * Register a value in the keep. Compact the keep if it is full. The next + * time this value is encountered, its integer can be sent instead. + * @param value A value. + */ + public void register(Object value) { + if (JSONzip.probe) { + int integer = find(value); + if (integer >= 0) { + JSONzip.log("\nDuplicate key " + value); + } + } + if (this.length >= this.capacity) { + compact(); + } + this.list[this.length] = value; + this.map.put(value, this.length); + this.ticks[this.length] = 1; + if (JSONzip.probe) { + JSONzip.log("<" + this.length + " " + value + "> "); + } + this.length += 1; + } + + /** + * Return the value associated with the integer. * @param integer The number of an item in the keep. * @return The value. */ - abstract public Object value(int integer); + public Object value(int integer) { + return this.list[integer]; + } } diff --git a/zip/PostMortem.java b/zip/PostMortem.java index 22416d700..0f220ee48 100644 --- a/zip/PostMortem.java +++ b/zip/PostMortem.java @@ -29,8 +29,8 @@ of this software and associated documentation files (the "Software"), to deal * processors. Testing that JSONzip can compress an object and reproduce a * corresponding object is not sufficient. Complete testing requires that the * same internal data structures were constructed on both ends. If those - * structures are not equivalent, then it is likely that the implementations - * are not correct, even if convention tests are passed. + * structures are not exactly equivalent, then it is likely that the + * implementations are not correct, even if conventional tests are passed. * * PostMortem allows for testing of deep structures without breaking * encapsulation. diff --git a/zip/Decompressor.java b/zip/Unzipper.java similarity index 64% rename from zip/Decompressor.java rename to zip/Unzipper.java index 091ec54fc..a7c308eda 100644 --- a/zip/Decompressor.java +++ b/zip/Unzipper.java @@ -1,7 +1,5 @@ package org.json.zip; -import java.io.UnsupportedEncodingException; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -32,27 +30,27 @@ of this software and associated documentation files (the "Software"), to deal */ /** - * JSONzip is a compression scheme for JSON text. + * JSONzip is a binary compression scheme for JSON text. * * @author JSON.org - * @version 2014-04-28 + * @version 2014-05-03 */ -public class Decompressor extends JSONzip { +public class Unzipper extends JSONzip { /** - * A decompressor reads bits from a BitReader. + * A decoder reads bits from a BitReader. */ BitReader bitreader; /** - * Create a new compressor. It may be used for an entire session or + * Create a new unzipper. It may be used for an entire session or * subsession. * * @param bitreader - * The bitreader that this decompressor will read from. + * The bitreader that this decoder will read from. */ - public Decompressor(BitReader bitreader) { + public Unzipper(BitReader bitreader) { super(); this.bitreader = bitreader; } @@ -81,9 +79,9 @@ private boolean bit() throws JSONException { * Read enough bits to obtain an integer from the keep, and increase that * integer's weight. * - * @param keep - * @param bitreader - * @return + * @param keep The keep providing the context. + * @param bitreader The bitreader that is the source of bits. + * @return The value associated with the number. * @throws JSONException */ private Object getAndTick(Keep keep, BitReader bitreader) @@ -110,13 +108,13 @@ private Object getAndTick(Keep keep, BitReader bitreader) * The pad method skips the bits that padded a stream to fit some * allocation. pad(8) will skip over the remainder of a byte. * - * @param factor + * @param width The width of the pad field in bits. * @return true if all of the padding bits were zero. * @throws JSONException */ - public boolean pad(int factor) throws JSONException { + public boolean pad(int width) throws JSONException { try { - return this.bitreader.pad(factor); + return this.bitreader.pad(width); } catch (Throwable e) { throw new JSONException(e); } @@ -142,28 +140,76 @@ private int read(int width) throws JSONException { } } + /** + * Read Huffman encoded characters into a keep. + * @param huff A Huffman decoder. + * @param ext A Huffman decoder for the extended bytes. + * @param keep The keep that will receive the kim. + * @return The string that was read. + * @throws JSONException + */ + private String read(Huff huff, Huff ext, Keep keep) throws JSONException { + Kim kim; + int at = 0; + int allocation = 256; + byte[] bytes = new byte[allocation]; + if (bit()) { + return getAndTick(keep, this.bitreader).toString(); + } + while (true) { + if (at >= allocation) { + allocation *= 2; + bytes = java.util.Arrays.copyOf(bytes, allocation); + } + int c = huff.read(this.bitreader); + if (c == end) { + break; + } + while ((c & 128) == 128) { + bytes[at] = (byte) c; + at += 1; + c = ext.read(this.bitreader); + } + bytes[at] = (byte) c; + at += 1; + } + if (at == 0) { + return ""; + } + kim = new Kim(bytes, at); + keep.register(kim); + return kim.toString(); + } + /** * Read a JSONArray. * * @param stringy * true if the first element is a string. - * @return * @throws JSONException */ private JSONArray readArray(boolean stringy) throws JSONException { JSONArray jsonarray = new JSONArray(); - jsonarray.put(stringy ? readString() : readValue()); + jsonarray.put(stringy + ? read(this.stringhuff, this.stringhuffext, this.stringkeep) + : readValue()); while (true) { if (probe) { - log("\n"); + log(); } if (!bit()) { if (!bit()) { return jsonarray; } - jsonarray.put(stringy ? readValue() : readString()); + jsonarray.put(stringy + ? readValue() + : read(this.stringhuff, this.stringhuffext, + this.stringkeep)); } else { - jsonarray.put(stringy ? readString() : readValue()); + jsonarray.put(stringy + ? read(this.stringhuff, this.stringhuffext, + this.stringkeep) + : readValue()); } } } @@ -171,7 +217,7 @@ private JSONArray readArray(boolean stringy) throws JSONException { /** * Read a JSON value. The type of value is determined by the next 3 bits. * - * @return + * @return The read value. * @throws JSONException */ private Object readJSON() throws JSONException { @@ -195,96 +241,25 @@ private Object readJSON() throws JSONException { } } - private String readName() throws JSONException { - byte[] bytes = new byte[65536]; - int length = 0; - if (!bit()) { - while (true) { - int c = this.namehuff.read(this.bitreader); - if (c == end) { - break; - } - bytes[length] = (byte) c; - length += 1; - } - if (length == 0) { - return ""; - } - Kim kim = new Kim(bytes, length); - this.namekeep.register(kim); - return kim.toString(); - } - return getAndTick(this.namekeep, this.bitreader).toString(); - } - private JSONObject readObject() throws JSONException { JSONObject jsonobject = new JSONObject(); while (true) { if (probe) { log(); } - String name = readName(); - jsonobject.put(name, !bit() ? readString() : readValue()); + String name = read(this.namehuff, this.namehuffext, this.namekeep); + if (jsonobject.opt(name) != null) { + throw new JSONException("Duplicate key."); + } + jsonobject.put(name, !bit() + ? read(this.stringhuff, this.stringhuffext, this.stringkeep) + : readValue()); if (!bit()) { return jsonobject; } } } - private String readString() throws JSONException { - Kim kim; - int from = 0; - int thru = 0; - int previousFrom = none; - int previousThru = 0; - if (bit()) { - return getAndTick(this.stringkeep, this.bitreader).toString(); - } - byte[] bytes = new byte[65536]; - boolean one = bit(); - this.substringkeep.reserve(); - while (true) { - if (one) { - from = thru; - kim = (Kim) getAndTick(this.substringkeep, this.bitreader); - thru = kim.copy(bytes, from); - if (previousFrom != none) { - this.substringkeep.registerOne(new Kim(bytes, previousFrom, - previousThru + 1)); - } - previousFrom = from; - previousThru = thru; - one = bit(); - } else { - from = none; - while (true) { - int c = this.substringhuff.read(this.bitreader); - if (c == end) { - break; - } - bytes[thru] = (byte) c; - thru += 1; - if (previousFrom != none) { - this.substringkeep.registerOne(new Kim(bytes, - previousFrom, previousThru + 1)); - previousFrom = none; - } - } - if (!bit()) { - break; - } - one = true; - } - } - if (thru == 0) { - return ""; - } - kim = new Kim(bytes, thru); - this.stringkeep.register(kim); - this.substringkeep.registerMany(kim); - return kim.toString(); - } - private Object readValue() throws JSONException { switch (read(2)) { case 0: @@ -298,7 +273,7 @@ private Object readValue() throws JSONException { integer += int7; break; } - return new Integer(integer); + return integer; case 1: byte[] bytes = new byte[256]; int length = 0; @@ -314,13 +289,13 @@ private Object readValue() throws JSONException { try { value = JSONObject.stringToValue(new String(bytes, 0, length, "US-ASCII")); - } catch (UnsupportedEncodingException e) { + } catch (java.io.UnsupportedEncodingException e) { throw new JSONException(e); } - this.values.register(value); + this.valuekeep.register(value); return value; case 2: - return getAndTick(this.values, this.bitreader); + return getAndTick(this.valuekeep, this.bitreader); case 3: return readJSON(); default: @@ -328,8 +303,8 @@ private Object readValue() throws JSONException { } } - public Object unzip() throws JSONException { - begin(); + public Object decode() throws JSONException { + generate(); return readJSON(); } } diff --git a/zip/Compressor.java b/zip/Zipper.java similarity index 70% rename from zip/Compressor.java rename to zip/Zipper.java index 5937d6612..48b4f1acb 100644 --- a/zip/Compressor.java +++ b/zip/Zipper.java @@ -1,6 +1,5 @@ package org.json.zip; -import java.io.IOException; import java.util.Collection; import java.util.Iterator; import java.util.Map; @@ -35,36 +34,36 @@ of this software and associated documentation files (the "Software"), to deal */ /** - * JSONzip is a compression scheme for JSON text. + * JSONzip is a binary compression scheme for JSON text. * * @author JSON.org - * @version 2014-04-28 + * @version 2014-05-03 */ /** - * A compressor implements the compression behavior of JSONzip. It provides a + * An encoder implements the compression behavior of JSONzip. It provides a * zip method that takes a JSONObject or JSONArray and delivers a stream of * bits to a BitWriter. * * FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY * FOR PRODUCTION USE. */ -public class Compressor extends JSONzip { +public class Zipper extends JSONzip { /** - * A compressor outputs to a BitWriter. + * An encoder outputs to a BitWriter. */ final BitWriter bitwriter; /** - * Create a new compressor. It may be used for an entire session or + * Create a new encoder. It may be used for an entire session or * subsession. * * @param bitwriter - * The BitWriter this Compressor will output to. Don't forget to - * flush. + * The BitWriter this encoder will output to. + * Don't forget to flush. */ - public Compressor(BitWriter bitwriter) { + public Zipper(BitWriter bitwriter) { super(); this.bitwriter = bitwriter; } @@ -76,7 +75,7 @@ public Compressor(BitWriter bitwriter) { * * @param digit * An ASCII character from a JSON number. - * @return + * @return The number code. */ private static int bcd(char digit) { if (digit >= '0' && digit <= '9') { @@ -107,7 +106,7 @@ public void flush() throws JSONException { /** * Output a one bit. * - * @throws IOException + * @throws JSONException */ private void one() throws JSONException { write(1, 1); @@ -116,15 +115,15 @@ private void one() throws JSONException { /** * Pad the output to fill an allotment of bits. * - * @param factor + * @param width * The size of the bit allotment. A value of 8 will complete and * flush the current byte. If you don't pad, then some of the * last bits might not be sent to the Output Stream. * @throws JSONException */ - public void pad(int factor) throws JSONException { + public void pad(int width) throws JSONException { try { - this.bitwriter.pad(factor); + this.bitwriter.pad(width); } catch (Throwable e) { throw new JSONException(e); } @@ -171,29 +170,19 @@ private void write(int integer, Huff huff) throws JSONException { * A kim containing the bytes to be written. * @param huff * The Huffman encoder. + * @param ext + * The Huffman encoder for the extended bytes. * @throws JSONException */ - private void write(Kim kim, Huff huff) throws JSONException { - write(kim, 0, kim.length, huff); - } - - /** - * Write a range of bytes from a Kim with Huffman encoding. - * - * @param kim - * A Kim containing the bytes to be written. - * @param from - * The index of the first byte to write. - * @param thru - * The index after the last byte to write. - * @param huff - * The Huffman encoder. - * @throws JSONException - */ - private void write(Kim kim, int from, int thru, Huff huff) - throws JSONException { - for (int at = from; at < thru; at += 1) { - write(kim.get(at), huff); + private void write(Kim kim, Huff huff, Huff ext) throws JSONException { + for (int at = 0; at < kim.length; at += 1) { + int c = kim.get(at); + write(c, huff); + while ((c & 128) == 128) { + at += 1; + c = kim.get(at); + write(c, ext); + } } } @@ -207,7 +196,7 @@ private void write(Kim kim, int from, int thru, Huff huff) * The Keep that the integer is one of. * @throws JSONException */ - private void writeAndTick(int integer, Keep keep) throws JSONException { + private void write(int integer, Keep keep) throws JSONException { int width = keep.bitsize(); keep.tick(integer); if (probe) { @@ -219,10 +208,10 @@ private void writeAndTick(int integer, Keep keep) throws JSONException { /** * Write a JSON Array. * - * @param jsonarray - * @throws JSONException + * @param jsonarray The JSONArray to write. + * @throws JSONException If the write fails. */ - private void writeArray(JSONArray jsonarray) throws JSONException { + private void write(JSONArray jsonarray) throws JSONException { // JSONzip has three encodings for arrays: // The array is empty (zipEmptyArray). @@ -295,9 +284,9 @@ private void writeJSON(Object value) throws JSONException { value = new JSONArray(value); } if (value instanceof JSONObject) { - writeObject((JSONObject) value); + write((JSONObject) value); } else if (value instanceof JSONArray) { - writeArray((JSONArray) value); + write((JSONArray) value); } else { throw new JSONException("Unrecognized object"); } @@ -308,7 +297,7 @@ private void writeJSON(Object value) throws JSONException { * Write the name of an object property. Names have their own Keep and * Huffman encoder because they are expected to be a more restricted set. * - * @param name + * @param name The name string. * @throws JSONException */ private void writeName(String name) throws JSONException { @@ -320,13 +309,13 @@ private void writeName(String name) throws JSONException { int integer = this.namekeep.find(kim); if (integer != none) { one(); - writeAndTick(integer, this.namekeep); + write(integer, this.namekeep); } else { // Otherwise, emit the string with Huffman encoding, and register it. zero(); - write(kim, this.namehuff); + write(kim, this.namehuff, this.namehuffext); write(end, namehuff); this.namekeep.register(kim); } @@ -335,17 +324,16 @@ private void writeName(String name) throws JSONException { /** * Write a JSON object. * - * @param jsonobject - * @return + * @param jsonobject The JSONObject to be written. * @throws JSONException */ - private void writeObject(JSONObject jsonobject) throws JSONException { + private void write(JSONObject jsonobject) throws JSONException { // JSONzip has two encodings for objects: Empty Objects (zipEmptyObject) and // non-empty objects (zipObject). boolean first = true; - Iterator keys = jsonobject.keys(); + Iterator keys = jsonobject.keys(); while (keys.hasNext()) { if (probe) { log(); @@ -379,7 +367,7 @@ private void writeObject(JSONObject jsonobject) throws JSONException { /** * Write a string. * - * @param string + * @param string The string to write. * @throws JSONException */ private void writeString(String string) throws JSONException { @@ -388,9 +376,7 @@ private void writeString(String string) throws JSONException { if (string.length() == 0) { zero(); - zero(); - write(end, this.substringhuff); - zero(); + write(end, this.stringhuff); } else { Kim kim = new Kim(string); @@ -400,90 +386,18 @@ private void writeString(String string) throws JSONException { int integer = this.stringkeep.find(kim); if (integer != none) { one(); - writeAndTick(integer, this.stringkeep); + write(integer, this.stringkeep); } else { -// But if it is not found, emit the string's substrings. Register the string -// so that the next lookup will succeed. - - writeSubstring(kim); - this.stringkeep.register(kim); - } - } - } +// But if it is not found, emit the string's characters. Register the string +// so that a later lookup can succeed. - /** - * Write a string, attempting to match registered substrings. - * - * @param kim - * @throws JSONException - */ - private void writeSubstring(Kim kim) throws JSONException { - this.substringkeep.reserve(); - zero(); - int from = 0; - int thru = kim.length; - int until = thru - JSONzip.minSubstringLength; - int previousFrom = none; - int previousThru = 0; - -// Find a substring from the substring keep. - - while (true) { - int at; - int integer = none; - for (at = from; at <= until; at += 1) { - integer = this.substringkeep.match(kim, at, thru); - if (integer != none) { - break; - } - } - if (integer == none) { - break; - } - -// If a substring is found, emit any characters that were before the matched -// substring. Then emit the substring's integer and loop back to match the -// remainder with another substring. - - if (from != at) { zero(); - write(kim, from, at, this.substringhuff); - write(end, this.substringhuff); - if (previousFrom != none) { - this.substringkeep.registerOne(kim, previousFrom, - previousThru); - previousFrom = none; - } - } - one(); - writeAndTick(integer, this.substringkeep); - from = at + this.substringkeep.length(integer); - if (previousFrom != none) { - this.substringkeep.registerOne(kim, previousFrom, - previousThru); - previousFrom = none; - } - previousFrom = at; - previousThru = from + 1; - } - -// If a substring is not found, then emit the remaining characters. - - zero(); - if (from < thru) { - write(kim, from, thru, this.substringhuff); - if (previousFrom != none) { - this.substringkeep.registerOne(kim, previousFrom, previousThru); + write(kim, this.stringhuff, this.stringhuffext); + write(end, this.stringhuff); + this.stringkeep.register(kim); } } - write(end, this.substringhuff); - zero(); - -// Register the string's substrings in the trie in hopes of future substring -// matching. - - substringkeep.registerMany(kim); } /** @@ -496,10 +410,10 @@ private void writeSubstring(Kim kim) throws JSONException { private void writeValue(Object value) throws JSONException { if (value instanceof Number) { String string = JSONObject.numberToString((Number) value); - int integer = this.values.find(string); + int integer = this.valuekeep.find(string); if (integer != none) { write(2, 2); - writeAndTick(integer, this.values); + write(integer, this.valuekeep); return; } if (value instanceof Integer || value instanceof Long) { @@ -527,7 +441,7 @@ private void writeValue(Object value) throws JSONException { write(bcd(string.charAt(i)), 4); } write(endOfNumber, 4); - this.values.register(string); + this.valuekeep.register(string); } else { write(3, 2); writeJSON(value); @@ -538,32 +452,30 @@ private void writeValue(Object value) throws JSONException { * Output a zero bit. * * @throws JSONException - * - * @throws IOException */ private void zero() throws JSONException { write(0, 1); } /** - * Compress a JSONObject. + * Encode a JSONObject. * - * @param jsonobject + * @param jsonobject The JSONObject. * @throws JSONException */ - public void zip(JSONObject jsonobject) throws JSONException { - begin(); + public void encode(JSONObject jsonobject) throws JSONException { + generate(); writeJSON(jsonobject); } /** - * Compress a JSONArray. + * Encode a JSONArray. * - * @param jsonarray + * @param jsonarray The JSONArray. * @throws JSONException */ - public void zip(JSONArray jsonarray) throws JSONException { - begin(); + public void encode(JSONArray jsonarray) throws JSONException { + generate(); writeJSON(jsonarray); } } From 1272d80a034976d4c571fa62ae9c7efbc5a6814d Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 20 May 2014 14:39:51 -0700 Subject: [PATCH 15/31] education --- zip/Huff.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/zip/Huff.java b/zip/Huff.java index 98c650e69..a7849ae93 100644 --- a/zip/Huff.java +++ b/zip/Huff.java @@ -29,7 +29,7 @@ of this software and associated documentation files (the "Software"), to deal /** * JSONzip is a compression scheme for JSON text. * @author JSON.org - * @version 2014-05-03 + * @version 2014-05-20 */ /** @@ -53,6 +53,11 @@ public class Huff implements None, PostMortem { */ private final int domain; + /** + * The number of characters to process before generation is no longer done. + */ + public static final int education = 1000000; + /** * An array that maps symbol values to symbols. */ @@ -140,7 +145,7 @@ public boolean postMortem(PostMortem pm) { */ public Huff(int domain) { this.domain = domain; - this.toLearn = 1000000; + this.toLearn = education; int length = domain * 2 - 1; this.symbols = new Symbol[length]; From aab1017e66f9faf03f5c49e9f167d2be66488215 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 20 May 2014 15:22:05 -0700 Subject: [PATCH 16/31] this.namehuffext.generate --- zip/JSONzip.java | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/zip/JSONzip.java b/zip/JSONzip.java index 220686de3..d8e3ac652 100644 --- a/zip/JSONzip.java +++ b/zip/JSONzip.java @@ -44,7 +44,7 @@ of this software and associated documentation files (the "Software"), to deal * ADEQUATELY FOR PRODUCTION USE. * * @author JSON.org - * @version 2014-05-03 + * @version 2014-05-20 */ public abstract class JSONzip implements None, PostMortem { /** @@ -55,29 +55,29 @@ public abstract class JSONzip implements None, PostMortem { }; /** - * The first positive integer than cannot be encoded in 4 bits. + * The end of string code. */ - public static final long int4 = 16; + public static final int end = 256; /** - * The first positive integer than cannot be encoded in 7 bits. + * The end of number code. */ - public static final long int7 = 144; + public static final int endOfNumber = bcd.length; /** - * The first positive integer than cannot be encoded in 14 bits. + * The first positive integer that cannot be encoded in 4 bits. */ - public static final long int14 = 16528; + public static final long int4 = 16; /** - * The end of string code. + * The first positive integer that cannot be encoded in 7 bits. */ - public static final int end = 256; + public static final long int7 = 144; /** - * The end of number code. + * The first positive integer that cannot be encoded in 14 bits. */ - public static final int endOfNumber = bcd.length; + public static final long int14 = 16528; /** * The package supports tracing for debugging. @@ -177,6 +177,7 @@ protected JSONzip() { */ protected void generate() { this.namehuff.generate(); + this.namehuffext.generate(); this.stringhuff.generate(); this.stringhuffext.generate(); } From 8114b976ce7224ff3b89f3d7e53f4207b7dcdf4f Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Wed, 21 May 2014 20:59:05 -0700 Subject: [PATCH 17/31] deleted --- zip/MapKeep.java | 160 ------------------- zip/TrieKeep.java | 396 ---------------------------------------------- 2 files changed, 556 deletions(-) delete mode 100644 zip/MapKeep.java delete mode 100644 zip/TrieKeep.java diff --git a/zip/MapKeep.java b/zip/MapKeep.java deleted file mode 100644 index 1374e08d3..000000000 --- a/zip/MapKeep.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.json.zip; - -import java.util.HashMap; - -import org.json.Kim; - -/* - Copyright (c) 2013 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 keep is an associative data structure that maintains usage counts of each - * of the associations in its keeping. When the keep becomes full, it purges - * little used associations, and ages the survivors. Each key is assigned an - * integer value. When the keep is compacted, each key can be given a new - * value. - */ -class MapKeep extends Keep { - private Object[] list; - private HashMap map; - - /** - * Create a new Keep. - * @param bits - * The capacity of the keep expressed in the number of bits - * required to hold an integer. - */ - public MapKeep(int bits) { - super(bits); - this.list = new Object[this.capacity]; - this.map = new HashMap(this.capacity); - } - - /** - * Compact the keep. A keep may contain at most this.capacity elements. - * The keep contents can be reduced by deleting all elements with low use - * counts, and by reducing the use counts of the survivors. - */ - private void compact() { - int from = 0; - int to = 0; - while (from < this.capacity) { - Object key = this.list[from]; - long usage = age(this.uses[from]); - if (usage > 0) { - this.uses[to] = usage; - this.list[to] = key; - this.map.put(key, new Integer(to)); - to += 1; - } else { - this.map.remove(key); - } - from += 1; - } - if (to < this.capacity) { - this.length = to; - } else { - this.map.clear(); - this.length = 0; - } - this.power = 0; - } - - /** - * Find the integer value associated with this key, or nothing if this key - * is not in the keep. - * - * @param key - * An object. - * @return An integer - */ - public int find(Object key) { - Object o = this.map.get(key); - return o instanceof Integer ? ((Integer) o).intValue() : none; - } - - public boolean postMortem(PostMortem pm) { - MapKeep that = (MapKeep) pm; - if (this.length != that.length) { - JSONzip.log(this.length + " <> " + that.length); - return false; - } - for (int i = 0; i < this.length; i += 1) { - boolean b; - if (this.list[i] instanceof Kim) { - b = ((Kim) this.list[i]).equals(that.list[i]); - } else { - Object o = this.list[i]; - Object q = that.list[i]; - if (o instanceof Number) { - o = o.toString(); - } - if (q instanceof Number) { - q = q.toString(); - } - b = o.equals(q); - } - if (!b) { - JSONzip.log("\n[" + i + "]\n " + this.list[i] + "\n " - + that.list[i] + "\n " + this.uses[i] + "\n " - + that.uses[i]); - return false; - } - } - return true; - } - - /** - * Register a value in the keep. Compact the keep if it is full. The next - * time this value is encountered, its integer can be sent instead. - * @param value A value. - */ - public void register(Object value) { - if (JSONzip.probe) { - int integer = find(value); - if (integer >= 0) { - JSONzip.log("\nDuplicate key " + value); - } - } - if (this.length >= this.capacity) { - compact(); - } - this.list[this.length] = value; - this.map.put(value, new Integer(this.length)); - this.uses[this.length] = 1; - if (JSONzip.probe) { - JSONzip.log("<" + this.length + " " + value + "> "); - } - this.length += 1; - } - - /** - * Return the value associated with the integer. - * @param integer The number of an item in the keep. - * @return The value. - */ - public Object value(int integer) { - return this.list[integer]; - } -} diff --git a/zip/TrieKeep.java b/zip/TrieKeep.java deleted file mode 100644 index dcb13c7a0..000000000 --- a/zip/TrieKeep.java +++ /dev/null @@ -1,396 +0,0 @@ -package org.json.zip; - -import org.json.Kim; - -/* - Copyright (c) 2013 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 TrieKeep is a Keep that implements a Trie. - */ -class TrieKeep extends Keep { - - /** - * The trie is made of nodes. - */ - class Node implements PostMortem { - private int integer; - private Node[] next; - - /** - * Each non-leaf node contains links to up to 256 next nodes. Each node - * has an integer value. - */ - public Node() { - this.integer = none; - this.next = null; - } - - /** - * Get one of a node's 256 links. If it is a leaf node, it returns - * null. - * - * @param cell - * A integer between 0 and 255. - * @return - */ - public Node get(int cell) { - return this.next == null ? null : this.next[cell]; - } - - /** - * Get one of a node's 256 links. If it is a leap node, it returns - * null. The argument is treated as an unsigned integer. - * - * @param cell - * A byte. - * @return - */ - public Node get(byte cell) { - return get(((int) cell) & 0xFF); - } - - /** - * Compare two nodes. Their lengths must be equal. Their links must - * also compare. - */ - public boolean postMortem(PostMortem pm) { - Node that = (Node) pm; - if (that == null) { - JSONzip.log("\nMisalign"); - return false; - } - if (this.integer != that.integer) { - JSONzip.log("\nInteger " + this.integer + " <> " + - that.integer); - return false; - } - if (this.next == null) { - if (that.next == null) { - return true; - } - JSONzip.log("\nNext is null " + this.integer); - return false; - } - for (int i = 0; i < 256; i += 1) { - Node node = this.next[i]; - if (node != null) { - if (!node.postMortem(that.next[i])) { - return false; - } - } else if (that.next[i] != null) { - JSONzip.log("\nMisalign " + i); - return false; - } - } - return true; - } - - /** - * Set a node's link to another node. - * - * @param cell - * An integer between 0 and 255. - * @param node - * The new value for the cell. - */ - public void set(int cell, Node node) { - if (this.next == null) { - this.next = new Node[256]; - } - if (JSONzip.probe) { - if (node == null || this.next[cell] != null) { - JSONzip.log("\nUnexpected set.\n"); - } - } - this.next[cell] = node; - } - - /** - * Set a node's link to another node. - * - * @param cell - * A byte. - * @param node - * The new value for the cell. - */ - public void set(byte cell, Node node) { - set(((int) cell) & 0xFF, node); - } - - /** - * Get one of a node's 256 links. It will not return null. If there is - * no link, then a link is manufactured. - * - * @param cell - * A integer between 0 and 255. - * @return - */ - public Node vet(int cell) { - Node node = get(cell); - if (node == null) { - node = new Node(); - set(cell, node); - } - return node; - } - - /** - * Get one of a node's 256 links. It will not return null. If there is - * no link, then a link is manufactured. - * - * @param cell - * A byte. - * @return - */ - public Node vet(byte cell) { - return vet(((int) cell) & 0xFF); - } - } - - private int[] froms; - private int[] thrus; - private Node root; - private Kim[] kims; - - /** - * Create a new Keep of kims. - * - * @param bits - * The log2 of the capacity of the Keep. For example, if bits is - * 12, then the keep's capacity will be 4096. - */ - public TrieKeep(int bits) { - super(bits); - this.froms = new int[this.capacity]; - this.thrus = new int[this.capacity]; - this.kims = new Kim[this.capacity]; - this.root = new Node(); - } - - /** - * Get the kim associated with an integer. - * - * @param integer - * @return - */ - public Kim kim(int integer) { - Kim kim = this.kims[integer]; - int from = this.froms[integer]; - int thru = this.thrus[integer]; - if (from != 0 || thru != kim.length) { - kim = new Kim(kim, from, thru); - this.froms[integer] = 0; - this.thrus[integer] = kim.length; - this.kims[integer] = kim; - } - return kim; - } - - /** - * Get the length of the Kim associated with an integer. This is sometimes - * much faster than get(integer).length. - * - * @param integer - * @return - */ - public int length(int integer) { - return this.thrus[integer] - this.froms[integer]; - } - - /** - * Find the integer value associated with this key, or nothing if this key - * is not in the keep. - * - * @param key - * An object. - * @return An integer - */ - public int match(Kim kim, int from, int thru) { - Node node = this.root; - int best = none; - for (int at = from; at < thru; at += 1) { - node = node.get(kim.get(at)); - if (node == null) { - break; - } - if (node.integer != none) { - best = node.integer; - } - from += 1; - } - return best; - } - - public boolean postMortem(PostMortem pm) { - boolean result = true; - TrieKeep that = (TrieKeep) pm; - if (this.length != that.length) { - JSONzip.log("\nLength " + this.length + " <> " + that.length); - return false; - } - if (this.capacity != that.capacity) { - JSONzip.log("\nCapacity " + this.capacity + " <> " + - that.capacity); - return false; - } - for (int i = 0; i < this.length; i += 1) { - Kim thiskim = this.kim(i); - Kim thatkim = that.kim(i); - if (!thiskim.equals(thatkim)) { - JSONzip.log("\n[" + i + "] " + thiskim + " <> " + thatkim); - result = false; - } - } - return result && this.root.postMortem(that.root); - } - - public void registerMany(Kim kim) { - int length = kim.length; - int limit = this.capacity - this.length; - if (limit > JSONzip.substringLimit) { - limit = JSONzip.substringLimit; - } - int until = length - (JSONzip.minSubstringLength - 1); - for (int from = 0; from < until; from += 1) { - int len = length - from; - if (len > JSONzip.maxSubstringLength) { - len = JSONzip.maxSubstringLength; - } - len += from; - Node node = this.root; - for (int at = from; at < len; at += 1) { - Node next = node.vet(kim.get(at)); - if (next.integer == none - && at - from >= (JSONzip.minSubstringLength - 1)) { - next.integer = this.length; - this.uses[this.length] = 1; - this.kims[this.length] = kim; - this.froms[this.length] = from; - this.thrus[this.length] = at + 1; - if (JSONzip.probe) { - try { - JSONzip.log("<<" + this.length + " " - + new Kim(kim, from, at + 1) + ">> "); - } catch (Throwable ignore) { - } - } - this.length += 1; - limit -= 1; - if (limit <= 0) { - return; - } - } - node = next; - } - } - } - - public void registerOne(Kim kim) { - int integer = registerOne(kim, 0, kim.length); - if (integer != none) { - this.kims[integer] = kim; - } - } - - public int registerOne(Kim kim, int from, int thru) { - if (this.length < this.capacity) { - Node node = this.root; - for (int at = from; at < thru; at += 1) { - node = node.vet(kim.get(at)); - } - if (node.integer == none) { - int integer = this.length; - node.integer = integer; - this.uses[integer] = 1; - this.kims[integer] = kim; - this.froms[integer] = from; - this.thrus[integer] = thru; - if (JSONzip.probe) { - try { - JSONzip.log("<<" + integer + " " + new Kim(kim, from, thru) + ">> "); - } catch (Throwable ignore) { - } - } - this.length += 1; - return integer; - } - } - return none; - } - - /** - * Reserve space in the keep, compacting if necessary. A keep may contain - * at most -capacity- elements. The keep contents can be reduced by - * deleting all elements with low use counts, rebuilding the trie with the - * survivors. - */ - public void reserve() { - if (this.capacity - this.length < JSONzip.substringLimit) { - int from = 0; - int to = 0; - this.root = new Node(); - while (from < this.capacity) { - if (this.uses[from] > 1) { - Kim kim = this.kims[from]; - int thru = this.thrus[from]; - Node node = this.root; - for (int at = this.froms[from]; at < thru; at += 1) { - Node next = node.vet(kim.get(at)); - node = next; - } - node.integer = to; - this.uses[to] = age(this.uses[from]); - this.froms[to] = this.froms[from]; - this.thrus[to] = thru; - this.kims[to] = kim; - to += 1; - } - from += 1; - } - -// It is possible, but highly unlikely, that too many items survive. -// If that happens, clear the keep. - - if (this.capacity - to < JSONzip.substringLimit) { - this.power = 0; - this.root = new Node(); - to = 0; - } - this.length = to; - while (to < this.capacity) { - this.uses[to] = 0; - this.kims[to] = null; - this.froms[to] = 0; - this.thrus[to] = 0; - to += 1; - - } - } - } - - public Object value(int integer) { - return kim(integer); - } -} From 9b6872b6e52e28b1b99aa328c2109436de58622f Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 6 Feb 2015 09:02:41 -0800 Subject: [PATCH 18/31] This package needs a new owner. --- Kim.java | 372 ------------------------------ README | 7 +- zip/BitInputStream.java | 153 ------------- zip/BitOutputStream.java | 154 ------------- zip/BitReader.java | 42 ---- zip/BitWriter.java | 45 ---- zip/Huff.java | 404 -------------------------------- zip/JSONzip.java | 255 --------------------- zip/Keep.java | 191 ---------------- zip/None.java | 15 -- zip/PostMortem.java | 47 ---- zip/README | 2 - zip/Unzipper.java | 310 ------------------------- zip/Zipper.java | 481 --------------------------------------- 14 files changed, 6 insertions(+), 2472 deletions(-) delete mode 100644 Kim.java delete mode 100644 zip/BitInputStream.java delete mode 100644 zip/BitOutputStream.java delete mode 100644 zip/BitReader.java delete mode 100644 zip/BitWriter.java delete mode 100644 zip/Huff.java delete mode 100644 zip/JSONzip.java delete mode 100644 zip/Keep.java delete mode 100644 zip/None.java delete mode 100644 zip/PostMortem.java delete mode 100644 zip/README delete mode 100644 zip/Unzipper.java delete mode 100644 zip/Zipper.java diff --git a/Kim.java b/Kim.java deleted file mode 100644 index 9f7af92d0..000000000 --- a/Kim.java +++ /dev/null @@ -1,372 +0,0 @@ -package org.json; - - -/* - Copyright (c) 2013 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. - */ - -/** - * Kim makes immutable eight bit Unicode strings. If the MSB of a byte is set, - * then the next byte is a continuation byte. The last byte of a character - * never has the MSB reset. Every byte that is not the last byte has the MSB - * set. Kim stands for "Keep it minimal". A Unicode character is never longer - * than 3 bytes. Every byte contributes 7 bits to the character. ASCII is - * unmodified. - * - * Kim UTF-8 - * one byte U+007F U+007F - * two bytes U+3FFF U+07FF - * three bytes U+10FFF U+FFFF - * four bytes U+10FFFF - * - * Characters in the ranges U+0800..U+3FFF and U+10000..U+10FFFF will be one - * byte smaller when encoded in Kim compared to UTF-8. - * - * Kim is beneficial when using scripts such as Old South Arabian, Aramaic, - * Avestan, Balinese, Batak, Bopomofo, Buginese, Buhid, Carian, Cherokee, - * Coptic, Cyrillic, Deseret, Egyptian Hieroglyphs, Ethiopic, Georgian, - * Glagolitic, Gothic, Hangul Jamo, Hanunoo, Hiragana, Kanbun, Kaithi, - * Kannada, Katakana, Kharoshthi, Khmer, Lao, Lepcha, Limbu, Lycian, Lydian, - * Malayalam, Mandaic, Meroitic, Miao, Mongolian, Myanmar, New Tai Lue, - * Ol Chiki, Old Turkic, Oriya, Osmanya, Pahlavi, Parthian, Phags-Pa, - * Phoenician, Samaritan, Sharada, Sinhala, Sora Sompeng, Tagalog, Tagbanwa, - * Takri, Tai Le, Tai Tham, Tamil, Telugu, Thai, Tibetan, Tifinagh, UCAS. - * - * A kim object can be constructed from an ordinary UTF-16 string, or from a - * byte array. A kim object can produce a UTF-16 string. - * - * As with UTF-8, it is possible to detect character boundaries within a byte - * sequence. UTF-8 is one of the world's great inventions. While Kim is more - * efficient, it is not clear that it is worth the expense of transition. - * - * @version 2013-04-18 - */ -public class Kim { - - /** - * The byte array containing the kim's content. - */ - private byte[] bytes = null; - - /** - * The kim's hashcode, conforming to Java's hashcode conventions. - */ - private int hashcode = 0; - - /** - * The number of bytes in the kim. The number of bytes can be as much as - * three times the number of characters. - */ - public int length = 0; - - /** - * The memoization of toString(). - */ - private String string = null; - - /** - * Make a kim from a portion of a byte array. - * - * @param bytes - * A byte array. - * @param from - * The index of the first byte. - * @param thru - * The index of the last byte plus one. - */ - public Kim(byte[] bytes, int from, int thru) { - -// As the bytes are copied into the new kim, a hashcode is computed using a -// modified Fletcher code. - - int sum = 1; - int value; - this.hashcode = 0; - this.length = thru - from; - if (this.length > 0) { - this.bytes = new byte[this.length]; - for (int at = 0; at < this.length; at += 1) { - value = (int) bytes[at + from] & 0xFF; - sum += value; - this.hashcode += sum; - this.bytes[at] = (byte) value; - } - this.hashcode += sum << 16; - } - } - - /** - * Make a kim from a byte array. - * - * @param bytes - * The byte array. - * @param length - * The number of bytes. - */ - public Kim(byte[] bytes, int length) { - this(bytes, 0, length); - } - - /** - * Make a new kim from a substring of an existing kim. The coordinates are - * in byte units, not character units. - * - * @param kim - * The source of bytes. - * @param from - * The point at which to take bytes. - * @param thru - * The point at which to stop taking bytes. - */ - public Kim(Kim kim, int from, int thru) { - this(kim.bytes, from, thru); - } - - /** - * Make a kim from a string. - * - * @param string - * The string. - * @throws JSONException - * if surrogate pair mismatch. - */ - public Kim(String string) throws JSONException { - int stringLength = string.length(); - this.hashcode = 0; - this.length = 0; - -// First pass: Determine the length of the kim, allowing for the UTF-16 -// to UTF-32 conversion, and then the UTF-32 to Kim conversion. - - if (stringLength > 0) { - for (int i = 0; i < stringLength; i += 1) { - int c = string.charAt(i); - if (c <= 0x7F) { - this.length += 1; - } else if (c <= 0x3FFF) { - this.length += 2; - } else { - if (c >= 0xD800 && c <= 0xDFFF) { - i += 1; - int d = string.charAt(i); - if (c > 0xDBFF || d < 0xDC00 || d > 0xDFFF) { - throw new JSONException("Bad UTF16"); - } - } - this.length += 3; - } - } - -// Second pass: Allocate a byte array and fill that array with the conversion -// while computing the hashcode. - - this.bytes = new byte[length]; - int at = 0; - int b; - int sum = 1; - for (int i = 0; i < stringLength; i += 1) { - int character = string.charAt(i); - if (character <= 0x7F) { - bytes[at] = (byte) character; - sum += character; - this.hashcode += sum; - at += 1; - } else if (character <= 0x3FFF) { - b = 0x80 | (character >>> 7); - bytes[at] = (byte) b; - sum += b; - this.hashcode += sum; - at += 1; - b = character & 0x7F; - bytes[at] = (byte) b; - sum += b; - this.hashcode += sum; - at += 1; - } else { - if (character >= 0xD800 && character <= 0xDBFF) { - i += 1; - character = (((character & 0x3FF) << 10) | (string - .charAt(i) & 0x3FF)) + 65536; - } - b = 0x80 | (character >>> 14); - bytes[at] = (byte) b; - sum += b; - this.hashcode += sum; - at += 1; - b = 0x80 | ((character >>> 7) & 0xFF); - bytes[at] = (byte) b; - sum += b; - this.hashcode += sum; - at += 1; - b = character & 0x7F; - bytes[at] = (byte) b; - sum += b; - this.hashcode += sum; - at += 1; - } - } - this.hashcode += sum << 16; - } - } - - /** - * Returns the character at the specified index. The index refers to byte - * values and ranges from 0 to length - 1. The index of the next character - * is at index + Kim.characterSize(kim.characterAt(index)). - * - * @param at - * the index of the char value. The first character is at 0. - * @returns a Unicode character between 0 and 0x10FFFF. - * @throws JSONException - * if at does not point to a valid character. - */ - public int characterAt(int at) throws JSONException { - int c = get(at); - if ((c & 0x80) == 0) { - return c; - } - int character; - int c1 = get(at + 1); - if ((c1 & 0x80) == 0) { - character = ((c & 0x7F) << 7) | c1; - if (character > 0x7F) { - return character; - } - } else { - int c2 = get(at + 2); - character = ((c & 0x7F) << 14) | ((c1 & 0x7F) << 7) | c2; - if ((c2 & 0x80) == 0 && character > 0x3FFF && character <= 0x10FFFF - && (character < 0xD800 || character > 0xDFFF)) { - return character; - } - } - throw new JSONException("Bad character at " + at); - } - - /** - * Returns the number of bytes needed to contain the character in Kim - * format. - * - * @param character - * a Unicode character between 0 and 0x10FFFF. - * @return 1, 2, or 3 - * @throws JSONException - * if the character is not representable in a kim. - */ - public static int characterSize(int character) throws JSONException { - if (character < 0 || character > 0x10FFFF) { - throw new JSONException("Bad character " + character); - } - return character <= 0x7F ? 1 : character <= 0x3FFF ? 2 : 3; - } - - /** - * Copy the contents of this kim to a byte array. - * - * @param bytes - * A byte array of sufficient size. - * @param at - * The position within the byte array to take the byes. - * @return The position immediately after the copy. - */ - public int copy(byte[] bytes, int at) { - System.arraycopy(this.bytes, 0, bytes, at, this.length); - return at + this.length; - } - - /** - * Two kim objects containing exactly the same bytes in the same order are - * equal to each other. - * - * @param obj - * the other kim with which to compare. - * @returns true if this and obj are both kim objects containing identical - * byte sequences. - */ - public boolean equals(Object obj) { - if (!(obj instanceof Kim)) { - return false; - } - Kim that = (Kim) obj; - if (this == that) { - return true; - } - if (this.hashcode != that.hashcode) { - return false; - } - return java.util.Arrays.equals(this.bytes, that.bytes); - } - - /** - * Get a byte from a kim. - * @param at - * The position of the byte. The first byte is at 0. - * @return The byte. - * @throws JSONException - * if there is no byte at that position. - */ - public int get(int at) throws JSONException { - if (at < 0 || at > this.length) { - throw new JSONException("Bad character at " + at); - } - return ((int) this.bytes[at]) & 0xFF; - } - - /** - * Returns a hash code value for the kim. - */ - public int hashCode() { - return this.hashcode; - } - - /** - * Produce a UTF-16 String from this kim. The number of codepoints in the - * string will not be greater than the number of bytes in the kim, although - * it could be less. - * - * @return The string. A kim memoizes its string representation. - * @throws JSONException - * if the kim is not valid. - */ - public String toString() throws JSONException { - if (this.string == null) { - int c; - int length = 0; - char chars[] = new char[this.length]; - for (int at = 0; at < this.length; at += characterSize(c)) { - c = this.characterAt(at); - if (c < 0x10000) { - chars[length] = (char) c; - length += 1; - } else { - chars[length] = (char) (0xD800 | ((c - 0x10000) >>> 10)); - length += 1; - chars[length] = (char) (0xDC00 | (c & 0x03FF)); - length += 1; - } - } - this.string = new String(chars, 0, length); - } - return this.string; - } -} diff --git a/README b/README index 6afe0c691..2de22ffc4 100755 --- a/README +++ b/README @@ -1,9 +1,14 @@ JSON in Java [package org.json] +This package needs a new owner. I have not used it in over a decade, and I do +not have time to maintain programs that I do not use. + +If you think you can give this package a good home, please contact me. + Douglas Crockford douglas@crockford.com -2011-02-02 +2015-02-06 JSON is a light-weight, language independent, data interchange format. diff --git a/zip/BitInputStream.java b/zip/BitInputStream.java deleted file mode 100644 index 2282d30c0..000000000 --- a/zip/BitInputStream.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.json.zip; - -import java.io.IOException; -import java.io.InputStream; - -/* - Copyright (c) 2013 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. - */ - -/** - * This is a big endian bit reader. It reads its bits from an InputStream. - * - * @version 2013-05-03 - * - */ -public class BitInputStream implements BitReader { - /** - * The number of bits remaining in the current byte. - */ - private int available = 0; - - /** - * Up to a byte's worth of unread bits. - */ - private int unread = 0; - - /** - * The source of the bits. - */ - private InputStream in; - - /** - * The number of bits read so far. This is used in padding. - */ - private long nrBits = 0; - - /** - * Make a BitReader from an InputStream. The BitReader will take bytes from - * the InputStream and unpack them into bits. - * - * @param in - * An InputStream. - */ - public BitInputStream(InputStream in) { - this.in = in; - } - - /** - * Read one bit. - * - * @return true if it is a 1 bit. - */ - public boolean bit() throws IOException { - return read(1) != 0; - } - - /** - * Get the number of bits that have been read from this BitInputStream. - * This includes pad bits that have been skipped, but might not include - * bytes that have been read from the underlying InputStream that have not - * yet been delivered as bits. - * - * @return The number of bits read so far. - */ - public long nrBits() { - return this.nrBits; - } - - /** - * Check that the rest of the block has been padded with zeroes. - * - * @param width - * The size of the block to pad in bits. - * This will typically be 8, 16, 32, 64, 128, 256, etc. - * @return true if the block was zero padded, or false if the the padding - * contains any one bits. - * @throws IOException - */ - public boolean pad(int width) throws IOException { - boolean result = true; - int gap = (int)this.nrBits % width; - if (gap < 0) { - gap += width; - } - if (gap != 0) { - int padding = width - gap; - while (padding > 0) { - if (bit()) { - result = false; - } - padding -= 1; - } - } - return result; - } - - /** - * Read some bits. - * - * @param width - * The number of bits to read. (0..32) - * @throws IOException - * @return the bits - */ - public int read(int width) throws IOException { - if (width == 0) { - return 0; - } - if (width < 0 || width > 32) { - throw new IOException("Bad read width."); - } - int result = 0; - while (width > 0) { - if (this.available == 0) { - this.unread = this.in.read(); - if (this.unread < 0) { - throw new IOException("Attempt to read past end."); - } - this.available = 8; - } - int take = width; - if (take > this.available) { - take = this.available; - } - result |= ((this.unread >>> (this.available - take)) & - ((1 << take) - 1)) << (width - take); - this.nrBits += take; - this.available -= take; - width -= take; - } - return result; - } -} diff --git a/zip/BitOutputStream.java b/zip/BitOutputStream.java deleted file mode 100644 index da47301cf..000000000 --- a/zip/BitOutputStream.java +++ /dev/null @@ -1,154 +0,0 @@ -package org.json.zip; - -import java.io.IOException; -import java.io.OutputStream; - -/* - Copyright (c) 2013 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. - */ - -/** - * This is a big endian bit writer. It writes its bits to an OutputStream. - * - * @version 2013-05-03 - * - */ -public class BitOutputStream implements BitWriter { - - /** - * The number of bits written. - */ - private long nrBits = 0; - - /** - * The destination of the bits. - */ - private OutputStream out; - - /** - * Holder of bits not yet written. - */ - private int unwritten; - - /** - * The number of unused bits in this.unwritten. - */ - private int vacant = 8; - - /** - * Use an OutputStream to produce a BitWriter. The BitWriter will send its - * bits to the OutputStream as each byte is filled. - * - * @param out - * An Output Stream - */ - public BitOutputStream(OutputStream out) { - this.out = out; - } - - /** - * Returns the number of bits that have been written to this - * bitOutputStream. This may include bits that have not yet been written - * to the underlying outputStream. - */ - public long nrBits() { - return this.nrBits; - } - - /** - * Write a 1 bit. - * - * @throws IOException - */ - public void one() throws IOException { - write(1, 1); - } - - /** - * Pad the rest of the block with zeros and flush. pad(8) flushes the last - * unfinished byte. The underlying OutputStream will be flushed. - * - * @param width - * The size of the block to pad in bits. - * This will typically be 8, 16, 32, 64, 128, 256, etc. - * @throws IOException - */ - public void pad(int width) throws IOException { - int gap = (int)this.nrBits % width; - if (gap < 0) { - gap += width; - } - if (gap != 0) { - int padding = width - gap; - while (padding > 0) { - this.zero(); - padding -= 1; - } - } - this.out.flush(); - } - - /** - * Write some bits. Up to 32 bits can be written at a time. - * - * @param bits - * The bits to be written. - * @param width - * The number of bits to write. (0..32) - * @throws IOException - */ - public void write(int bits, int width) throws IOException { - if (bits == 0 && width == 0) { - return; - } - if (width <= 0 || width > 32) { - throw new IOException("Bad write width."); - } - while (width > 0) { - int actual = width; - if (actual > this.vacant) { - actual = this.vacant; - } - this.unwritten |= ((bits >>> (width - actual)) & - ((1 << actual) - 1)) << (this.vacant - actual); - width -= actual; - nrBits += actual; - this.vacant -= actual; - if (this.vacant == 0) { - this.out.write(this.unwritten); - this.unwritten = 0; - this.vacant = 8; - } - } - } - - /** - * Write a 0 bit. - * - * @throws IOException - */ - public void zero() throws IOException { - write(0, 1); - - } -} diff --git a/zip/BitReader.java b/zip/BitReader.java deleted file mode 100644 index 4fd99dbbf..000000000 --- a/zip/BitReader.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.json.zip; - -import java.io.IOException; - -public interface BitReader { - - /** - * Read one bit. - * - * @return true if it is a 1 bit. - */ - public boolean bit() throws IOException; - - /** - * Returns the number of bits that have been read from this bitreader. - * - * @return The number of bits read so far. - */ - public long nrBits(); - - /** - * Check that the rest of the block has been padded with zeros. - * - * @param width - * The size in bits of the block to pad. This will typically be - * 8, 16, 32, 64, 128, 256, etc. - * @return true if the block was zero padded, or false if the the padding - * contained any one bits. - * @throws IOException - */ - public boolean pad(int width) throws IOException; - - /** - * Read some bits. - * - * @param width - * The number of bits to read. (0..32) - * @throws IOException - * @return the bits - */ - public int read(int width) throws IOException; -} diff --git a/zip/BitWriter.java b/zip/BitWriter.java deleted file mode 100644 index ba8a109c6..000000000 --- a/zip/BitWriter.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.json.zip; - -import java.io.IOException; - -/** - * A bitwriter is a an interface that allows for doing output at the bit level. - * Most IO interfaces only allow for writing at the byte level or higher. - */ -public interface BitWriter { - - /** - * Write a 1 bit. - * - * @throws IOException - */ - public void one() throws IOException; - - /** - * Pad the rest of the block with zeros and flush. - * - * @param width - * The size in bits of the block to pad. This will typically be - * 8, 16, 32, 64, 128, 256, etc. - * @throws IOException - */ - public void pad(int width) throws IOException; - - /** - * Write some bits. Up to 32 bits can be written at a time. - * - * @param bits - * The bits to be written. - * @param width - * The number of bits to write. (0..32) - * @throws IOException - */ - public void write(int bits, int width) throws IOException; - - /** - * Write a 0 bit. - * - * @throws IOException - */ - public void zero() throws IOException; -} diff --git a/zip/Huff.java b/zip/Huff.java deleted file mode 100644 index a7849ae93..000000000 --- a/zip/Huff.java +++ /dev/null @@ -1,404 +0,0 @@ -package org.json.zip; - -import org.json.JSONException; - -/* - Copyright (c) 2013 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. - */ - -/** - * JSONzip is a compression scheme for JSON text. - * @author JSON.org - * @version 2014-05-20 - */ - -/** - * A Huffman encoder/decoder. It operates over a domain of integers, which may - * map to characters or other symbols. Symbols that are used frequently are - * given shorter codes than symbols that are used infrequently. This usually - * produces shorter messages. - * - * Initially, all of the symbols are given the same weight. The weight of a - * symbol is incremented by the tick method. The generate method is used to - * generate the encoding table. The table must be generated before encoding or - * decoding. You may regenerate the table with the latest weights at any time. - * - * After a million ticks, it is assumed that the distribution is well - * understood and that no more regeneration will be required. - */ -public class Huff implements None, PostMortem { - - /** - * The number of symbols known to the encoder. - */ - private final int domain; - - /** - * The number of characters to process before generation is no longer done. - */ - public static final int education = 1000000; - - /** - * An array that maps symbol values to symbols. - */ - private final Symbol[] symbols; - - /** - * The root of the decoding table, and the terminal of the encoding table. - */ - private Symbol table; - - /** - * The number of characters left to learn to adapt the coding table. - */ - private int toLearn; - - /** - * Have any weights changed since the table was last generated? - */ - private boolean upToDate = false; - - /** - * The number of bits in the last symbol. This is used in tracing. - */ - private int width; - - private static class Symbol implements PostMortem { - public Symbol back; - public Symbol next; - public Symbol zero; - public Symbol one; - public final int integer; - public long weight; - - /** - * Make a symbol representing a character or other value. - * - * @param integer - * The symbol's number - */ - public Symbol(int integer) { - this.integer = integer; - this.weight = 0; - this.next = null; - this.back = null; - this.one = null; - this.zero = null; - } - - public boolean postMortem(PostMortem pm) { - boolean result = true; - Symbol that = (Symbol) pm; - - if (this.integer != that.integer || this.weight != that.weight) { - return false; - } - if ((this.back == null) != (that.back == null)) { - return false; - } - Symbol zero = this.zero; - Symbol one = this.one; - if (zero == null) { - if (that.zero != null) { - return false; - } - } else { - result = zero.postMortem(that.zero); - } - if (one == null) { - if (that.one != null) { - return false; - } - } else { - result = one.postMortem(that.one); - } - return result; - } - - } - - /** - * Construct a Huffman encoder/decoder. - * - * @param domain - * The number of values known to the object. - */ - public Huff(int domain) { - this.domain = domain; - this.toLearn = education; - int length = domain * 2 - 1; - this.symbols = new Symbol[length]; - -// Make the leaf symbols. - - for (int i = 0; i < domain; i += 1) { - symbols[i] = new Symbol(i); - } - -// Make the links. - - for (int i = domain; i < length; i += 1) { - symbols[i] = new Symbol(none); - } - } - - /** - * Generate the encoding/decoding table. The table determines the bit - * sequences used by the read and write methods. - */ - public void generate() { - if (!this.upToDate) { - -// Phase One: Sort the symbols by weight into a linked list. - - Symbol head = this.symbols[0]; - Symbol next; - Symbol previous = head; - Symbol symbol; - - this.table = null; - head.next = null; - for (int i = 1; i < this.domain; i += 1) { - symbol = symbols[i]; - -// If this symbol weights less than the head, then it becomes the new head. - - if (symbol.weight < head.weight) { - symbol.next = head; - head = symbol; - } else { - -// We will start the search from the previous symbol instead of the head unless -// the current symbol weights less than the previous symbol. - - if (symbol.weight < previous.weight) { - previous = head; - } - -// Find a connected pair (previous and next) where the symbol weighs the same -// or more than previous but less than the next. Link the symbol between them. - - while (true) { - next = previous.next; - if (next == null || symbol.weight < next.weight) { - break; - } - previous = next; - } - symbol.next = next; - previous.next = symbol; - previous = symbol; - } - } - -// Phase Two: Make new symbols from the two lightest symbols until only one -// symbol remains. The final symbol becomes the root of the table binary tree. - - int avail = this.domain; - Symbol first; - Symbol second; - previous = head; - while (true) { - first = head; - second = first.next; - head = second.next; - symbol = this.symbols[avail]; - avail += 1; - symbol.weight = first.weight + second.weight; - symbol.zero = first; - symbol.one = second; - symbol.back = null; - first.back = symbol; - second.back = symbol; - if (head == null) { - break; - } - -// Insert the new symbol back into the sorted list. - - if (symbol.weight < head.weight) { - symbol.next = head; - head = symbol; - previous = head; - } else { - while (true) { - next = previous.next; - if (next == null || symbol.weight < next.weight) { - break; - } - previous = next; - } - symbol.next = next; - previous.next = symbol; - previous = symbol; - } - - } - -// The last remaining symbol is the root of the table. - - this.table = symbol; - this.upToDate = true; - } - } - - private boolean postMortem(int integer) { - int[] bits = new int[this.domain]; - Symbol symbol = this.symbols[integer]; - if (symbol.integer != integer) { - return false; - } - int i = 0; - while (true) { - Symbol back = symbol.back; - if (back == null) { - break; - } - if (back.zero == symbol) { - bits[i] = 0; - } else if (back.one == symbol) { - bits[i] = 1; - } else { - return false; - } - i += 1; - symbol = back; - } - if (symbol != this.table) { - return false; - } - this.width = 0; - symbol = this.table; - while (symbol.integer == none) { - i -= 1; - symbol = bits[i] != 0 ? symbol.one : symbol.zero; - } - return symbol.integer == integer && i == 0; - } - - /** - * Compare two Huffman tables. - */ - public boolean postMortem(PostMortem pm) { - -// Go through every integer in the domain, generating its bit sequence, and -// then prove that that bit sequence produces the same integer. - - for (int integer = 0; integer < this.domain; integer += 1) { - if (!postMortem(integer)) { - JSONzip.log("\nBad huff "); - JSONzip.logchar(integer, integer); - return false; - } - } - return this.table.postMortem(((Huff) pm).table); - } - - /** - * Read bits until a symbol can be identified. The weight of the read - * symbol will be incremented. - * - * @param bitreader - * The source of bits. - * @return The integer value of the symbol. - * @throws JSONException - */ - public int read(BitReader bitreader) throws JSONException { - try { - this.width = 0; - Symbol symbol = this.table; - while (symbol.integer == none) { - this.width += 1; - symbol = bitreader.bit() ? symbol.one : symbol.zero; - } - tick(symbol.integer); - if (JSONzip.probe) { - JSONzip.logchar(symbol.integer, this.width); - } - return symbol.integer; - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * Increase the weight associated with a value by 1. - * - * @param value - * The number of the symbol to tick - */ - public void tick(int value) { - if (this.toLearn > 0) { - this.toLearn -= 1; - this.symbols[value].weight += 1; - this.upToDate = false; - } - } - - /** - * Recur from a symbol back, emitting bits. We recur before emitting to - * make the bits come out in the right order. - * - * @param symbol - * The symbol to write. - * @param bitwriter - * The bitwriter to write it to. - * @throws JSONException - */ - private void write(Symbol symbol, BitWriter bitwriter) - throws JSONException { - try { - Symbol back = symbol.back; - if (back != null) { - this.width += 1; - write(back, bitwriter); - if (back.zero == symbol) { - bitwriter.zero(); - } else { - bitwriter.one(); - } - } - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * Write the bits corresponding to a symbol. The weight of the symbol will - * be incremented. - * - * @param value - * The number of the symbol to write - * @param bitwriter - * The destination of the bits. - * @throws JSONException - */ - public void write(int value, BitWriter bitwriter) throws JSONException { - this.width = 0; - write(this.symbols[value], bitwriter); - tick(value); - if (JSONzip.probe) { - JSONzip.logchar(value, this.width); - } - } -} diff --git a/zip/JSONzip.java b/zip/JSONzip.java deleted file mode 100644 index d8e3ac652..000000000 --- a/zip/JSONzip.java +++ /dev/null @@ -1,255 +0,0 @@ -package org.json.zip; - -/* - Copyright (c) 2013 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. - */ - -/** - * JSONzip is a binary-encoded JSON dialect. It is designed to compress the - * messages in a session in bandwidth constrained applications, such as mobile. - * - * JSONzip is adaptive, so with each message seen, it should improve its - * compression. It minimizes JSON's overhead, reducing punctuation - * to a small number of bits. It uses Huffman encoding to reduce the average - * size of characters. It uses caches (or Keeps) to keep recently seen strings - * and values, so repetitive content (such as object keys) can be - * substantially reduced. It uses a character encoding called Kim (Keep it - * minimal) that is smaller than UTF-8 for most East European, African, and - * Asian scripts. - * - * JSONzip tends to reduce most content by about half. If there is a lot of - * recurring information, the reduction can be much more dramatic. - * - * FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT YET BEEN TESTED - * ADEQUATELY FOR PRODUCTION USE. - * - * @author JSON.org - * @version 2014-05-20 - */ -public abstract class JSONzip implements None, PostMortem { - /** - * The characters in JSON numbers can be reduced to 4 bits each. - */ - public static final byte[] bcd = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '+', 'E' - }; - - /** - * The end of string code. - */ - public static final int end = 256; - - /** - * The end of number code. - */ - public static final int endOfNumber = bcd.length; - - /** - * The first positive integer that cannot be encoded in 4 bits. - */ - public static final long int4 = 16; - - /** - * The first positive integer that cannot be encoded in 7 bits. - */ - public static final long int7 = 144; - - /** - * The first positive integer that cannot be encoded in 14 bits. - */ - public static final long int14 = 16528; - - /** - * The package supports tracing for debugging. - */ - public static final boolean probe = false; - - /** - * The value code for an empty object. - */ - public static final int zipEmptyObject = 0; - - /** - * The value code for an empty array. - */ - public static final int zipEmptyArray = 1; - - /** - * The value code for true. - */ - public static final int zipTrue = 2; - - /** - * The value code for false. - */ - public static final int zipFalse = 3; - - /** - * The value code for null. - */ - public static final int zipNull = 4; - - /** - * The value code for a non-empty object. - */ - public static final int zipObject = 5; - - /** - * The value code for an array with a string as its first element. - */ - public static final int zipArrayString = 6; - - /** - * The value code for an array with a non-string value as its first element. - */ - public static final int zipArrayValue = 7; - - /** - * A Huffman encoder for names. - */ - protected final Huff namehuff; - - /** - * A Huffman encoder for names extended bytes. - */ - protected final Huff namehuffext; - - /** - * A place to keep the names (keys). - */ - protected final Keep namekeep; - - /** - * A Huffman encoder for string values. - */ - protected final Huff stringhuff; - - /** - * A Huffman encoder for string values extended bytes. - */ - protected final Huff stringhuffext; - - /** - * A place to keep the strings. - */ - protected final Keep stringkeep; - - /** - * A place to keep the values. - */ - protected final Keep valuekeep; - - /** - * Initialize the data structures. - */ - protected JSONzip() { - this.namehuff = new Huff(end + 1); - this.namehuffext = new Huff(end + 1); - this.namekeep = new Keep(9); - this.stringhuff = new Huff(end + 1); - this.stringhuffext = new Huff(end + 1); - this.stringkeep = new Keep(11); - this.valuekeep = new Keep(10); - } - - /** - * Generate the Huffman tables. - */ - protected void generate() { - this.namehuff.generate(); - this.namehuffext.generate(); - this.stringhuff.generate(); - this.stringhuffext.generate(); - } - - /** - * Write an end-of-line to the console. - */ - static void log() { - log("\n"); - } - - /** - * Write an integer to the console. - * - * @param integer The integer to write to the log. - */ - static void log(int integer) { - log(integer + " "); - } - - /** - * Write two integers, separated by ':' to the console. - * The second integer is suppressed if it is 1. - * - * @param integer The integer to write to the log. - * @param width The width of the integer in bits. - */ - static void log(int integer, int width) { - if (width == 1) { - log(integer); - } else { - log(integer + ":" + width + " "); - } - } - - /** - * Write a string to the console. - * - * @param string The string to be written to the log. - */ - static void log(String string) { - System.out.print(string); - } - - /** - * Write a character or its code to the console. - * - * @param integer The charcode to be written to the log. - * @param width The width of the charcode in bits. - */ - static void logchar(int integer, int width) { - if (integer > ' ' && integer <= '}') { - log("'" + (char) integer + "':" + width + " "); - } else { - log(integer, width); - } - } - - /** - * This method is used for testing the implementation of JSONzip. It is not - * suitable for any other purpose. It is used to compare a Compressor and a - * Decompressor, verifying that the data structures that were built during - * zipping and unzipping were the same. - * - * @return true if the structures match. - */ - public boolean postMortem(PostMortem pm) { - JSONzip that = (JSONzip) pm; - return this.namehuff.postMortem(that.namehuff) - && this.namekeep.postMortem(that.namekeep) - && this.stringkeep.postMortem(that.stringkeep) - && this.stringhuff.postMortem(that.stringhuff) - && this.valuekeep.postMortem(that.valuekeep); - } -} diff --git a/zip/Keep.java b/zip/Keep.java deleted file mode 100644 index bc647b6a0..000000000 --- a/zip/Keep.java +++ /dev/null @@ -1,191 +0,0 @@ -package org.json.zip; - -import java.util.HashMap; - -import org.json.Kim; - -/* - Copyright (c) 2013 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 keep is a data structure that associates strings (or substrings) with - * numbers. This allows the sending of small integers instead of strings. - * - * @author JSON.org - * @version 2013-05-03 - */ -class Keep implements None, PostMortem { - private int capacity; - protected int length; - private Object[] list; - private HashMap map; - private int power; - private long[] ticks; - - public Keep(int bits) { - this.capacity = 1 << bits; - this.length = 0; - this.power = 0; - this.ticks = new long[this.capacity]; - this.list = new Object[this.capacity]; - this.map = new HashMap(this.capacity); - } - - /** - * When an item ages, its use count is reduced by at least half. - * - * @param ticks - * The current use count of an item. - * @return The new use count for that item. - */ - public static long age(long ticks) { - return ticks >= 32 ? 16 : ticks / 2; - } - - /** - * Return the number of bits required to contain an integer based on the - * current length of the keep. As the keep fills up, the number of bits - * required to identify one of its items goes up. - */ - public int bitsize() { - while (1 << this.power < this.length) { - this.power += 1; - } - return this.power; - } - - /** - * Increase the usage count on an integer value. - */ - public void tick(int integer) { - this.ticks[integer] += 1; - } - - /** - * Compact the keep. A keep may contain at most this.capacity elements. - * The keep contents can be reduced by deleting all elements with low use - * counts, and by reducing the use counts of the survivors. - */ - private void compact() { - int from = 0; - int to = 0; - while (from < this.capacity) { - Object key = this.list[from]; - long usage = age(this.ticks[from]); - if (usage > 0) { - this.ticks[to] = usage; - this.list[to] = key; - this.map.put(key, to); - to += 1; - } else { - this.map.remove(key); - } - from += 1; - } - if (to < this.capacity) { - this.length = to; - } else { - this.map.clear(); - this.length = 0; - } - this.power = 0; - } - - /** - * Find the integer value associated with this key, or nothing if this key - * is not in the keep. - * - * @param key - * An object. - * @return An integer - */ - public int find(Object key) { - Object o = this.map.get(key); - return o instanceof Integer ? ((Integer) o).intValue() : none; - } - - public boolean postMortem(PostMortem pm) { - Keep that = (Keep) pm; - if (this.length != that.length) { - JSONzip.log(this.length + " <> " + that.length); - return false; - } - for (int i = 0; i < this.length; i += 1) { - boolean b; - if (this.list[i] instanceof Kim) { - b = this.list[i].equals(that.list[i]); - } else { - Object o = this.list[i]; - Object q = that.list[i]; - if (o instanceof Number) { - o = o.toString(); - } - if (q instanceof Number) { - q = q.toString(); - } - b = o.equals(q); - } - if (!b) { - JSONzip.log("\n[" + i + "]\n " + this.list[i] + "\n " - + that.list[i] + "\n " + this.ticks[i] + "\n " - + that.ticks[i]); - return false; - } - } - return true; - } - - /** - * Register a value in the keep. Compact the keep if it is full. The next - * time this value is encountered, its integer can be sent instead. - * @param value A value. - */ - public void register(Object value) { - if (JSONzip.probe) { - int integer = find(value); - if (integer >= 0) { - JSONzip.log("\nDuplicate key " + value); - } - } - if (this.length >= this.capacity) { - compact(); - } - this.list[this.length] = value; - this.map.put(value, this.length); - this.ticks[this.length] = 1; - if (JSONzip.probe) { - JSONzip.log("<" + this.length + " " + value + "> "); - } - this.length += 1; - } - - /** - * Return the value associated with the integer. - * @param integer The number of an item in the keep. - * @return The value. - */ - public Object value(int integer) { - return this.list[integer]; - } -} diff --git a/zip/None.java b/zip/None.java deleted file mode 100644 index 818e68d8f..000000000 --- a/zip/None.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.json.zip; - -/** - * None is an interface that makes the constant none (short for - * negative one or long for -1) available to any class that implements it. - * The none value is used to stand for an integer that is not an integer, - * such as the negative result of a search. - */ -public interface None { - /** - * Negative One. - */ - public static final int none = -1; - -} diff --git a/zip/PostMortem.java b/zip/PostMortem.java deleted file mode 100644 index 0f220ee48..000000000 --- a/zip/PostMortem.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.json.zip; - -/* - Copyright (c) 2013 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. - */ - -/** - * The PostMortem interface allows for testing the internal state of JSONzip - * processors. Testing that JSONzip can compress an object and reproduce a - * corresponding object is not sufficient. Complete testing requires that the - * same internal data structures were constructed on both ends. If those - * structures are not exactly equivalent, then it is likely that the - * implementations are not correct, even if conventional tests are passed. - * - * PostMortem allows for testing of deep structures without breaking - * encapsulation. - */ -public interface PostMortem { - /** - * Determine if two objects are equivalent. - * - * @param pm - * Another object of the same type. - * @return true if they match. - */ - public boolean postMortem(PostMortem pm); -} diff --git a/zip/README b/zip/README deleted file mode 100644 index 93e6470b7..000000000 --- a/zip/README +++ /dev/null @@ -1,2 +0,0 @@ -FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY FOR -PRODUCTION USE. diff --git a/zip/Unzipper.java b/zip/Unzipper.java deleted file mode 100644 index a7c308eda..000000000 --- a/zip/Unzipper.java +++ /dev/null @@ -1,310 +0,0 @@ -package org.json.zip; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.Kim; - -/* - Copyright (c) 2012 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. - */ - -/** - * JSONzip is a binary compression scheme for JSON text. - * - * @author JSON.org - * @version 2014-05-03 - */ - -public class Unzipper extends JSONzip { - - /** - * A decoder reads bits from a BitReader. - */ - BitReader bitreader; - - /** - * Create a new unzipper. It may be used for an entire session or - * subsession. - * - * @param bitreader - * The bitreader that this decoder will read from. - */ - public Unzipper(BitReader bitreader) { - super(); - this.bitreader = bitreader; - } - - /** - * Read one bit. - * - * @return true if 1, false if 0. - * @throws JSONException - */ - private boolean bit() throws JSONException { - boolean value; - try { - value = this.bitreader.bit(); - if (probe) { - log(value ? 1 : 0); - } - return value; - } catch (Throwable e) { - throw new JSONException(e); - } - - } - - /** - * Read enough bits to obtain an integer from the keep, and increase that - * integer's weight. - * - * @param keep The keep providing the context. - * @param bitreader The bitreader that is the source of bits. - * @return The value associated with the number. - * @throws JSONException - */ - private Object getAndTick(Keep keep, BitReader bitreader) - throws JSONException { - try { - int width = keep.bitsize(); - int integer = bitreader.read(width); - Object value = keep.value(integer); - if (JSONzip.probe) { - JSONzip.log("\"" + value + "\""); - JSONzip.log(integer, width); - } - if (integer >= keep.length) { - throw new JSONException("Deep error."); - } - keep.tick(integer); - return value; - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * The pad method skips the bits that padded a stream to fit some - * allocation. pad(8) will skip over the remainder of a byte. - * - * @param width The width of the pad field in bits. - * @return true if all of the padding bits were zero. - * @throws JSONException - */ - public boolean pad(int width) throws JSONException { - try { - return this.bitreader.pad(width); - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * Read an integer, specifying its width in bits. - * - * @param width - * 0 to 32. - * @return An unsigned integer. - * @throws JSONException - */ - private int read(int width) throws JSONException { - try { - int value = this.bitreader.read(width); - if (probe) { - log(value, width); - } - return value; - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * Read Huffman encoded characters into a keep. - * @param huff A Huffman decoder. - * @param ext A Huffman decoder for the extended bytes. - * @param keep The keep that will receive the kim. - * @return The string that was read. - * @throws JSONException - */ - private String read(Huff huff, Huff ext, Keep keep) throws JSONException { - Kim kim; - int at = 0; - int allocation = 256; - byte[] bytes = new byte[allocation]; - if (bit()) { - return getAndTick(keep, this.bitreader).toString(); - } - while (true) { - if (at >= allocation) { - allocation *= 2; - bytes = java.util.Arrays.copyOf(bytes, allocation); - } - int c = huff.read(this.bitreader); - if (c == end) { - break; - } - while ((c & 128) == 128) { - bytes[at] = (byte) c; - at += 1; - c = ext.read(this.bitreader); - } - bytes[at] = (byte) c; - at += 1; - } - if (at == 0) { - return ""; - } - kim = new Kim(bytes, at); - keep.register(kim); - return kim.toString(); - } - - /** - * Read a JSONArray. - * - * @param stringy - * true if the first element is a string. - * @throws JSONException - */ - private JSONArray readArray(boolean stringy) throws JSONException { - JSONArray jsonarray = new JSONArray(); - jsonarray.put(stringy - ? read(this.stringhuff, this.stringhuffext, this.stringkeep) - : readValue()); - while (true) { - if (probe) { - log(); - } - if (!bit()) { - if (!bit()) { - return jsonarray; - } - jsonarray.put(stringy - ? readValue() - : read(this.stringhuff, this.stringhuffext, - this.stringkeep)); - } else { - jsonarray.put(stringy - ? read(this.stringhuff, this.stringhuffext, - this.stringkeep) - : readValue()); - } - } - } - - /** - * Read a JSON value. The type of value is determined by the next 3 bits. - * - * @return The read value. - * @throws JSONException - */ - private Object readJSON() throws JSONException { - switch (read(3)) { - case zipObject: - return readObject(); - case zipArrayString: - return readArray(true); - case zipArrayValue: - return readArray(false); - case zipEmptyObject: - return new JSONObject(); - case zipEmptyArray: - return new JSONArray(); - case zipTrue: - return Boolean.TRUE; - case zipFalse: - return Boolean.FALSE; - default: - return JSONObject.NULL; - } - } - - private JSONObject readObject() throws JSONException { - JSONObject jsonobject = new JSONObject(); - while (true) { - if (probe) { - log(); - } - String name = read(this.namehuff, this.namehuffext, this.namekeep); - if (jsonobject.opt(name) != null) { - throw new JSONException("Duplicate key."); - } - jsonobject.put(name, !bit() - ? read(this.stringhuff, this.stringhuffext, this.stringkeep) - : readValue()); - if (!bit()) { - return jsonobject; - } - } - } - - private Object readValue() throws JSONException { - switch (read(2)) { - case 0: - int nr_bits = !bit() ? 4 : !bit() ? 7 : 14; - int integer = read(nr_bits); - switch (nr_bits) { - case 7: - integer += int4; - break; - case 14: - integer += int7; - break; - } - return integer; - case 1: - byte[] bytes = new byte[256]; - int length = 0; - while (true) { - int c = read(4); - if (c == endOfNumber) { - break; - } - bytes[length] = bcd[c]; - length += 1; - } - Object value; - try { - value = JSONObject.stringToValue(new String(bytes, 0, length, - "US-ASCII")); - } catch (java.io.UnsupportedEncodingException e) { - throw new JSONException(e); - } - this.valuekeep.register(value); - return value; - case 2: - return getAndTick(this.valuekeep, this.bitreader); - case 3: - return readJSON(); - default: - throw new JSONException("Impossible."); - } - } - - public Object decode() throws JSONException { - generate(); - return readJSON(); - } -} diff --git a/zip/Zipper.java b/zip/Zipper.java deleted file mode 100644 index 48b4f1acb..000000000 --- a/zip/Zipper.java +++ /dev/null @@ -1,481 +0,0 @@ -package org.json.zip; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.Kim; - -/* - Copyright (c) 2013 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. - */ - -/** - * JSONzip is a binary compression scheme for JSON text. - * - * @author JSON.org - * @version 2014-05-03 - */ - -/** - * An encoder implements the compression behavior of JSONzip. It provides a - * zip method that takes a JSONObject or JSONArray and delivers a stream of - * bits to a BitWriter. - * - * FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY - * FOR PRODUCTION USE. - */ -public class Zipper extends JSONzip { - - /** - * An encoder outputs to a BitWriter. - */ - final BitWriter bitwriter; - - /** - * Create a new encoder. It may be used for an entire session or - * subsession. - * - * @param bitwriter - * The BitWriter this encoder will output to. - * Don't forget to flush. - */ - public Zipper(BitWriter bitwriter) { - super(); - this.bitwriter = bitwriter; - } - - /** - * Return a 4 bit code for a character in a JSON number. The digits '0' to - * '9' get the codes 0 to 9. '.' is 10, '-' is 11, '+' is 12, and 'E' or - * 'e' is 13. - * - * @param digit - * An ASCII character from a JSON number. - * @return The number code. - */ - private static int bcd(char digit) { - if (digit >= '0' && digit <= '9') { - return digit - '0'; - } - switch (digit) { - case '.': - return 10; - case '-': - return 11; - case '+': - return 12; - default: - return 13; - } - } - - /** - * Finish the final byte and flush the bitwriter. This does the same thing - * as pad(8). - * - * @throws JSONException - */ - public void flush() throws JSONException { - pad(8); - } - - /** - * Output a one bit. - * - * @throws JSONException - */ - private void one() throws JSONException { - write(1, 1); - } - - /** - * Pad the output to fill an allotment of bits. - * - * @param width - * The size of the bit allotment. A value of 8 will complete and - * flush the current byte. If you don't pad, then some of the - * last bits might not be sent to the Output Stream. - * @throws JSONException - */ - public void pad(int width) throws JSONException { - try { - this.bitwriter.pad(width); - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * Write a number, using the number of bits necessary to hold the number. - * - * @param integer - * The value to be encoded. - * @param width - * The number of bits to encode the value, between 0 and 32. - * @throws JSONException - */ - private void write(int integer, int width) throws JSONException { - try { - this.bitwriter.write(integer, width); - if (probe) { - log(integer, width); - } - } catch (Throwable e) { - throw new JSONException(e); - } - } - - /** - * Write an integer with Huffman encoding. The bit pattern that is written - * will be determined by the Huffman encoder. - * - * @param integer - * The value to be written. - * @param huff - * The Huffman encoder. - * @throws JSONException - */ - private void write(int integer, Huff huff) throws JSONException { - huff.write(integer, this.bitwriter); - } - - /** - * Write each of the bytes in a kim with Huffman encoding. - * - * @param kim - * A kim containing the bytes to be written. - * @param huff - * The Huffman encoder. - * @param ext - * The Huffman encoder for the extended bytes. - * @throws JSONException - */ - private void write(Kim kim, Huff huff, Huff ext) throws JSONException { - for (int at = 0; at < kim.length; at += 1) { - int c = kim.get(at); - write(c, huff); - while ((c & 128) == 128) { - at += 1; - c = kim.get(at); - write(c, ext); - } - } - } - - /** - * Write an integer, using the number of bits necessary to hold the number - * as determined by its keep, and increment its usage count in the keep. - * - * @param integer - * The value to be encoded. - * @param keep - * The Keep that the integer is one of. - * @throws JSONException - */ - private void write(int integer, Keep keep) throws JSONException { - int width = keep.bitsize(); - keep.tick(integer); - if (probe) { - log("\"" + keep.value(integer) + "\""); - } - write(integer, width); - } - - /** - * Write a JSON Array. - * - * @param jsonarray The JSONArray to write. - * @throws JSONException If the write fails. - */ - private void write(JSONArray jsonarray) throws JSONException { - -// JSONzip has three encodings for arrays: -// The array is empty (zipEmptyArray). -// First value in the array is a string (zipArrayString). -// First value in the array is not a string (zipArrayValue). - - boolean stringy = false; - int length = jsonarray.length(); - if (length == 0) { - write(zipEmptyArray, 3); - } else { - Object value = jsonarray.get(0); - if (value == null) { - value = JSONObject.NULL; - } - if (value instanceof String) { - stringy = true; - write(zipArrayString, 3); - writeString((String) value); - } else { - write(zipArrayValue, 3); - writeValue(value); - } - for (int i = 1; i < length; i += 1) { - if (probe) { - log(); - } - value = jsonarray.get(i); - if (value == null) { - value = JSONObject.NULL; - } - if (value instanceof String != stringy) { - zero(); - } - one(); - if (value instanceof String) { - writeString((String) value); - } else { - writeValue(value); - } - } - zero(); - zero(); - - } - } - - /** - * Write a JSON value. - * - * @param value - * One of these types: JSONObject, JSONArray (or Map or - * Collection or array), Number (or Integer or Long or Double), - * or String, or Boolean, or JSONObject.NULL, or null. - * @throws JSONException - */ - private void writeJSON(Object value) throws JSONException { - if (JSONObject.NULL.equals(value)) { - write(zipNull, 3); - } else if (Boolean.FALSE.equals(value)) { - write(zipFalse, 3); - } else if (Boolean.TRUE.equals(value)) { - write(zipTrue, 3); - } else { - if (value instanceof Map) { - value = new JSONObject((Map) value); - } else if (value instanceof Collection) { - value = new JSONArray((Collection) value); - } else if (value.getClass().isArray()) { - value = new JSONArray(value); - } - if (value instanceof JSONObject) { - write((JSONObject) value); - } else if (value instanceof JSONArray) { - write((JSONArray) value); - } else { - throw new JSONException("Unrecognized object"); - } - } - } - - /** - * Write the name of an object property. Names have their own Keep and - * Huffman encoder because they are expected to be a more restricted set. - * - * @param name The name string. - * @throws JSONException - */ - private void writeName(String name) throws JSONException { - -// If this name has already been registered, then emit its integer and -// increment its usage count. - - Kim kim = new Kim(name); - int integer = this.namekeep.find(kim); - if (integer != none) { - one(); - write(integer, this.namekeep); - } else { - -// Otherwise, emit the string with Huffman encoding, and register it. - - zero(); - write(kim, this.namehuff, this.namehuffext); - write(end, namehuff); - this.namekeep.register(kim); - } - } - - /** - * Write a JSON object. - * - * @param jsonobject The JSONObject to be written. - * @throws JSONException - */ - private void write(JSONObject jsonobject) throws JSONException { - -// JSONzip has two encodings for objects: Empty Objects (zipEmptyObject) and -// non-empty objects (zipObject). - - boolean first = true; - Iterator keys = jsonobject.keys(); - while (keys.hasNext()) { - if (probe) { - log(); - } - Object key = keys.next(); - if (key instanceof String) { - if (first) { - first = false; - write(zipObject, 3); - } else { - one(); - } - writeName((String) key); - Object value = jsonobject.get((String) key); - if (value instanceof String) { - zero(); - writeString((String) value); - } else { - one(); - writeValue(value); - } - } - } - if (first) { - write(zipEmptyObject, 3); - } else { - zero(); - } - } - - /** - * Write a string. - * - * @param string The string to write. - * @throws JSONException - */ - private void writeString(String string) throws JSONException { - -// Special case for empty strings. - - if (string.length() == 0) { - zero(); - write(end, this.stringhuff); - } else { - Kim kim = new Kim(string); - -// Look for the string in the strings keep. If it is found, emit its -// integer and count that as a use. - - int integer = this.stringkeep.find(kim); - if (integer != none) { - one(); - write(integer, this.stringkeep); - } else { - -// But if it is not found, emit the string's characters. Register the string -// so that a later lookup can succeed. - - zero(); - write(kim, this.stringhuff, this.stringhuffext); - write(end, this.stringhuff); - this.stringkeep.register(kim); - } - } - } - - /** - * Write a value. - * - * @param value - * One of these types: Boolean, Number, etc. - * @throws JSONException - */ - private void writeValue(Object value) throws JSONException { - if (value instanceof Number) { - String string = JSONObject.numberToString((Number) value); - int integer = this.valuekeep.find(string); - if (integer != none) { - write(2, 2); - write(integer, this.valuekeep); - return; - } - if (value instanceof Integer || value instanceof Long) { - long longer = ((Number) value).longValue(); - if (longer >= 0 && longer < int14) { - write(0, 2); - if (longer < int4) { - zero(); - write((int) longer, 4); - return; - } - one(); - if (longer < int7) { - zero(); - write((int)(longer - int4), 7); - return; - } - one(); - write((int)(longer - int7), 14); - return; - } - } - write(1, 2); - for (int i = 0; i < string.length(); i += 1) { - write(bcd(string.charAt(i)), 4); - } - write(endOfNumber, 4); - this.valuekeep.register(string); - } else { - write(3, 2); - writeJSON(value); - } - } - - /** - * Output a zero bit. - * - * @throws JSONException - */ - private void zero() throws JSONException { - write(0, 1); - } - - /** - * Encode a JSONObject. - * - * @param jsonobject The JSONObject. - * @throws JSONException - */ - public void encode(JSONObject jsonobject) throws JSONException { - generate(); - writeJSON(jsonobject); - } - - /** - * Encode a JSONArray. - * - * @param jsonarray The JSONArray. - * @throws JSONException - */ - public void encode(JSONArray jsonarray) throws JSONException { - generate(); - writeJSON(jsonarray); - } -} From d6ba31819c48db39be068c8b3b1fa985df09ea65 Mon Sep 17 00:00:00 2001 From: stleary Date: Sun, 19 Apr 2015 17:53:40 -0500 Subject: [PATCH 19/31] fix toString(JSONArray) to emit object.toString() values --- JSONML.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/JSONML.java b/JSONML.java index 20e0be5fa..42027cb00 100755 --- a/JSONML.java +++ b/JSONML.java @@ -373,6 +373,8 @@ public static String toString(JSONArray ja) throws JSONException { sb.append(toString((JSONObject)object)); } else if (object instanceof JSONArray) { sb.append(toString((JSONArray)object)); + } else { + sb.append(object.toString()); } } } while (i < length); From 37f099ed4b19cd155cbedb9e9292bc3fc5794e41 Mon Sep 17 00:00:00 2001 From: stleary Date: Fri, 1 May 2015 12:52:23 -0500 Subject: [PATCH 20/31] s/covert/convert/ --- CDL.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CDL.java b/CDL.java index 995b1d478..8520e862d 100755 --- a/CDL.java +++ b/CDL.java @@ -26,7 +26,7 @@ of this software and associated documentation files (the "Software"), to deal /** * This provides static methods to convert comma delimited text into a - * JSONArray, and to covert a JSONArray into comma delimited text. Comma + * JSONArray, and to convert a JSONArray into comma delimited text. Comma * delimited text is a very popular format for data interchange. It is * understood by most database, spreadsheet, and organizer programs. *

    @@ -41,7 +41,7 @@ of this software and associated documentation files (the "Software"), to deal * The names for the elements in the JSONObjects can be taken from the names * in the first row. * @author JSON.org - * @version 2014-05-03 + * @version 2015-05-01 */ public class CDL { From a851bf095196aab53b7406a55f2f7c0125413a60 Mon Sep 17 00:00:00 2001 From: stleary Date: Tue, 5 May 2015 20:11:28 -0500 Subject: [PATCH 21/31] Replaced tab chars, updated versions --- JSONObject.java | 32 ++++++++++++++++++++++---------- Property.java | 4 ++-- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index d66623110..e28c9cd37 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -91,7 +91,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2014-05-03 + * @version 2015-05-05 */ public class JSONObject { /** @@ -298,7 +298,7 @@ public JSONObject(Object bean) { */ public JSONObject(Object object, String names[]) { this(); - Class c = object.getClass(); + Class c = object.getClass(); for (int i = 0; i < names.length; i += 1) { String name = names[i]; try { @@ -631,7 +631,7 @@ public static String[] getNames(Object object) { if (object == null) { return null; } - Class klass = object.getClass(); + Class klass = object.getClass(); Field[] fields = klass.getFields(); int length = fields.length; if (length == 0) { @@ -981,7 +981,7 @@ public String optString(String key, String defaultValue) { } private void populateMap(Object bean) { - Class klass = bean.getClass(); + Class klass = bean.getClass(); // If klass is a System class then set includeSuperClass to false. @@ -1512,10 +1512,14 @@ public static String valueToString(Object value) throws JSONException { return value.toString(); } if (value instanceof Map) { - return new JSONObject((Map)value).toString(); + @SuppressWarnings("unchecked") + Map map = (Map) value; + return new JSONObject(map).toString(); } if (value instanceof Collection) { - return new JSONArray((Collection) value).toString(); + @SuppressWarnings("unchecked") + Collection coll = (Collection) value; + return new JSONArray(coll).toString(); } if (value.getClass().isArray()) { return new JSONArray(value).toString(); @@ -1551,13 +1555,17 @@ public static Object wrap(Object object) { } if (object instanceof Collection) { - return new JSONArray((Collection) object); + @SuppressWarnings("unchecked") + Collection coll = (Collection) object; + return new JSONArray(coll); } if (object.getClass().isArray()) { return new JSONArray(object); } if (object instanceof Map) { - return new JSONObject((Map) object); + @SuppressWarnings("unchecked") + Map map = (Map) object; + return new JSONObject(map); } Package objectPackage = object.getClass().getPackage(); String objectPackageName = objectPackage != null ? objectPackage @@ -1595,9 +1603,13 @@ static final Writer writeValue(Writer writer, Object value, } else if (value instanceof JSONArray) { ((JSONArray) value).write(writer, indentFactor, indent); } else if (value instanceof Map) { - new JSONObject((Map) value).write(writer, indentFactor, indent); + @SuppressWarnings("unchecked") + Map map = (Map) value; + new JSONObject(map).write(writer, indentFactor, indent); } else if (value instanceof Collection) { - new JSONArray((Collection) value).write(writer, indentFactor, + @SuppressWarnings("unchecked") + Collection coll = (Collection) value; + new JSONArray(coll).write(writer, indentFactor, indent); } else if (value.getClass().isArray()) { new JSONArray(value).write(writer, indentFactor, indent); diff --git a/Property.java b/Property.java index 8122241e9..73ddb1287 100644 --- a/Property.java +++ b/Property.java @@ -31,7 +31,7 @@ of this software and associated documentation files (the "Software"), to deal /** * Converts a Property file data into JSONObject and back. * @author JSON.org - * @version 2014-05-03 + * @version 2015-05-05 */ public class Property { /** @@ -43,7 +43,7 @@ public class Property { public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { JSONObject jo = new JSONObject(); if (properties != null && !properties.isEmpty()) { - Enumeration enumProperties = properties.propertyNames(); + Enumeration enumProperties = properties.propertyNames(); while(enumProperties.hasMoreElements()) { String name = (String)enumProperties.nextElement(); jo.put(name, properties.getProperty(name)); From d2cd1a8df5cd6392650f455a5df844ec55c3e1dc Mon Sep 17 00:00:00 2001 From: stleary Date: Thu, 4 Jun 2015 22:26:16 -0500 Subject: [PATCH 22/31] iterable --- JSONArray.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index 3f05548d5..deede035a 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -75,9 +75,9 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2014-05-03 + * @version 2015-06-04 */ -public class JSONArray { +public class JSONArray implements Iterable { /** * The arrayList where the JSONArray's properties are kept. @@ -179,6 +179,11 @@ public JSONArray(Object array) throws JSONException { } } + @Override + public Iterator iterator() { + return myArrayList.iterator(); + } + /** * Get the object value associated with an index. * From 8bc62cc34c071a73c339a878b116bcb1f80d69d2 Mon Sep 17 00:00:00 2001 From: stleary Date: Sat, 20 Jun 2015 13:26:55 -0500 Subject: [PATCH 23/31] support for BigInteger and BigDecimal --- JSONObject.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/JSONObject.java b/JSONObject.java index e28c9cd37..6b8ce6bcd 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -30,6 +30,7 @@ of this software and associated documentation files (the "Software"), to deal import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.math.*; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; @@ -503,6 +504,46 @@ public boolean getBoolean(String key) throws JSONException { + "] is not a Boolean."); } + /** + * Get the BigInteger 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 cannot + * be converted to BigInteger. + */ + public BigInteger getBigInteger(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger."); + } + } + + /** + * Get the BigDecimal 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 + * cannot be converted to BigDecimal. + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal."); + } + } + /** * Get the double value associated with a key. * @@ -688,6 +729,10 @@ public JSONObject increment(String key) throws JSONException { Object value = this.opt(key); if (value == null) { this.put(key, 1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger)value).add(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal)value).add(BigDecimal.ONE)); } else if (value instanceof Integer) { this.put(key, (Integer) value + 1); } else if (value instanceof Long) { @@ -843,6 +888,44 @@ public double optDouble(String key) { return this.optDouble(key, Double.NaN); } + /** + * Get an optional BigInteger 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 BigInteger optBigInteger(String key, BigInteger defaultValue) { + try { + return this.getBigInteger(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal 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 BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + try { + return this.getBigDecimal(key); + } catch (Exception e) { + return defaultValue; + } + } + /** * 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 From 5d6bf7d132e3ac0a9096dd60b5408f826f59b518 Mon Sep 17 00:00:00 2001 From: stleary Date: Sat, 20 Jun 2015 15:18:22 -0500 Subject: [PATCH 24/31] support BigInteger and BigDecimal --- JSONObject.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/JSONObject.java b/JSONObject.java index e28c9cd37..6b8ce6bcd 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -30,6 +30,7 @@ of this software and associated documentation files (the "Software"), to deal import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.math.*; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; @@ -503,6 +504,46 @@ public boolean getBoolean(String key) throws JSONException { + "] is not a Boolean."); } + /** + * Get the BigInteger 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 cannot + * be converted to BigInteger. + */ + public BigInteger getBigInteger(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger."); + } + } + + /** + * Get the BigDecimal 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 + * cannot be converted to BigDecimal. + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal."); + } + } + /** * Get the double value associated with a key. * @@ -688,6 +729,10 @@ public JSONObject increment(String key) throws JSONException { Object value = this.opt(key); if (value == null) { this.put(key, 1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger)value).add(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal)value).add(BigDecimal.ONE)); } else if (value instanceof Integer) { this.put(key, (Integer) value + 1); } else if (value instanceof Long) { @@ -843,6 +888,44 @@ public double optDouble(String key) { return this.optDouble(key, Double.NaN); } + /** + * Get an optional BigInteger 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 BigInteger optBigInteger(String key, BigInteger defaultValue) { + try { + return this.getBigInteger(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal 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 BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + try { + return this.getBigDecimal(key); + } catch (Exception e) { + return defaultValue; + } + } + /** * 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 From b39ccc2a67563cd90421d694bb7d0a7e296f9b99 Mon Sep 17 00:00:00 2001 From: stleary Date: Sat, 20 Jun 2015 15:20:56 -0500 Subject: [PATCH 25/31] support BigDecimal and BigInteger --- JSONObject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONObject.java b/JSONObject.java index 6b8ce6bcd..14f58b821 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -92,7 +92,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2015-05-05 + * @version 2015-06-20 */ public class JSONObject { /** From a76d7262d11e34c9adb403d76c98db21e0c83bb6 Mon Sep 17 00:00:00 2001 From: stleary Date: Sat, 20 Jun 2015 16:20:00 -0500 Subject: [PATCH 26/31] support BigInteger and BigDecimal --- JSONObject.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JSONObject.java b/JSONObject.java index 14f58b821..682c51369 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -1633,7 +1633,8 @@ public static Object wrap(Object object) { || object instanceof Short || object instanceof Integer || object instanceof Long || object instanceof Boolean || object instanceof Float || object instanceof Double - || object instanceof String) { + || object instanceof String || object instanceof BigInteger + || object instanceof BigDecimal) { return object; } From 6ab6f063c82e1604f2a5af59d4f4a072165ab55b Mon Sep 17 00:00:00 2001 From: stleary Date: Fri, 3 Jul 2015 20:41:47 -0500 Subject: [PATCH 27/31] latest --- JSONArray.java | 43 ++++++++++++++++++++++++++++++++++++++++++- JSONObject.java | 2 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index deede035a..22d4ab7ff 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -28,6 +28,7 @@ of this software and associated documentation files (the "Software"), to deal import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Array; +import java.math.*; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -75,7 +76,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2015-06-04 + * @version 2015-07-04 */ public class JSONArray implements Iterable { @@ -246,6 +247,46 @@ public double getDouble(int index) throws JSONException { } } + /** + * Get the BigDecimal 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 BigDecimal. + */ + public BigDecimal getBigDecimal (int index) throws JSONException { + Object object = this.get(index); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigDecimal."); + } + } + + /** + * Get the BigInteger 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 BigInteger. + */ + public BigInteger getBigInteger (int index) throws JSONException { + Object object = this.get(index); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigInteger."); + } + } + /** * Get the int value associated with an index. * diff --git a/JSONObject.java b/JSONObject.java index 14f58b821..24223c5a6 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -92,7 +92,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2015-06-20 + * @version 2015-07-04 */ public class JSONObject { /** From 410afaff145ba59c4979c0492e2d244ac6e6dfe4 Mon Sep 17 00:00:00 2001 From: stleary Date: Mon, 6 Jul 2015 22:20:19 -0500 Subject: [PATCH 28/31] latest --- JSONArray.java | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/JSONArray.java b/JSONArray.java index 22d4ab7ff..684ed7a05 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -531,6 +531,44 @@ public int optInt(int index, int defaultValue) { } } + /** + * Get the optional BigInteger 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 BigInteger optBigInteger(int index, BigInteger defaultValue) { + try { + return this.getBigInteger(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional BigDecimal 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 BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { + try { + return this.getBigDecimal(index); + } catch (Exception e) { + return defaultValue; + } + } + /** * Get the optional JSONArray associated with an index. * From 71d9ad2b9909fa5d7fe6fcc85879002628ea9de4 Mon Sep 17 00:00:00 2001 From: stleary Date: Mon, 6 Jul 2015 22:27:10 -0500 Subject: [PATCH 29/31] update version dates --- JSONArray.java | 2 +- JSONObject.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index 684ed7a05..b1334db25 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -76,7 +76,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2015-07-04 + * @version 2015-07-06 */ public class JSONArray implements Iterable { diff --git a/JSONObject.java b/JSONObject.java index ff3f90061..34d5c6c83 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -92,7 +92,7 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2015-07-04 + * @version 2015-07-06 */ public class JSONObject { /** From 96b2e3845930ace5fb652a5e561751eb193ff016 Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Mon, 20 Jul 2015 10:02:21 -0500 Subject: [PATCH 30/31] Update README --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index 2de22ffc4..925141f9e 100755 --- a/README +++ b/README @@ -71,3 +71,5 @@ XML.java: XML provides support for converting between JSON and XML. JSONML.java: JSONML provides support for converting between JSONML and XML. XMLTokener.java: XMLTokener extends JSONTokener for parsing XML text. + +Unit tests are maintained in a separate project. Contributing developers can test JSON-java pull requests with the code in this project: https://github.com/stleary/JSON-Java-unit-test From 18d45f32ca0cd47082769ee2f7632947e4197290 Mon Sep 17 00:00:00 2001 From: Randall Whitman Date: Wed, 5 Jul 2017 17:55:18 -0700 Subject: [PATCH 31/31] DEPRECATED --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1a24f11e..5acef50d0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# JSON-java +# DEPRECATED fork of JSON-java -This repository is a fork of the original [JSON-java](https://github.com/douglascrockford/JSON-java) repository by Douglas Crockford. This repository only adds two files, build.xml and build.properties, which allow for the ant build system to compile this code into a jar file. The jar file produced by this project is used for JSON parsing in the [GIS Tools for Hadoop](http://github.com/Esri/gis-tools-for-hadoop) and [Spatial Framework for Hadoop](http://github.com/Esri/spatial-framework-for-hadoop) projects. +This repository is a fork of the original [JSON-java](https://github.com/douglascrockford/JSON-java) repository by Douglas Crockford. This repository only adds two files, build.xml and build.properties, which allow for the ant build system to compile this code into a jar file. The jar file produced by this project was used for JSON parsing in the [GIS Tools for Hadoop](http://github.com/Esri/gis-tools-for-hadoop) and [Spatial Framework for Hadoop](http://github.com/Esri/spatial-framework-for-hadoop) projects. Other JSON library implementations may be found at: [JSON.org](http://www.json.org).