diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ 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/pom.xml b/pom.xml index c98e74b27..c462e522a 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.json json - 20131018 + 20141006 jar JSON in Java @@ -88,8 +88,8 @@ maven-compiler-plugin 2.3.2 - 1.2 - 1.2 + 1.6 + 1.6 @@ -108,13 +108,16 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.7 + 2.9 attach-javadocs jar + + -Xdoclint:none + diff --git a/src/main/java/org/json/CDL.java b/src/main/java/org/json/CDL.java index 0fc3cf828..995b1d478 100644 --- a/src/main/java/org/json/CDL.java +++ b/src/main/java/org/json/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/src/main/java/org/json/Cookie.java b/src/main/java/org/json/Cookie.java index a2d9c4ed9..1867dbd74 100644 --- a/src/main/java/org/json/Cookie.java +++ b/src/main/java/org/json/Cookie.java @@ -28,7 +28,7 @@ of this software and associated documentation files (the "Software"), to deal * 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 + * @version 2014-05-03 */ public class Cookie { @@ -45,10 +45,10 @@ public class Cookie { * @return The escaped result. */ public static String escape(String string) { - char c; - String s = string.trim(); - StringBuffer sb = new StringBuffer(); - int length = s.length(); + 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 == ';') { @@ -116,7 +116,7 @@ public static JSONObject toJSONObject(String string) throws JSONException { * @throws JSONException */ public static String toString(JSONObject jo) throws JSONException { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append(escape(jo.getString("name"))); sb.append("="); @@ -149,7 +149,7 @@ public static String toString(JSONObject jo) throws JSONException { */ public static String unescape(String string) { int length = string.length(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; ++i) { char c = string.charAt(i); if (c == '+') { diff --git a/src/main/java/org/json/CookieList.java b/src/main/java/org/json/CookieList.java index 1111135f3..b716fd7e3 100644 --- a/src/main/java/org/json/CookieList.java +++ b/src/main/java/org/json/CookieList.java @@ -29,7 +29,7 @@ of this software and associated documentation files (the "Software"), to deal /** * Convert a web browser cookie list string to a JSONObject and back. * @author JSON.org - * @version 2010-12-24 + * @version 2014-05-03 */ public class CookieList { @@ -58,7 +58,6 @@ public static JSONObject toJSONObject(String string) throws JSONException { 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 '='. @@ -69,12 +68,12 @@ public static JSONObject toJSONObject(String string) throws JSONException { * @throws JSONException */ public static String toString(JSONObject jo) throws JSONException { - boolean b = false; - Iterator keys = jo.keys(); - String string; - StringBuffer sb = new StringBuffer(); + boolean b = false; + Iterator keys = jo.keys(); + String string; + StringBuilder sb = new StringBuilder(); while (keys.hasNext()) { - string = keys.next().toString(); + string = keys.next(); if (!jo.isNull(string)) { if (b) { sb.append(';'); diff --git a/src/main/java/org/json/HTTP.java b/src/main/java/org/json/HTTP.java index cc8203d1b..bd1d6fb44 100644 --- a/src/main/java/org/json/HTTP.java +++ b/src/main/java/org/json/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/src/main/java/org/json/HTTPTokener.java b/src/main/java/org/json/HTTPTokener.java index ed41744a0..b2489b68d 100644 --- a/src/main/java/org/json/HTTPTokener.java +++ b/src/main/java/org/json/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/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index 673a91927..3f05548d5 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -75,20 +75,20 @@ of this software and associated documentation files (the "Software"), to deal * * * @author JSON.org - * @version 2013-04-18 + * @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; } @@ -813,9 +813,42 @@ public JSONArray put(int index, Object value) throws JSONException { * was no value. */ public Object remove(int index) { - Object o = this.opt(index); - this.myArrayList.remove(index); - return o; + 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 true; } /** diff --git a/src/main/java/org/json/JSONException.java b/src/main/java/org/json/JSONException.java index 971547e63..6fef51943 100644 --- a/src/main/java/org/json/JSONException.java +++ b/src/main/java/org/json/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/src/main/java/org/json/JSONML.java b/src/main/java/org/json/JSONML.java index 4be686351..20e0be5fa 100644 --- a/src/main/java/org/json/JSONML.java +++ b/src/main/java/org/json/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 2013-06-17 + * @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 */ @@ -1281,6 +1284,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 = 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. @@ -1321,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; } @@ -1469,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(); @@ -1508,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 @@ -1552,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); @@ -1596,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) { @@ -1623,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/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java index 13c84f1f5..32548ed9f 100644 --- a/src/main/java/org/json/JSONTokener.java +++ b/src/main/java/org/json/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/src/main/java/org/json/JSONWriter.java b/src/main/java/org/json/JSONWriter.java index 35b60d900..8c69cafdd 100644 --- a/src/main/java/org/json/JSONWriter.java +++ b/src/main/java/org/json/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/src/main/java/org/json/Kim.java b/src/main/java/org/json/Kim.java index d4770b566..9f7af92d0 100644 --- a/src/main/java/org/json/Kim.java +++ b/src/main/java/org/json/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/src/main/java/org/json/Property.java b/src/main/java/org/json/Property.java index dbbd7ef7e..8122241e9 100644 --- a/src/main/java/org/json/Property.java +++ b/src/main/java/org/json/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/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index cea3abe7b..07090abe3 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/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/src/main/java/org/json/XMLTokener.java b/src/main/java/org/json/XMLTokener.java index be15ebeba..d3197653c 100644 --- a/src/main/java/org/json/XMLTokener.java +++ b/src/main/java/org/json/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/src/main/java/org/json/zip/BitInputStream.java b/src/main/java/org/json/zip/BitInputStream.java index 8b56fffbc..6a99be427 100644 --- a/src/main/java/org/json/zip/BitInputStream.java +++ b/src/main/java/org/json/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 org.json.zip.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/src/main/java/org/json/zip/BitOutputStream.java b/src/main/java/org/json/zip/BitOutputStream.java index 526ad6111..da47301cf 100644 --- a/src/main/java/org/json/zip/BitOutputStream.java +++ b/src/main/java/org/json/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/src/main/java/org/json/zip/BitReader.java b/src/main/java/org/json/zip/BitReader.java index 1987729b8..4fd99dbbf 100644 --- a/src/main/java/org/json/zip/BitReader.java +++ b/src/main/java/org/json/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/src/main/java/org/json/zip/BitWriter.java b/src/main/java/org/json/zip/BitWriter.java index 83eb7e314..ba8a109c6 100644 --- a/src/main/java/org/json/zip/BitWriter.java +++ b/src/main/java/org/json/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/src/main/java/org/json/zip/Huff.java b/src/main/java/org/json/zip/Huff.java index 2e1d1c925..a7849ae93 100644 --- a/src/main/java/org/json/zip/Huff.java +++ b/src/main/java/org/json/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-20 */ /** @@ -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 { @@ -50,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. */ @@ -60,6 +68,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 +113,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 +145,7 @@ public boolean postMortem(PostMortem pm) { */ public Huff(int domain) { this.domain = domain; + this.toLearn = education; int length = domain * 2 - 1; this.symbols = new Symbol[length]; @@ -141,7 +155,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 +165,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 +188,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 +302,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 +342,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 +391,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/src/main/java/org/json/zip/JSONzip.java b/src/main/java/org/json/zip/JSONzip.java index 2128742c2..d8e3ac652 100644 --- a/src/main/java/org/json/zip/JSONzip.java +++ b/src/main/java/org/json/zip/JSONzip.java @@ -1,6 +1,5 @@ package org.json.zip; - /* Copyright (c) 2013 JSON.org @@ -27,8 +26,10 @@ 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 - * improve its compression. It minimizes JSON's overhead, reducing punctuation + * 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 @@ -43,17 +44,9 @@ of this software and associated documentation files (the "Software"), to deal * ADEQUATELY FOR PRODUCTION USE. * * @author JSON.org - * @version 2013-04-18 + * @version 2014-05-20 */ 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. */ @@ -61,21 +54,6 @@ public abstract class JSONzip implements None, PostMortem { '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. */ @@ -87,26 +65,24 @@ 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. + * The first positive integer that cannot be encoded in 4 bits. */ - public static final int maxSubstringLength = 10; + public static final long int4 = 16; /** - * The minimum substring length. + * The first positive integer that cannot be encoded in 7 bits. */ - public static final int minSubstringLength = 3; + public static final long int7 = 144; /** - * The package supports tracing for debugging. + * The first positive integer that cannot be encoded in 14 bits. */ - public static final boolean probe = false; + public static final long int14 = 16528; /** - * The maximum number of substrings added to the substrings keep per - * string. + * The package supports tracing for debugging. */ - public static final int substringLimit = 40; + public static final boolean probe = false; /** * The value code for an empty object. @@ -154,62 +130,56 @@ 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.namehuffext.generate(); + this.stringhuff.generate(); + this.stringhuffext.generate(); } /** @@ -222,7 +192,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 + " "); @@ -230,18 +200,23 @@ 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) { - log(integer + ":" + width + " "); + if (width == 1) { + log(integer); + } else { + log(integer + ":" + 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); @@ -250,8 +225,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 <= '}') { @@ -274,8 +249,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/src/main/java/org/json/zip/Keep.java b/src/main/java/org/json/zip/Keep.java index 377e344e2..bc647b6a0 100644 --- a/src/main/java/org/json/zip/Keep.java +++ b/src/main/java/org/json/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/src/main/java/org/json/zip/MapKeep.java b/src/main/java/org/json/zip/MapKeep.java deleted file mode 100644 index 1374e08d3..000000000 --- a/src/main/java/org/json/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/src/main/java/org/json/zip/PostMortem.java b/src/main/java/org/json/zip/PostMortem.java index 22416d700..0f220ee48 100644 --- a/src/main/java/org/json/zip/PostMortem.java +++ b/src/main/java/org/json/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/src/main/java/org/json/zip/TrieKeep.java b/src/main/java/org/json/zip/TrieKeep.java deleted file mode 100644 index dcb13c7a0..000000000 --- a/src/main/java/org/json/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); - } -} diff --git a/src/main/java/org/json/zip/Decompressor.java b/src/main/java/org/json/zip/Unzipper.java similarity index 62% rename from src/main/java/org/json/zip/Decompressor.java rename to src/main/java/org/json/zip/Unzipper.java index 108a2e2c1..a7c308eda 100644 --- a/src/main/java/org/json/zip/Decompressor.java +++ b/src/main/java/org/json/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 2013-04-18 + * @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,100 +241,39 @@ 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("\n"); + log(); + } + String name = read(this.namehuff, this.namehuffext, this.namekeep); + if (jsonobject.opt(name) != null) { + throw new JSONException("Duplicate key."); } - String name = readName(); - jsonobject.put(name, !bit() ? readString() : readValue()); + 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: - 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 integer; case 1: byte[] bytes = new byte[256]; int length = 0; @@ -304,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: @@ -318,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/src/main/java/org/json/zip/Compressor.java b/src/main/java/org/json/zip/Zipper.java similarity index 68% rename from src/main/java/org/json/zip/Compressor.java rename to src/main/java/org/json/zip/Zipper.java index 6dddff420..48b4f1acb 100644 --- a/src/main/java/org/json/zip/Compressor.java +++ b/src/main/java/org/json/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 2013-04-18 + * @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; } @@ -75,8 +74,8 @@ public Compressor(BitWriter bitwriter) { * 'e' is 13. * * @param digit - * An ASCII character from a JSIN number. - * @return + * An ASCII character from a JSON number. + * @return The number code. */ private static int bcd(char digit) { if (digit >= '0' && digit <= '9') { @@ -107,27 +106,24 @@ public void flush() throws JSONException { /** * Output a one bit. * - * @throws IOException + * @throws JSONException */ private void one() throws JSONException { - if (probe) { - log(1); - } write(1, 1); } /** * 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); } @@ -174,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); + } } } @@ -210,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) { @@ -222,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). @@ -298,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"); } @@ -311,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 { @@ -323,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); } @@ -338,20 +324,19 @@ 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("\n"); + log(); } Object key = keys.next(); if (key instanceof String) { @@ -382,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 { @@ -391,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); @@ -403,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. +// But if it is not found, emit the string's characters. Register the string +// so that a later lookup can 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(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); } /** @@ -499,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) { @@ -517,11 +428,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; } } @@ -530,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); @@ -541,35 +452,30 @@ private void writeValue(Object value) throws JSONException { * Output a zero bit. * * @throws JSONException - * - * @throws IOException */ private void zero() throws JSONException { - if (probe) { - log(0); - } 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); } }