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);
}
}