1
0
Fork 0
mirror of https://github.com/ethauvin/JSON-java.git synced 2025-06-17 07:50:52 -07:00

Java 1.8.

This commit is contained in:
Douglas Crockford 2014-05-05 15:09:32 -07:00
parent 48d31b7f5c
commit a9a0762383
26 changed files with 747 additions and 819 deletions

View file

@ -41,7 +41,7 @@ SOFTWARE.
* The names for the elements in the JSONObjects can be taken from the names * The names for the elements in the JSONObjects can be taken from the names
* in the first row. * in the first row.
* @author JSON.org * @author JSON.org
* @version 2012-11-13 * @version 2014-05-03
*/ */
public class CDL { public class CDL {
@ -142,7 +142,7 @@ public class CDL {
* @return A string ending in NEWLINE. * @return A string ending in NEWLINE.
*/ */
public static String rowToString(JSONArray ja) { public static String rowToString(JSONArray ja) {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < ja.length(); i += 1) { for (int i = 0; i < ja.length(); i += 1) {
if (i > 0) { if (i > 0) {
sb.append(','); sb.append(',');

View file

@ -1,169 +1,169 @@
package org.json; package org.json;
/* /*
Copyright (c) 2002 JSON.org Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
The Software shall be used for Good, not Evil. The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
/** /**
* Convert a web browser cookie specification to a JSONObject and back. * Convert a web browser cookie specification to a JSONObject and back.
* JSON and Cookies are both notations for name/value pairs. * JSON and Cookies are both notations for name/value pairs.
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2014-05-03
*/ */
public class Cookie { public class Cookie {
/** /**
* Produce a copy of a string in which the characters '+', '%', '=', ';' * Produce a copy of a string in which the characters '+', '%', '=', ';'
* and control characters are replaced with "%hh". This is a gentle form * and control characters are replaced with "%hh". This is a gentle form
* of URL encoding, attempting to cause as little distortion to the * of URL encoding, attempting to cause as little distortion to the
* string as possible. The characters '=' and ';' are meta characters in * string as possible. The characters '=' and ';' are meta characters in
* cookies. By convention, they are escaped using the URL-encoding. This is * cookies. By convention, they are escaped using the URL-encoding. This is
* only a convention, not a standard. Often, cookies are expected to have * only a convention, not a standard. Often, cookies are expected to have
* encoded values. We encode '=' and ';' because we must. We encode '%' and * encoded values. We encode '=' and ';' because we must. We encode '%' and
* '+' because they are meta characters in URL encoding. * '+' because they are meta characters in URL encoding.
* @param string The source string. * @param string The source string.
* @return The escaped result. * @return The escaped result.
*/ */
public static String escape(String string) { public static String escape(String string) {
char c; char c;
String s = string.trim(); String s = string.trim();
StringBuffer sb = new StringBuffer(); int length = s.length();
int length = s.length(); StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i += 1) { for (int i = 0; i < length; i += 1) {
c = s.charAt(i); c = s.charAt(i);
if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
sb.append('%'); sb.append('%');
sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16));
sb.append(Character.forDigit((char)(c & 0x0f), 16)); sb.append(Character.forDigit((char)(c & 0x0f), 16));
} else { } else {
sb.append(c); sb.append(c);
} }
} }
return sb.toString(); return sb.toString();
} }
/** /**
* Convert a cookie specification string into a JSONObject. The string * Convert a cookie specification string into a JSONObject. The string
* will contain a name value pair separated by '='. The name and the value * will contain a name value pair separated by '='. The name and the value
* will be unescaped, possibly converting '+' and '%' sequences. The * will be unescaped, possibly converting '+' and '%' sequences. The
* cookie properties may follow, separated by ';', also represented as * cookie properties may follow, separated by ';', also represented as
* name=value (except the secure property, which does not have a value). * name=value (except the secure property, which does not have a value).
* The name will be stored under the key "name", and the value will be * The name will be stored under the key "name", and the value will be
* stored under the key "value". This method does not do checking or * stored under the key "value". This method does not do checking or
* validation of the parameters. It only converts the cookie string into * validation of the parameters. It only converts the cookie string into
* a JSONObject. * a JSONObject.
* @param string The cookie specification string. * @param string The cookie specification string.
* @return A JSONObject containing "name", "value", and possibly other * @return A JSONObject containing "name", "value", and possibly other
* members. * members.
* @throws JSONException * @throws JSONException
*/ */
public static JSONObject toJSONObject(String string) throws JSONException { public static JSONObject toJSONObject(String string) throws JSONException {
String name; String name;
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject();
Object value; Object value;
JSONTokener x = new JSONTokener(string); JSONTokener x = new JSONTokener(string);
jo.put("name", x.nextTo('=')); jo.put("name", x.nextTo('='));
x.next('='); x.next('=');
jo.put("value", x.nextTo(';')); jo.put("value", x.nextTo(';'));
x.next(); x.next();
while (x.more()) { while (x.more()) {
name = unescape(x.nextTo("=;")); name = unescape(x.nextTo("=;"));
if (x.next() != '=') { if (x.next() != '=') {
if (name.equals("secure")) { if (name.equals("secure")) {
value = Boolean.TRUE; value = Boolean.TRUE;
} else { } else {
throw x.syntaxError("Missing '=' in cookie parameter."); throw x.syntaxError("Missing '=' in cookie parameter.");
} }
} else { } else {
value = unescape(x.nextTo(';')); value = unescape(x.nextTo(';'));
x.next(); x.next();
} }
jo.put(name, value); jo.put(name, value);
} }
return jo; return jo;
} }
/** /**
* Convert a JSONObject into a cookie specification string. The JSONObject * Convert a JSONObject into a cookie specification string. The JSONObject
* must contain "name" and "value" members. * must contain "name" and "value" members.
* If the JSONObject contains "expires", "domain", "path", or "secure" * If the JSONObject contains "expires", "domain", "path", or "secure"
* members, they will be appended to the cookie specification string. * members, they will be appended to the cookie specification string.
* All other members are ignored. * All other members are ignored.
* @param jo A JSONObject * @param jo A JSONObject
* @return A cookie specification string * @return A cookie specification string
* @throws JSONException * @throws JSONException
*/ */
public static String toString(JSONObject jo) 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(escape(jo.getString("name")));
sb.append("="); sb.append("=");
sb.append(escape(jo.getString("value"))); sb.append(escape(jo.getString("value")));
if (jo.has("expires")) { if (jo.has("expires")) {
sb.append(";expires="); sb.append(";expires=");
sb.append(jo.getString("expires")); sb.append(jo.getString("expires"));
} }
if (jo.has("domain")) { if (jo.has("domain")) {
sb.append(";domain="); sb.append(";domain=");
sb.append(escape(jo.getString("domain"))); sb.append(escape(jo.getString("domain")));
} }
if (jo.has("path")) { if (jo.has("path")) {
sb.append(";path="); sb.append(";path=");
sb.append(escape(jo.getString("path"))); sb.append(escape(jo.getString("path")));
} }
if (jo.optBoolean("secure")) { if (jo.optBoolean("secure")) {
sb.append(";secure"); sb.append(";secure");
} }
return sb.toString(); return sb.toString();
} }
/** /**
* Convert <code>%</code><i>hh</i> sequences to single characters, and * Convert <code>%</code><i>hh</i> sequences to single characters, and
* convert plus to space. * convert plus to space.
* @param string A string that may contain * @param string A string that may contain
* <code>+</code>&nbsp;<small>(plus)</small> and * <code>+</code>&nbsp;<small>(plus)</small> and
* <code>%</code><i>hh</i> sequences. * <code>%</code><i>hh</i> sequences.
* @return The unescaped string. * @return The unescaped string.
*/ */
public static String unescape(String string) { public static String unescape(String string) {
int length = string.length(); int length = string.length();
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
char c = string.charAt(i); char c = string.charAt(i);
if (c == '+') { if (c == '+') {
c = ' '; c = ' ';
} else if (c == '%' && i + 2 < length) { } else if (c == '%' && i + 2 < length) {
int d = JSONTokener.dehexchar(string.charAt(i + 1)); int d = JSONTokener.dehexchar(string.charAt(i + 1));
int e = JSONTokener.dehexchar(string.charAt(i + 2)); int e = JSONTokener.dehexchar(string.charAt(i + 2));
if (d >= 0 && e >= 0) { if (d >= 0 && e >= 0) {
c = (char)(d * 16 + e); c = (char)(d * 16 + e);
i += 2; i += 2;
} }
} }
sb.append(c); sb.append(c);
} }
return sb.toString(); return sb.toString();
} }
} }

View file

@ -1,90 +1,89 @@
package org.json; package org.json;
/* /*
Copyright (c) 2002 JSON.org Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
The Software shall be used for Good, not Evil. The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
import java.util.Iterator; import java.util.Iterator;
/** /**
* Convert a web browser cookie list string to a JSONObject and back. * Convert a web browser cookie list string to a JSONObject and back.
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2014-05-03
*/ */
public class CookieList { public class CookieList {
/** /**
* Convert a cookie list into a JSONObject. A cookie list is a sequence * Convert a cookie list into a JSONObject. A cookie list is a sequence
* of name/value pairs. The names are separated from the values by '='. * of name/value pairs. The names are separated from the values by '='.
* The pairs are separated by ';'. The names and the values * The pairs are separated by ';'. The names and the values
* will be unescaped, possibly converting '+' and '%' sequences. * will be unescaped, possibly converting '+' and '%' sequences.
* *
* To add a cookie to a cooklist, * To add a cookie to a cooklist,
* cookielistJSONObject.put(cookieJSONObject.getString("name"), * cookielistJSONObject.put(cookieJSONObject.getString("name"),
* cookieJSONObject.getString("value")); * cookieJSONObject.getString("value"));
* @param string A cookie list string * @param string A cookie list string
* @return A JSONObject * @return A JSONObject
* @throws JSONException * @throws JSONException
*/ */
public static JSONObject toJSONObject(String string) throws JSONException { public static JSONObject toJSONObject(String string) throws JSONException {
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject();
JSONTokener x = new JSONTokener(string); JSONTokener x = new JSONTokener(string);
while (x.more()) { while (x.more()) {
String name = Cookie.unescape(x.nextTo('=')); String name = Cookie.unescape(x.nextTo('='));
x.next('='); x.next('=');
jo.put(name, Cookie.unescape(x.nextTo(';'))); jo.put(name, Cookie.unescape(x.nextTo(';')));
x.next(); x.next();
} }
return jo; return jo;
} }
/**
/** * Convert a JSONObject into a cookie list. A cookie list is a sequence
* 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 '='.
* of name/value pairs. The names are separated from the values by '='. * The pairs are separated by ';'. The characters '%', '+', '=', and ';'
* The pairs are separated by ';'. The characters '%', '+', '=', and ';' * in the names and values are replaced by "%hh".
* in the names and values are replaced by "%hh". * @param jo A JSONObject
* @param jo A JSONObject * @return A cookie list string
* @return A cookie list string * @throws JSONException
* @throws JSONException */
*/ public static String toString(JSONObject jo) throws JSONException {
public static String toString(JSONObject jo) throws JSONException { boolean b = false;
boolean b = false; Iterator<String> keys = jo.keys();
Iterator keys = jo.keys(); String string;
String string; StringBuilder sb = new StringBuilder();
StringBuffer sb = new StringBuffer(); while (keys.hasNext()) {
while (keys.hasNext()) { string = keys.next();
string = keys.next().toString(); if (!jo.isNull(string)) {
if (!jo.isNull(string)) { if (b) {
if (b) { sb.append(';');
sb.append(';'); }
} sb.append(Cookie.escape(string));
sb.append(Cookie.escape(string)); sb.append("=");
sb.append("="); sb.append(Cookie.escape(jo.getString(string)));
sb.append(Cookie.escape(jo.getString(string))); b = true;
b = true; }
} }
} return sb.toString();
return sb.toString(); }
} }
}

View file

@ -29,7 +29,7 @@ import java.util.Iterator;
/** /**
* Convert an HTTP header to a JSONObject and back. * Convert an HTTP header to a JSONObject and back.
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2014-05-03
*/ */
public class HTTP { public class HTTP {
@ -125,9 +125,9 @@ public class HTTP {
* information. * information.
*/ */
public static String toString(JSONObject jo) throws JSONException { public static String toString(JSONObject jo) throws JSONException {
Iterator keys = jo.keys(); Iterator<String> keys = jo.keys();
String string; String string;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { if (jo.has("Status-Code") && jo.has("Reason-Phrase")) {
sb.append(jo.getString("HTTP-Version")); sb.append(jo.getString("HTTP-Version"));
sb.append(' '); sb.append(' ');
@ -147,7 +147,7 @@ public class HTTP {
} }
sb.append(CRLF); sb.append(CRLF);
while (keys.hasNext()) { while (keys.hasNext()) {
string = keys.next().toString(); string = keys.next();
if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) && if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) &&
!"Reason-Phrase".equals(string) && !"Method".equals(string) && !"Reason-Phrase".equals(string) && !"Method".equals(string) &&
!"Request-URI".equals(string) && !jo.isNull(string)) { !"Request-URI".equals(string) && !jo.isNull(string)) {

View file

@ -28,7 +28,7 @@ SOFTWARE.
* The HTTPTokener extends the JSONTokener to provide additional methods * The HTTPTokener extends the JSONTokener to provide additional methods
* for the parsing of HTTP headers. * for the parsing of HTTP headers.
* @author JSON.org * @author JSON.org
* @version 2012-11-13 * @version 2014-05-03
*/ */
public class HTTPTokener extends JSONTokener { public class HTTPTokener extends JSONTokener {
@ -49,7 +49,7 @@ public class HTTPTokener extends JSONTokener {
public String nextToken() throws JSONException { public String nextToken() throws JSONException {
char c; char c;
char q; char q;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
do { do {
c = next(); c = next();
} while (Character.isWhitespace(c)); } while (Character.isWhitespace(c));

View file

@ -75,20 +75,20 @@ import java.util.Map;
* </ul> * </ul>
* *
* @author JSON.org * @author JSON.org
* @version 2014-04-21 * @version 2014-05-03
*/ */
public class JSONArray { public class JSONArray {
/** /**
* The arrayList where the JSONArray's properties are kept. * The arrayList where the JSONArray's properties are kept.
*/ */
private final ArrayList myArrayList; private final ArrayList<Object> myArrayList;
/** /**
* Construct an empty JSONArray. * Construct an empty JSONArray.
*/ */
public JSONArray() { public JSONArray() {
this.myArrayList = new ArrayList(); this.myArrayList = new ArrayList<Object>();
} }
/** /**
@ -150,10 +150,10 @@ public class JSONArray {
* @param collection * @param collection
* A Collection. * A Collection.
*/ */
public JSONArray(Collection collection) { public JSONArray(Collection<Object> collection) {
this.myArrayList = new ArrayList(); this.myArrayList = new ArrayList<Object>();
if (collection != null) { if (collection != null) {
Iterator iter = collection.iterator(); Iterator<Object> iter = collection.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
this.myArrayList.add(JSONObject.wrap(iter.next())); this.myArrayList.add(JSONObject.wrap(iter.next()));
} }
@ -357,7 +357,7 @@ public class JSONArray {
*/ */
public String join(String separator) throws JSONException { public String join(String separator) throws JSONException {
int len = this.length(); int len = this.length();
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i += 1) { for (int i = 0; i < len; i += 1) {
if (i > 0) { if (i > 0) {
@ -593,7 +593,7 @@ public class JSONArray {
* A Collection value. * A Collection value.
* @return this. * @return this.
*/ */
public JSONArray put(Collection value) { public JSONArray put(Collection<Object> value) {
this.put(new JSONArray(value)); this.put(new JSONArray(value));
return this; return this;
} }
@ -646,7 +646,7 @@ public class JSONArray {
* A Map value. * A Map value.
* @return this. * @return this.
*/ */
public JSONArray put(Map value) { public JSONArray put(Map<String, Object> value) {
this.put(new JSONObject(value)); this.put(new JSONObject(value));
return this; return this;
} }
@ -695,7 +695,7 @@ public class JSONArray {
* @throws JSONException * @throws JSONException
* If the index is negative or if the value is not finite. * 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<Object> value) throws JSONException {
this.put(index, new JSONArray(value)); this.put(index, new JSONArray(value));
return this; return this;
} }
@ -767,7 +767,7 @@ public class JSONArray {
* If the index is negative or if the the value is an invalid * If the index is negative or if the the value is an invalid
* number. * number.
*/ */
public JSONArray put(int index, Map value) throws JSONException { public JSONArray put(int index, Map<String, Object> value) throws JSONException {
this.put(index, new JSONObject(value)); this.put(index, new JSONObject(value));
return this; return this;
} }

View file

@ -4,7 +4,7 @@ package org.json;
* The JSONException is thrown by the JSON.org classes when things are amiss. * The JSONException is thrown by the JSON.org classes when things are amiss.
* *
* @author JSON.org * @author JSON.org
* @version 2013-02-10 * @version 2014-05-03
*/ */
public class JSONException extends RuntimeException { public class JSONException extends RuntimeException {
private static final long serialVersionUID = 0; private static final long serialVersionUID = 0;
@ -22,6 +22,7 @@ public class JSONException extends RuntimeException {
/** /**
* Constructs a new JSONException with the specified cause. * Constructs a new JSONException with the specified cause.
* @param cause The cause.
*/ */
public JSONException(Throwable cause) { public JSONException(Throwable cause) {
super(cause.getMessage()); super(cause.getMessage());
@ -32,9 +33,10 @@ public class JSONException extends RuntimeException {
* Returns the cause of this exception or null if the cause is nonexistent * Returns the cause of this exception or null if the cause is nonexistent
* or unknown. * 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. * or unknown.
*/ */
@Override
public Throwable getCause() { public Throwable getCause() {
return this.cause; return this.cause;
} }

View file

@ -33,7 +33,7 @@ import java.util.Iterator;
* the JsonML transform. * the JsonML transform.
* *
* @author JSON.org * @author JSON.org
* @version 2012-03-28 * @version 2014-05-03
*/ */
public class JSONML { public class JSONML {
@ -53,12 +53,12 @@ public class JSONML {
) throws JSONException { ) throws JSONException {
String attribute; String attribute;
char c; char c;
String closeTag = null; String closeTag = null;
int i; int i;
JSONArray newja = null; JSONArray newja = null;
JSONObject newjo = null; JSONObject newjo = null;
Object token; Object token;
String tagName = null; String tagName = null;
// Test for and skip past these forms: // Test for and skip past these forms:
// <!-- ... --> // <!-- ... -->
@ -312,15 +312,15 @@ public class JSONML {
* @throws JSONException * @throws JSONException
*/ */
public static String toString(JSONArray ja) throws JSONException { public static String toString(JSONArray ja) throws JSONException {
int i; int i;
JSONObject jo; JSONObject jo;
String key; String key;
Iterator keys; Iterator<String> keys;
int length; int length;
Object object; Object object;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
String tagName; String tagName;
String value; String value;
// Emit <tagName // Emit <tagName
@ -339,7 +339,7 @@ public class JSONML {
keys = jo.keys(); keys = jo.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
key = keys.next().toString(); key = keys.next();
XML.noSpace(key); XML.noSpace(key);
value = jo.optString(key); value = jo.optString(key);
if (value != null) { if (value != null) {
@ -355,7 +355,7 @@ public class JSONML {
i = 1; i = 1;
} }
//Emit content in body // Emit content in body
length = ja.length(); length = ja.length();
if (i >= length) { if (i >= length) {
@ -394,15 +394,15 @@ public class JSONML {
* @throws JSONException * @throws JSONException
*/ */
public static String toString(JSONObject jo) throws JSONException { public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
int i; int i;
JSONArray ja; JSONArray ja;
String key; String key;
Iterator keys; Iterator<String> keys;
int length; int length;
Object object; Object object;
String tagName; String tagName;
String value; String value;
//Emit <tagName //Emit <tagName
@ -419,7 +419,7 @@ public class JSONML {
keys = jo.keys(); keys = jo.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
key = keys.next().toString(); key = keys.next();
if (!"tagName".equals(key) && !"childNodes".equals(key)) { if (!"tagName".equals(key) && !"childNodes".equals(key)) {
XML.noSpace(key); XML.noSpace(key);
value = jo.optString(key); value = jo.optString(key);

View file

@ -36,6 +36,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
@ -90,7 +91,7 @@ import java.util.Set;
* </ul> * </ul>
* *
* @author JSON.org * @author JSON.org
* @version 2014-04-21 * @version 2014-05-03
*/ */
public class JSONObject { public class JSONObject {
/** /**
@ -106,6 +107,7 @@ public class JSONObject {
* *
* @return NULL. * @return NULL.
*/ */
@Override
protected final Object clone() { protected final Object clone() {
return this; return this;
} }
@ -118,6 +120,7 @@ public class JSONObject {
* @return true if the object parameter is the JSONObject.NULL object or * @return true if the object parameter is the JSONObject.NULL object or
* null. * null.
*/ */
@Override
public boolean equals(Object object) { public boolean equals(Object object) {
return object == null || object == this; return object == null || object == this;
} }
@ -135,7 +138,7 @@ public class JSONObject {
/** /**
* The map where the JSONObject's properties are kept. * The map where the JSONObject's properties are kept.
*/ */
private final Map map; private final Map<String, Object> map;
/** /**
* It is sometimes more convenient and less ambiguous to have a * It is sometimes more convenient and less ambiguous to have a
@ -149,7 +152,7 @@ public class JSONObject {
* Construct an empty JSONObject. * Construct an empty JSONObject.
*/ */
public JSONObject() { public JSONObject() {
this.map = new HashMap(); this.map = new HashMap<String, Object>();
} }
/** /**
@ -239,15 +242,15 @@ public class JSONObject {
* the JSONObject. * the JSONObject.
* @throws JSONException * @throws JSONException
*/ */
public JSONObject(Map map) { public JSONObject(Map<String, Object> map) {
this.map = new HashMap(); this.map = new HashMap<String, Object>();
if (map != null) { if (map != null) {
Iterator i = map.entrySet().iterator(); Iterator<Entry<String, Object>> i = map.entrySet().iterator();
while (i.hasNext()) { while (i.hasNext()) {
Map.Entry e = (Map.Entry) i.next(); Entry<String, Object> entry = i.next();
Object value = e.getValue(); Object value = entry.getValue();
if (value != null) { if (value != null) {
this.map.put(e.getKey(), wrap(value)); this.map.put(entry.getKey(), wrap(value));
} }
} }
} }
@ -338,10 +341,10 @@ public class JSONObject {
// Iterate through the keys in the bundle. // Iterate through the keys in the bundle.
Enumeration keys = bundle.getKeys(); Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) { while (keys.hasMoreElements()) {
Object key = keys.nextElement(); Object key = keys.nextElement();
if (key instanceof String) { if (key != null) {
// Go through the path, ensuring that there is a nested JSONObject for each // 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 // segment except the last. Add the value using the last segment's name into
@ -609,11 +612,11 @@ public class JSONObject {
if (length == 0) { if (length == 0) {
return null; return null;
} }
Iterator iterator = jo.keys(); Iterator<String> iterator = jo.keys();
String[] names = new String[length]; String[] names = new String[length];
int i = 0; int i = 0;
while (iterator.hasNext()) { while (iterator.hasNext()) {
names[i] = (String) iterator.next(); names[i] = iterator.next();
i += 1; i += 1;
} }
return names; return names;
@ -686,13 +689,13 @@ public class JSONObject {
if (value == null) { if (value == null) {
this.put(key, 1); this.put(key, 1);
} else if (value instanceof Integer) { } else if (value instanceof Integer) {
this.put(key, ((Integer) value).intValue() + 1); this.put(key, (Integer) value + 1);
} else if (value instanceof Long) { } else if (value instanceof Long) {
this.put(key, ((Long) value).longValue() + 1); this.put(key, (Long) value + 1);
} else if (value instanceof Double) { } else if (value instanceof Double) {
this.put(key, ((Double) value).doubleValue() + 1); this.put(key, (Double) value + 1);
} else if (value instanceof Float) { } else if (value instanceof Float) {
this.put(key, ((Float) value).floatValue() + 1); this.put(key, (Float) value + 1);
} else { } else {
throw new JSONException("Unable to increment [" + quote(key) + "]."); throw new JSONException("Unable to increment [" + quote(key) + "].");
} }
@ -717,7 +720,7 @@ public class JSONObject {
* *
* @return An iterator of the keys. * @return An iterator of the keys.
*/ */
public Iterator keys() { public Iterator<String> keys() {
return this.keySet().iterator(); return this.keySet().iterator();
} }
@ -726,7 +729,7 @@ public class JSONObject {
* *
* @return A keySet. * @return A keySet.
*/ */
public Set keySet() { public Set<String> keySet() {
return this.map.keySet(); return this.map.keySet();
} }
@ -748,7 +751,7 @@ public class JSONObject {
*/ */
public JSONArray names() { public JSONArray names() {
JSONArray ja = new JSONArray(); JSONArray ja = new JSONArray();
Iterator keys = this.keys(); Iterator<String> keys = this.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
ja.put(keys.next()); ja.put(keys.next());
} }
@ -1050,7 +1053,7 @@ public class JSONObject {
* @return this. * @return this.
* @throws JSONException * @throws JSONException
*/ */
public JSONObject put(String key, Collection value) throws JSONException { public JSONObject put(String key, Collection<Object> value) throws JSONException {
this.put(key, new JSONArray(value)); this.put(key, new JSONArray(value));
return this; return this;
} }
@ -1114,7 +1117,7 @@ public class JSONObject {
* @return this. * @return this.
* @throws JSONException * @throws JSONException
*/ */
public JSONObject put(String key, Map value) throws JSONException { public JSONObject put(String key, Map<String, Object> value) throws JSONException {
this.put(key, new JSONObject(value)); this.put(key, new JSONObject(value));
return this; return this;
} }
@ -1151,9 +1154,9 @@ public class JSONObject {
* are both non-null, and only if there is not already a member with that * are both non-null, and only if there is not already a member with that
* name. * name.
* *
* @param key * @param key string
* @param value * @param value object
* @return his. * @return this.
* @throws JSONException * @throws JSONException
* if the key is a duplicate * if the key is a duplicate
*/ */
@ -1294,13 +1297,13 @@ public class JSONObject {
if (!(other instanceof JSONObject)) { if (!(other instanceof JSONObject)) {
return false; return false;
} }
Set set = this.keySet(); Set<String> set = this.keySet();
if (!set.equals(((JSONObject)other).keySet())) { if (!set.equals(((JSONObject)other).keySet())) {
return false; return false;
} }
Iterator iterator = set.iterator(); Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
String name = (String)iterator.next(); String name = iterator.next();
Object valueThis = this.get(name); Object valueThis = this.get(name);
Object valueOther = ((JSONObject)other).get(name); Object valueOther = ((JSONObject)other).get(name);
if (valueThis instanceof JSONObject) { if (valueThis instanceof JSONObject) {
@ -1361,8 +1364,8 @@ public class JSONObject {
} else { } else {
Long myLong = new Long(string); Long myLong = new Long(string);
if (string.equals(myLong.toString())) { if (string.equals(myLong.toString())) {
if (myLong.longValue() == myLong.intValue()) { if (myLong == myLong.intValue()) {
return new Integer(myLong.intValue()); return myLong.intValue();
} else { } else {
return myLong; return myLong;
} }
@ -1509,10 +1512,10 @@ public class JSONObject {
return value.toString(); return value.toString();
} }
if (value instanceof Map) { if (value instanceof Map) {
return new JSONObject((Map) value).toString(); return new JSONObject((Map<String, Object>)value).toString();
} }
if (value instanceof Collection) { if (value instanceof Collection) {
return new JSONArray((Collection) value).toString(); return new JSONArray((Collection<Object>) value).toString();
} }
if (value.getClass().isArray()) { if (value.getClass().isArray()) {
return new JSONArray(value).toString(); return new JSONArray(value).toString();
@ -1548,13 +1551,13 @@ public class JSONObject {
} }
if (object instanceof Collection) { if (object instanceof Collection) {
return new JSONArray((Collection) object); return new JSONArray((Collection<Object>) object);
} }
if (object.getClass().isArray()) { if (object.getClass().isArray()) {
return new JSONArray(object); return new JSONArray(object);
} }
if (object instanceof Map) { if (object instanceof Map) {
return new JSONObject((Map) object); return new JSONObject((Map<String, Object>) object);
} }
Package objectPackage = object.getClass().getPackage(); Package objectPackage = object.getClass().getPackage();
String objectPackageName = objectPackage != null ? objectPackage String objectPackageName = objectPackage != null ? objectPackage
@ -1592,9 +1595,9 @@ public class JSONObject {
} else if (value instanceof JSONArray) { } else if (value instanceof JSONArray) {
((JSONArray) value).write(writer, indentFactor, indent); ((JSONArray) value).write(writer, indentFactor, indent);
} else if (value instanceof Map) { } else if (value instanceof Map) {
new JSONObject((Map) value).write(writer, indentFactor, indent); new JSONObject((Map<String, Object>) value).write(writer, indentFactor, indent);
} else if (value instanceof Collection) { } else if (value instanceof Collection) {
new JSONArray((Collection) value).write(writer, indentFactor, new JSONArray((Collection<Object>) value).write(writer, indentFactor,
indent); indent);
} else if (value.getClass().isArray()) { } else if (value.getClass().isArray()) {
new JSONArray(value).write(writer, indentFactor, indent); new JSONArray(value).write(writer, indentFactor, indent);
@ -1636,7 +1639,7 @@ public class JSONObject {
try { try {
boolean commanate = false; boolean commanate = false;
final int length = this.length(); final int length = this.length();
Iterator keys = this.keys(); Iterator<String> keys = this.keys();
writer.write('{'); writer.write('{');
if (length == 1) { if (length == 1) {
@ -1663,8 +1666,7 @@ public class JSONObject {
if (indentFactor > 0) { if (indentFactor > 0) {
writer.write(' '); writer.write(' ');
} }
writeValue(writer, this.map.get(key), indentFactor, writeValue(writer, this.map.get(key), indentFactor, newindent);
newindent);
commanate = true; commanate = true;
} }
if (indentFactor > 0) { if (indentFactor > 0) {

View file

@ -36,7 +36,7 @@ SOFTWARE.
* it. It is used by the JSONObject and JSONArray constructors to parse * it. It is used by the JSONObject and JSONArray constructors to parse
* JSON source strings. * JSON source strings.
* @author JSON.org * @author JSON.org
* @version 2012-02-16 * @version 2014-05-03
*/ */
public class JSONTokener { public class JSONTokener {
@ -69,6 +69,7 @@ public class JSONTokener {
/** /**
* Construct a JSONTokener from an InputStream. * Construct a JSONTokener from an InputStream.
* @param inputStream The source.
*/ */
public JSONTokener(InputStream inputStream) throws JSONException { public JSONTokener(InputStream inputStream) throws JSONException {
this(new InputStreamReader(inputStream)); this(new InputStreamReader(inputStream));
@ -250,7 +251,7 @@ public class JSONTokener {
*/ */
public String nextString(char quote) throws JSONException { public String nextString(char quote) throws JSONException {
char c; char c;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
c = this.next(); c = this.next();
switch (c) { switch (c) {
@ -306,7 +307,7 @@ public class JSONTokener {
* @return A string. * @return A string.
*/ */
public String nextTo(char delimiter) throws JSONException { public String nextTo(char delimiter) throws JSONException {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
char c = this.next(); char c = this.next();
if (c == delimiter || c == 0 || c == '\n' || c == '\r') { if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
@ -328,7 +329,7 @@ public class JSONTokener {
*/ */
public String nextTo(String delimiters) throws JSONException { public String nextTo(String delimiters) throws JSONException {
char c; char c;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
c = this.next(); c = this.next();
if (delimiters.indexOf(c) >= 0 || c == 0 || if (delimiters.indexOf(c) >= 0 || c == 0 ||
@ -375,7 +376,7 @@ public class JSONTokener {
* formatting character. * formatting character.
*/ */
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c); sb.append(c);
c = this.next(); c = this.next();
@ -414,10 +415,9 @@ public class JSONTokener {
return c; return c;
} }
} while (c != to); } while (c != to);
} catch (IOException exc) { } catch (IOException exception) {
throw new JSONException(exc); throw new JSONException(exception);
} }
this.back(); this.back();
return c; return c;
} }

View file

@ -269,7 +269,7 @@ public class JSONWriter {
/** /**
* Push an array or object scope. * 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. * @throws JSONException If nesting is too deep.
*/ */
private void push(JSONObject jo) throws JSONException { private void push(JSONObject jo) throws JSONException {

View file

@ -137,7 +137,6 @@ public class Kim {
* The point at which to take bytes. * The point at which to take bytes.
* @param thru * @param thru
* The point at which to stop taking bytes. * The point at which to stop taking bytes.
* @return the substring
*/ */
public Kim(Kim kim, int from, int thru) { public Kim(Kim kim, int from, int thru) {
this(kim.bytes, from, thru); this(kim.bytes, from, thru);

View file

@ -31,7 +31,7 @@ import java.util.Properties;
/** /**
* Converts a Property file data into JSONObject and back. * Converts a Property file data into JSONObject and back.
* @author JSON.org * @author JSON.org
* @version 2013-05-23 * @version 2014-05-03
*/ */
public class Property { public class Property {
/** /**
@ -50,7 +50,6 @@ public class Property {
} }
} }
return jo; return jo;
} }
/** /**
@ -62,13 +61,12 @@ public class Property {
public static Properties toProperties(JSONObject jo) throws JSONException { public static Properties toProperties(JSONObject jo) throws JSONException {
Properties properties = new Properties(); Properties properties = new Properties();
if (jo != null) { if (jo != null) {
Iterator keys = jo.keys(); Iterator<String> keys = jo.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
String name = keys.next().toString(); String name = keys.next();
properties.put(name, jo.getString(name)); properties.put(name, jo.getString(name));
} }
} }
return properties; return properties;
} }
} }

2
README
View file

@ -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 not evil." If your conscience cannot live with that, then choose a different
package. 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 JSONObject.java: The JSONObject can parse text from a String or a JSONTokener

View file

@ -26,41 +26,40 @@ SOFTWARE.
import java.util.Iterator; import java.util.Iterator;
/** /**
* This provides static methods to convert an XML text into a JSONObject, * This provides static methods to convert an XML text into a JSONObject,
* and to covert a JSONObject into an XML text. * and to covert a JSONObject into an XML text.
* @author JSON.org * @author JSON.org
* @version 2013-11-12 * @version 2014-05-03
*/ */
public class XML { public class XML {
/** The Character '&amp;'. */ /** The Character '&amp;'. */
public static final Character AMP = new Character('&'); public static final Character AMP = '&';
/** The Character '''. */ /** The Character '''. */
public static final Character APOS = new Character('\''); public static final Character APOS = '\'';
/** The Character '!'. */ /** The Character '!'. */
public static final Character BANG = new Character('!'); public static final Character BANG = '!';
/** The Character '='. */ /** The Character '='. */
public static final Character EQ = new Character('='); public static final Character EQ = '=';
/** The Character '>'. */ /** The Character '>'. */
public static final Character GT = new Character('>'); public static final Character GT = '>';
/** The Character '&lt;'. */ /** The Character '&lt;'. */
public static final Character LT = new Character('<'); public static final Character LT = '<';
/** The Character '?'. */ /** The Character '?'. */
public static final Character QUEST = new Character('?'); public static final Character QUEST = '?';
/** The Character '"'. */ /** The Character '"'. */
public static final Character QUOT = new Character('"'); public static final Character QUOT = '"';
/** The Character '/'. */ /** The Character '/'. */
public static final Character SLASH = new Character('/'); public static final Character SLASH = '/';
/** /**
* Replace special characters with XML escapes: * Replace special characters with XML escapes:
@ -74,7 +73,7 @@ public class XML {
* @return The escaped string. * @return The escaped string.
*/ */
public static String escape(String 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++) { for (int i = 0, length = string.length(); i < length; i++) {
char c = string.charAt(i); char c = string.charAt(i);
switch (c) { switch (c) {
@ -103,7 +102,7 @@ public class XML {
/** /**
* Throw an exception if the string contains whitespace. * Throw an exception if the string contains whitespace.
* Whitespace is not allowed in tagNames and attributes. * Whitespace is not allowed in tagNames and attributes.
* @param string * @param string A string.
* @throws JSONException * @throws JSONException
*/ */
public static void noSpace(String string) throws JSONException { public static void noSpace(String string) throws JSONException {
@ -379,15 +378,15 @@ public class XML {
*/ */
public static String toString(Object object, String tagName) public static String toString(Object object, String tagName)
throws JSONException { throws JSONException {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
int i; int i;
JSONArray ja; JSONArray ja;
JSONObject jo; JSONObject jo;
String key; String key;
Iterator keys; Iterator<String> keys;
int length; int length;
String string; String string;
Object value; Object value;
if (object instanceof JSONObject) { if (object instanceof JSONObject) {
// Emit <tagName> // Emit <tagName>
@ -403,16 +402,12 @@ public class XML {
jo = (JSONObject)object; jo = (JSONObject)object;
keys = jo.keys(); keys = jo.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
key = keys.next().toString(); key = keys.next();
value = jo.opt(key); value = jo.opt(key);
if (value == null) { if (value == null) {
value = ""; value = "";
} }
if (value instanceof String) { string = value instanceof String ? (String)value : null;
string = (String)value;
} else {
string = null;
}
// Emit content in body // Emit content in body

View file

@ -28,7 +28,7 @@ SOFTWARE.
* The XMLTokener extends the JSONTokener to provide additional methods * The XMLTokener extends the JSONTokener to provide additional methods
* for the parsing of XML texts. * for the parsing of XML texts.
* @author JSON.org * @author JSON.org
* @version 2012-11-13 * @version 2014-05-03
*/ */
public class XMLTokener extends JSONTokener { 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 /** The table of entity values. It initially contains Character values for
* amp, apos, gt, lt, quot. * amp, apos, gt, lt, quot.
*/ */
public static final java.util.HashMap entity; public static final java.util.HashMap<String, Character> entity;
static { static {
entity = new java.util.HashMap(8); entity = new java.util.HashMap<String, Character>(8);
entity.put("amp", XML.AMP); entity.put("amp", XML.AMP);
entity.put("apos", XML.APOS); entity.put("apos", XML.APOS);
entity.put("gt", XML.GT); entity.put("gt", XML.GT);
@ -63,7 +63,7 @@ public class XMLTokener extends JSONTokener {
public String nextCDATA() throws JSONException { public String nextCDATA() throws JSONException {
char c; char c;
int i; int i;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
c = next(); c = next();
if (end()) { if (end()) {
@ -91,7 +91,7 @@ public class XMLTokener extends JSONTokener {
*/ */
public Object nextContent() throws JSONException { public Object nextContent() throws JSONException {
char c; char c;
StringBuffer sb; StringBuilder sb;
do { do {
c = next(); c = next();
} while (Character.isWhitespace(c)); } while (Character.isWhitespace(c));
@ -101,7 +101,7 @@ public class XMLTokener extends JSONTokener {
if (c == '<') { if (c == '<') {
return XML.LT; return XML.LT;
} }
sb = new StringBuffer(); sb = new StringBuilder();
for (;;) { for (;;) {
if (c == '<' || c == 0) { if (c == '<' || c == 0) {
back(); back();
@ -125,7 +125,7 @@ public class XMLTokener extends JSONTokener {
* @throws JSONException If missing ';' in XML entity. * @throws JSONException If missing ';' in XML entity.
*/ */
public Object nextEntity(char ampersand) throws JSONException { public Object nextEntity(char ampersand) throws JSONException {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
char c = next(); char c = next();
if (Character.isLetterOrDigit(c) || c == '#') { if (Character.isLetterOrDigit(c) || c == '#') {
@ -219,7 +219,7 @@ public class XMLTokener extends JSONTokener {
public Object nextToken() throws JSONException { public Object nextToken() throws JSONException {
char c; char c;
char q; char q;
StringBuffer sb; StringBuilder sb;
do { do {
c = next(); c = next();
} while (Character.isWhitespace(c)); } while (Character.isWhitespace(c));
@ -244,7 +244,7 @@ public class XMLTokener extends JSONTokener {
case '"': case '"':
case '\'': case '\'':
q = c; q = c;
sb = new StringBuffer(); sb = new StringBuilder();
for (;;) { for (;;) {
c = next(); c = next();
if (c == 0) { if (c == 0) {
@ -263,7 +263,7 @@ public class XMLTokener extends JSONTokener {
// Name // Name
sb = new StringBuffer(); sb = new StringBuilder();
for (;;) { for (;;) {
sb.append(c); sb.append(c);
c = next(); c = next();

View file

@ -30,15 +30,10 @@ import java.io.InputStream;
/** /**
* This is a big endian bit reader. It reads its bits from an InputStream. * This is a big endian bit reader. It reads its bits from an InputStream.
* *
* @version 2013-04-18 * @version 2013-05-03
* *
*/ */
public class BitInputStream implements BitReader { public class BitInputStream implements BitReader {
/**
* 2^n - 1
*/
static final int[] mask = { 0, 1, 3, 7, 15, 31, 63, 127, 255 };
/** /**
* The number of bits remaining in the current byte. * The number of bits remaining in the current byte.
*/ */
@ -70,23 +65,6 @@ public class BitInputStream implements BitReader {
this.in = 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. * Read one bit.
* *
@ -111,20 +89,26 @@ public class BitInputStream implements BitReader {
/** /**
* Check that the rest of the block has been padded with zeroes. * Check that the rest of the block has been padded with zeroes.
* *
* @param factor * @param width
* The size of the block to pad. This will typically be 8, 16, * The size of the block to pad in bits.
* 32, 64, 128, 256, etc. * 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 * @return true if the block was zero padded, or false if the the padding
* contains any one bits. * contains any one bits.
* @throws IOException * @throws IOException
*/ */
public boolean pad(int factor) throws IOException { public boolean pad(int width) throws IOException {
int padding = factor - (int) (this.nrBits % factor);
boolean result = true; boolean result = true;
int gap = (int)this.nrBits % width;
for (int i = 0; i < padding; i += 1) { if (gap < 0) {
if (bit()) { gap += width;
result = false; }
if (gap != 0) {
int padding = width - gap;
while (padding > 0) {
if (bit()) {
result = false;
}
padding -= 1;
} }
} }
return result; return result;
@ -158,8 +142,8 @@ public class BitInputStream implements BitReader {
if (take > this.available) { if (take > this.available) {
take = this.available; take = this.available;
} }
result |= ((this.unread >>> (this.available - take)) & mask[take]) result |= ((this.unread >>> (this.available - take)) &
<< (width - take); ((1 << take) - 1)) << (width - take);
this.nrBits += take; this.nrBits += take;
this.available -= take; this.available -= take;
width -= take; width -= take;

View file

@ -30,7 +30,7 @@ import java.io.OutputStream;
/** /**
* This is a big endian bit writer. It writes its bits to an OutputStream. * 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 { public class BitOutputStream implements BitWriter {
@ -85,25 +85,25 @@ public class BitOutputStream implements BitWriter {
} }
/** /**
* 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. * unfinished byte. The underlying OutputStream will be flushed.
* *
* @param factor * @param width
* The size of the block to pad. This will typically be 8, 16, * The size of the block to pad in bits.
* 32, 64, 128, 256, etc. * This will typically be 8, 16, 32, 64, 128, 256, etc.
* @return this
* @throws IOException * @throws IOException
*/ */
public void pad(int factor) throws IOException { public void pad(int width) throws IOException {
int padding = factor - (int) (nrBits % factor); int gap = (int)this.nrBits % width;
int excess = padding & 7; if (gap < 0) {
if (excess > 0) { gap += width;
this.write(0, excess);
padding -= excess;
} }
while (padding > 0) { if (gap != 0) {
this.write(0, 8); int padding = width - gap;
padding -= 8; while (padding > 0) {
this.zero();
padding -= 1;
}
} }
this.out.flush(); this.out.flush();
} }
@ -130,7 +130,7 @@ public class BitOutputStream implements BitWriter {
actual = this.vacant; actual = this.vacant;
} }
this.unwritten |= ((bits >>> (width - actual)) & this.unwritten |= ((bits >>> (width - actual)) &
BitInputStream.mask[actual]) << (this.vacant - actual); ((1 << actual) - 1)) << (this.vacant - actual);
width -= actual; width -= actual;
nrBits += actual; nrBits += actual;
this.vacant -= actual; this.vacant -= actual;

View file

@ -3,6 +3,7 @@ package org.json.zip;
import java.io.IOException; import java.io.IOException;
public interface BitReader { public interface BitReader {
/** /**
* Read one bit. * Read one bit.
* *
@ -18,16 +19,16 @@ public interface BitReader {
public long nrBits(); 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 * The size in bits of the block to pad. This will typically be
* 8, 16, 32, 64, 128, 256, etc. * 8, 16, 32, 64, 128, 256, etc.
* @return true if the block was zero padded, or false if the the padding * @return true if the block was zero padded, or false if the the padding
* contained any one bits. * contained any one bits.
* @throws IOException * @throws IOException
*/ */
public boolean pad(int factor) throws IOException; public boolean pad(int width) throws IOException;
/** /**
* Read some bits. * Read some bits.

View file

@ -7,10 +7,6 @@ import java.io.IOException;
* Most IO interfaces only allow for writing at the byte level or higher. * Most IO interfaces only allow for writing at the byte level or higher.
*/ */
public interface BitWriter { public interface BitWriter {
/**
* Returns the number of bits that have been written to this bitwriter.
*/
public long nrBits();
/** /**
* Write a 1 bit. * Write a 1 bit.
@ -22,14 +18,12 @@ public interface BitWriter {
/** /**
* Pad the rest of the block with zeros and flush. * 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 * The size in bits of the block to pad. This will typically be
* 8, 16, 32, 64, 128, 256, etc. * 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 * @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. * Write some bits. Up to 32 bits can be written at a time.

View file

@ -29,7 +29,7 @@ import org.json.JSONException;
/** /**
* JSONzip is a compression scheme for JSON text. * JSONzip is a compression scheme for JSON text.
* @author JSON.org * @author JSON.org
* @version 2013-04-18 * @version 2014-05-03
*/ */
/** /**
@ -42,6 +42,9 @@ import org.json.JSONException;
* symbol is incremented by the tick method. The generate method is used to * 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 * 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. * 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 { public class Huff implements None, PostMortem {
@ -60,6 +63,11 @@ public class Huff implements None, PostMortem {
*/ */
private Symbol table; private Symbol table;
/**
* The number of characters left to learn to adapt the coding table.
*/
private int toLearn;
/** /**
* Have any weights changed since the table was last generated? * Have any weights changed since the table was last generated?
*/ */
@ -100,7 +108,7 @@ public class Huff implements None, PostMortem {
if (this.integer != that.integer || this.weight != that.weight) { if (this.integer != that.integer || this.weight != that.weight) {
return false; return false;
} }
if ((this.back != null) != (that.back != null)) { if ((this.back == null) != (that.back == null)) {
return false; return false;
} }
Symbol zero = this.zero; Symbol zero = this.zero;
@ -132,6 +140,7 @@ public class Huff implements None, PostMortem {
*/ */
public Huff(int domain) { public Huff(int domain) {
this.domain = domain; this.domain = domain;
this.toLearn = 1000000;
int length = domain * 2 - 1; int length = domain * 2 - 1;
this.symbols = new Symbol[length]; this.symbols = new Symbol[length];
@ -141,7 +150,7 @@ public class Huff implements None, PostMortem {
symbols[i] = new Symbol(i); symbols[i] = new Symbol(i);
} }
// SMake the links. // Make the links.
for (int i = domain; i < length; i += 1) { for (int i = domain; i < length; i += 1) {
symbols[i] = new Symbol(none); symbols[i] = new Symbol(none);
@ -151,8 +160,6 @@ public class Huff implements None, PostMortem {
/** /**
* Generate the encoding/decoding table. The table determines the bit * Generate the encoding/decoding table. The table determines the bit
* sequences used by the read and write methods. * sequences used by the read and write methods.
*
* @return this
*/ */
public void generate() { public void generate() {
if (!this.upToDate) { if (!this.upToDate) {
@ -176,8 +183,8 @@ public class Huff implements None, PostMortem {
head = symbol; head = symbol;
} else { } else {
// To save time, we will start the search from the previous symbol instead // We will start the search from the previous symbol instead of the head unless
// of the head unless the current symbol weights less than the previous symbol. // the current symbol weights less than the previous symbol.
if (symbol.weight < previous.weight) { if (symbol.weight < previous.weight) {
previous = head; previous = head;
@ -290,7 +297,7 @@ public class Huff implements None, PostMortem {
public boolean postMortem(PostMortem pm) { public boolean postMortem(PostMortem pm) {
// Go through every integer in the domain, generating its bit sequence, and // 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) { for (int integer = 0; integer < this.domain; integer += 1) {
if (!postMortem(integer)) { if (!postMortem(integer)) {
@ -330,29 +337,16 @@ public class Huff implements None, PostMortem {
} }
/** /**
* Increase by 1 the weight associated with a value. * Increase the weight associated with a value by 1.
* *
* @param value * @param value
* The number of the symbol to tick * The number of the symbol to tick
* @return this
*/ */
public void tick(int value) { public void tick(int value) {
this.symbols[value].weight += 1; if (this.toLearn > 0) {
this.upToDate = false; this.toLearn -= 1;
} 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);
} }
} }
@ -392,7 +386,6 @@ public class Huff implements None, PostMortem {
* The number of the symbol to write * The number of the symbol to write
* @param bitwriter * @param bitwriter
* The destination of the bits. * The destination of the bits.
* @return this
* @throws JSONException * @throws JSONException
*/ */
public void write(int value, BitWriter bitwriter) throws JSONException { public void write(int value, BitWriter bitwriter) throws JSONException {

View file

@ -28,8 +28,8 @@ package org.json.zip;
* JSONzip is a binary-encoded JSON dialect. It is designed to compress the * JSONzip is a binary-encoded JSON dialect. It is designed to compress the
* messages in a session in bandwidth constrained applications, such as mobile. * messages in a session in bandwidth constrained applications, such as mobile.
* *
* JSONzip is adaptive, so with each message seen, it should * JSONzip is adaptive, so with each message seen, it should improve its
* improve its compression. It minimizes JSON's overhead, reducing punctuation * compression. It minimizes JSON's overhead, reducing punctuation
* to a small number of bits. It uses Huffman encoding to reduce the average * 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 * size of characters. It uses caches (or Keeps) to keep recently seen strings
* and values, so repetitive content (such as object keys) can be * and values, so repetitive content (such as object keys) can be
@ -44,17 +44,9 @@ package org.json.zip;
* ADEQUATELY FOR PRODUCTION USE. * ADEQUATELY FOR PRODUCTION USE.
* *
* @author JSON.org * @author JSON.org
* @version 2014-04-28 * @version 2014-05-03
*/ */
public abstract class JSONzip implements None, PostMortem { 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. * The characters in JSON numbers can be reduced to 4 bits each.
*/ */
@ -87,28 +79,11 @@ public abstract class JSONzip implements None, PostMortem {
*/ */
public static final int endOfNumber = bcd.length; public static final int endOfNumber = bcd.length;
/**
* The maximum substring length when registering many. The registration of
* one substring may be longer.
*/
public static final int maxSubstringLength = 10;
/**
* The minimum substring length.
*/
public static final int minSubstringLength = 3;
/** /**
* The package supports tracing for debugging. * The package supports tracing for debugging.
*/ */
public static final boolean probe = false; public static final boolean probe = false;
/**
* The maximum number of substrings added to the substrings keep per
* string.
*/
public static final int substringLimit = 40;
/** /**
* The value code for an empty object. * The value code for an empty object.
*/ */
@ -155,62 +130,55 @@ public abstract class JSONzip implements None, PostMortem {
protected final Huff namehuff; 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. * 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. * A place to keep the strings.
*/ */
protected final TrieKeep substringkeep; protected final Keep stringkeep;
/** /**
* A place to keep the values. * A place to keep the values.
*/ */
protected final MapKeep values; protected final Keep valuekeep;
/** /**
* Initialize the data structures. * Initialize the data structures.
*/ */
protected JSONzip() { protected JSONzip() {
this.namehuff = new Huff(end + 1); this.namehuff = new Huff(end + 1);
this.namekeep = new MapKeep(9); this.namehuffext = new Huff(end + 1);
this.stringkeep = new MapKeep(11); this.namekeep = new Keep(9);
this.substringhuff = new Huff(end + 1); this.stringhuff = new Huff(end + 1);
this.substringkeep = new TrieKeep(12); this.stringhuffext = new Huff(end + 1);
this.values = new MapKeep(10); this.stringkeep = new Keep(11);
this.valuekeep = new Keep(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);
} }
/** /**
* * Generate the Huffman tables.
*/ */
protected void begin() { protected void generate() {
this.namehuff.generate(); this.namehuff.generate();
this.substringhuff.generate(); this.stringhuff.generate();
this.stringhuffext.generate();
} }
/** /**
@ -223,7 +191,7 @@ public abstract class JSONzip implements None, PostMortem {
/** /**
* Write an integer to the console. * Write an integer to the console.
* *
* @param integer * @param integer The integer to write to the log.
*/ */
static void log(int integer) { static void log(int integer) {
log(integer + " "); log(integer + " ");
@ -233,8 +201,8 @@ public abstract class JSONzip implements None, PostMortem {
* Write two integers, separated by ':' to the console. * Write two integers, separated by ':' to the console.
* The second integer is suppressed if it is 1. * The second integer is suppressed if it is 1.
* *
* @param integer * @param integer The integer to write to the log.
* @param width * @param width The width of the integer in bits.
*/ */
static void log(int integer, int width) { static void log(int integer, int width) {
if (width == 1) { if (width == 1) {
@ -247,7 +215,7 @@ public abstract class JSONzip implements None, PostMortem {
/** /**
* Write a string to the console. * Write a string to the console.
* *
* @param string * @param string The string to be written to the log.
*/ */
static void log(String string) { static void log(String string) {
System.out.print(string); System.out.print(string);
@ -256,8 +224,8 @@ public abstract class JSONzip implements None, PostMortem {
/** /**
* Write a character or its code to the console. * Write a character or its code to the console.
* *
* @param integer * @param integer The charcode to be written to the log.
* @param width * @param width The width of the charcode in bits.
*/ */
static void logchar(int integer, int width) { static void logchar(int integer, int width) {
if (integer > ' ' && integer <= '}') { if (integer > ' ' && integer <= '}') {
@ -280,8 +248,7 @@ public abstract class JSONzip implements None, PostMortem {
return this.namehuff.postMortem(that.namehuff) return this.namehuff.postMortem(that.namehuff)
&& this.namekeep.postMortem(that.namekeep) && this.namekeep.postMortem(that.namekeep)
&& this.stringkeep.postMortem(that.stringkeep) && this.stringkeep.postMortem(that.stringkeep)
&& this.substringhuff.postMortem(that.substringhuff) && this.stringhuff.postMortem(that.stringhuff)
&& this.substringkeep.postMortem(that.substringkeep) && this.valuekeep.postMortem(that.valuekeep);
&& this.values.postMortem(that.values);
} }
} }

View file

@ -1,5 +1,8 @@
package org.json.zip; package org.json.zip;
import java.util.HashMap;
import org.json.Kim;
/* /*
Copyright (c) 2013 JSON.org Copyright (c) 2013 JSON.org
@ -30,30 +33,34 @@ package org.json.zip;
* numbers. This allows the sending of small integers instead of strings. * numbers. This allows the sending of small integers instead of strings.
* *
* @author JSON.org * @author JSON.org
* @version 2013-04-18 * @version 2013-05-03
*/ */
abstract class Keep implements None, PostMortem { class Keep implements None, PostMortem {
protected int capacity; private int capacity;
protected int length; protected int length;
protected int power; private Object[] list;
protected long[] uses; private HashMap<Object, Integer> map;
private int power;
private long[] ticks;
public Keep(int bits) { public Keep(int bits) {
this.capacity = JSONzip.twos[bits]; this.capacity = 1 << bits;
this.length = 0; this.length = 0;
this.power = 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<Object, Integer>(this.capacity);
}
/** /**
* When an item ages, its use count is reduced by at least half. * When an item ages, its use count is reduced by at least half.
* *
* @param use * @param ticks
* The current use count of an item. * The current use count of an item.
* @return The new use count for that item. * @return The new use count for that item.
*/ */
public static long age(long use) { public static long age(long ticks) {
return use >= 32 ? 16 : use / 2; return ticks >= 32 ? 16 : ticks / 2;
} }
/** /**
@ -62,7 +69,7 @@ abstract class Keep implements None, PostMortem {
* required to identify one of its items goes up. * required to identify one of its items goes up.
*/ */
public int bitsize() { public int bitsize() {
while (JSONzip.twos[this.power] < this.length) { while (1 << this.power < this.length) {
this.power += 1; this.power += 1;
} }
return this.power; return this.power;
@ -72,13 +79,113 @@ abstract class Keep implements None, PostMortem {
* Increase the usage count on an integer value. * Increase the usage count on an integer value.
*/ */
public void tick(int integer) { public void tick(int integer) {
this.uses[integer] += 1; this.ticks[integer] += 1;
} }
/** /**
* Get the value associated with an integer. * Compact the keep. A keep may contain at most this.capacity elements.
* The keep contents can be reduced by deleting all elements with low use
* counts, and by reducing the use counts of the survivors.
*/
private void compact() {
int from = 0;
int to = 0;
while (from < this.capacity) {
Object key = this.list[from];
long usage = age(this.ticks[from]);
if (usage > 0) {
this.ticks[to] = usage;
this.list[to] = key;
this.map.put(key, to);
to += 1;
} else {
this.map.remove(key);
}
from += 1;
}
if (to < this.capacity) {
this.length = to;
} else {
this.map.clear();
this.length = 0;
}
this.power = 0;
}
/**
* Find the integer value associated with this key, or nothing if this key
* is not in the keep.
*
* @param key
* An object.
* @return An integer
*/
public int find(Object key) {
Object o = this.map.get(key);
return o instanceof Integer ? ((Integer) o).intValue() : none;
}
public boolean postMortem(PostMortem pm) {
Keep that = (Keep) pm;
if (this.length != that.length) {
JSONzip.log(this.length + " <> " + that.length);
return false;
}
for (int i = 0; i < this.length; i += 1) {
boolean b;
if (this.list[i] instanceof Kim) {
b = this.list[i].equals(that.list[i]);
} else {
Object o = this.list[i];
Object q = that.list[i];
if (o instanceof Number) {
o = o.toString();
}
if (q instanceof Number) {
q = q.toString();
}
b = o.equals(q);
}
if (!b) {
JSONzip.log("\n[" + i + "]\n " + this.list[i] + "\n "
+ that.list[i] + "\n " + this.ticks[i] + "\n "
+ that.ticks[i]);
return false;
}
}
return true;
}
/**
* Register a value in the keep. Compact the keep if it is full. The next
* time this value is encountered, its integer can be sent instead.
* @param value A value.
*/
public void register(Object value) {
if (JSONzip.probe) {
int integer = find(value);
if (integer >= 0) {
JSONzip.log("\nDuplicate key " + value);
}
}
if (this.length >= this.capacity) {
compact();
}
this.list[this.length] = value;
this.map.put(value, this.length);
this.ticks[this.length] = 1;
if (JSONzip.probe) {
JSONzip.log("<" + this.length + " " + value + "> ");
}
this.length += 1;
}
/**
* Return the value associated with the integer.
* @param integer The number of an item in the keep. * @param integer The number of an item in the keep.
* @return The value. * @return The value.
*/ */
abstract public Object value(int integer); public Object value(int integer) {
return this.list[integer];
}
} }

View file

@ -29,8 +29,8 @@ package org.json.zip;
* processors. Testing that JSONzip can compress an object and reproduce a * processors. Testing that JSONzip can compress an object and reproduce a
* corresponding object is not sufficient. Complete testing requires that the * corresponding object is not sufficient. Complete testing requires that the
* same internal data structures were constructed on both ends. If those * same internal data structures were constructed on both ends. If those
* structures are not equivalent, then it is likely that the implementations * structures are not exactly equivalent, then it is likely that the
* are not correct, even if convention tests are passed. * implementations are not correct, even if conventional tests are passed.
* *
* PostMortem allows for testing of deep structures without breaking * PostMortem allows for testing of deep structures without breaking
* encapsulation. * encapsulation.

View file

@ -1,7 +1,5 @@
package org.json.zip; package org.json.zip;
import java.io.UnsupportedEncodingException;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -32,27 +30,27 @@ import org.json.Kim;
*/ */
/** /**
* JSONzip is a compression scheme for JSON text. * JSONzip is a binary compression scheme for JSON text.
* *
* @author JSON.org * @author JSON.org
* @version 2014-04-28 * @version 2014-05-03
*/ */
public class Decompressor extends JSONzip { public class Unzipper extends JSONzip {
/** /**
* A decompressor reads bits from a BitReader. * A decoder reads bits from a BitReader.
*/ */
BitReader bitreader; 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. * subsession.
* *
* @param bitreader * @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(); super();
this.bitreader = bitreader; this.bitreader = bitreader;
} }
@ -81,9 +79,9 @@ public class Decompressor extends JSONzip {
* Read enough bits to obtain an integer from the keep, and increase that * Read enough bits to obtain an integer from the keep, and increase that
* integer's weight. * integer's weight.
* *
* @param keep * @param keep The keep providing the context.
* @param bitreader * @param bitreader The bitreader that is the source of bits.
* @return * @return The value associated with the number.
* @throws JSONException * @throws JSONException
*/ */
private Object getAndTick(Keep keep, BitReader bitreader) private Object getAndTick(Keep keep, BitReader bitreader)
@ -110,13 +108,13 @@ public class Decompressor extends JSONzip {
* The pad method skips the bits that padded a stream to fit some * The pad method skips the bits that padded a stream to fit some
* allocation. pad(8) will skip over the remainder of a byte. * 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. * @return true if all of the padding bits were zero.
* @throws JSONException * @throws JSONException
*/ */
public boolean pad(int factor) throws JSONException { public boolean pad(int width) throws JSONException {
try { try {
return this.bitreader.pad(factor); return this.bitreader.pad(width);
} catch (Throwable e) { } catch (Throwable e) {
throw new JSONException(e); throw new JSONException(e);
} }
@ -142,28 +140,76 @@ public class Decompressor extends JSONzip {
} }
} }
/**
* 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. * Read a JSONArray.
* *
* @param stringy * @param stringy
* true if the first element is a string. * true if the first element is a string.
* @return
* @throws JSONException * @throws JSONException
*/ */
private JSONArray readArray(boolean stringy) throws JSONException { private JSONArray readArray(boolean stringy) throws JSONException {
JSONArray jsonarray = new JSONArray(); JSONArray jsonarray = new JSONArray();
jsonarray.put(stringy ? readString() : readValue()); jsonarray.put(stringy
? read(this.stringhuff, this.stringhuffext, this.stringkeep)
: readValue());
while (true) { while (true) {
if (probe) { if (probe) {
log("\n"); log();
} }
if (!bit()) { if (!bit()) {
if (!bit()) { if (!bit()) {
return jsonarray; return jsonarray;
} }
jsonarray.put(stringy ? readValue() : readString()); jsonarray.put(stringy
? readValue()
: read(this.stringhuff, this.stringhuffext,
this.stringkeep));
} else { } else {
jsonarray.put(stringy ? readString() : readValue()); jsonarray.put(stringy
? read(this.stringhuff, this.stringhuffext,
this.stringkeep)
: readValue());
} }
} }
} }
@ -171,7 +217,7 @@ public class Decompressor extends JSONzip {
/** /**
* Read a JSON value. The type of value is determined by the next 3 bits. * Read a JSON value. The type of value is determined by the next 3 bits.
* *
* @return * @return The read value.
* @throws JSONException * @throws JSONException
*/ */
private Object readJSON() throws JSONException { private Object readJSON() throws JSONException {
@ -195,96 +241,25 @@ public class Decompressor extends JSONzip {
} }
} }
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 { private JSONObject readObject() throws JSONException {
JSONObject jsonobject = new JSONObject(); JSONObject jsonobject = new JSONObject();
while (true) { while (true) {
if (probe) { if (probe) {
log(); log();
} }
String name = readName(); String name = read(this.namehuff, this.namehuffext, this.namekeep);
jsonobject.put(name, !bit() ? readString() : readValue()); if (jsonobject.opt(name) != null) {
throw new JSONException("Duplicate key.");
}
jsonobject.put(name, !bit()
? read(this.stringhuff, this.stringhuffext, this.stringkeep)
: readValue());
if (!bit()) { if (!bit()) {
return jsonobject; 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 { private Object readValue() throws JSONException {
switch (read(2)) { switch (read(2)) {
case 0: case 0:
@ -298,7 +273,7 @@ public class Decompressor extends JSONzip {
integer += int7; integer += int7;
break; break;
} }
return new Integer(integer); return integer;
case 1: case 1:
byte[] bytes = new byte[256]; byte[] bytes = new byte[256];
int length = 0; int length = 0;
@ -314,13 +289,13 @@ public class Decompressor extends JSONzip {
try { try {
value = JSONObject.stringToValue(new String(bytes, 0, length, value = JSONObject.stringToValue(new String(bytes, 0, length,
"US-ASCII")); "US-ASCII"));
} catch (UnsupportedEncodingException e) { } catch (java.io.UnsupportedEncodingException e) {
throw new JSONException(e); throw new JSONException(e);
} }
this.values.register(value); this.valuekeep.register(value);
return value; return value;
case 2: case 2:
return getAndTick(this.values, this.bitreader); return getAndTick(this.valuekeep, this.bitreader);
case 3: case 3:
return readJSON(); return readJSON();
default: default:
@ -328,8 +303,8 @@ public class Decompressor extends JSONzip {
} }
} }
public Object unzip() throws JSONException { public Object decode() throws JSONException {
begin(); generate();
return readJSON(); return readJSON();
} }
} }

View file

@ -1,6 +1,5 @@
package org.json.zip; package org.json.zip;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@ -35,36 +34,36 @@ import org.json.Kim;
*/ */
/** /**
* JSONzip is a compression scheme for JSON text. * JSONzip is a binary compression scheme for JSON text.
* *
* @author JSON.org * @author JSON.org
* @version 2014-04-28 * @version 2014-05-03
*/ */
/** /**
* A compressor implements the compression behavior of JSONzip. It provides a * An encoder implements the compression behavior of JSONzip. It provides a
* zip method that takes a JSONObject or JSONArray and delivers a stream of * zip method that takes a JSONObject or JSONArray and delivers a stream of
* bits to a BitWriter. * bits to a BitWriter.
* *
* FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY * FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY
* FOR PRODUCTION USE. * 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; 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. * subsession.
* *
* @param bitwriter * @param bitwriter
* The BitWriter this Compressor will output to. Don't forget to * The BitWriter this encoder will output to.
* flush. * Don't forget to flush.
*/ */
public Compressor(BitWriter bitwriter) { public Zipper(BitWriter bitwriter) {
super(); super();
this.bitwriter = bitwriter; this.bitwriter = bitwriter;
} }
@ -76,7 +75,7 @@ public class Compressor extends JSONzip {
* *
* @param digit * @param digit
* An ASCII character from a JSON number. * An ASCII character from a JSON number.
* @return * @return The number code.
*/ */
private static int bcd(char digit) { private static int bcd(char digit) {
if (digit >= '0' && digit <= '9') { if (digit >= '0' && digit <= '9') {
@ -107,7 +106,7 @@ public class Compressor extends JSONzip {
/** /**
* Output a one bit. * Output a one bit.
* *
* @throws IOException * @throws JSONException
*/ */
private void one() throws JSONException { private void one() throws JSONException {
write(1, 1); write(1, 1);
@ -116,15 +115,15 @@ public class Compressor extends JSONzip {
/** /**
* Pad the output to fill an allotment of bits. * 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 * 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 * flush the current byte. If you don't pad, then some of the
* last bits might not be sent to the Output Stream. * last bits might not be sent to the Output Stream.
* @throws JSONException * @throws JSONException
*/ */
public void pad(int factor) throws JSONException { public void pad(int width) throws JSONException {
try { try {
this.bitwriter.pad(factor); this.bitwriter.pad(width);
} catch (Throwable e) { } catch (Throwable e) {
throw new JSONException(e); throw new JSONException(e);
} }
@ -171,29 +170,19 @@ public class Compressor extends JSONzip {
* A kim containing the bytes to be written. * A kim containing the bytes to be written.
* @param huff * @param huff
* The Huffman encoder. * The Huffman encoder.
* @param ext
* The Huffman encoder for the extended bytes.
* @throws JSONException * @throws JSONException
*/ */
private void write(Kim kim, Huff huff) throws JSONException { private void write(Kim kim, Huff huff, Huff ext) throws JSONException {
write(kim, 0, kim.length, huff); for (int at = 0; at < kim.length; at += 1) {
} int c = kim.get(at);
write(c, huff);
/** while ((c & 128) == 128) {
* Write a range of bytes from a Kim with Huffman encoding. at += 1;
* c = kim.get(at);
* @param kim write(c, ext);
* 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);
} }
} }
@ -207,7 +196,7 @@ public class Compressor extends JSONzip {
* The Keep that the integer is one of. * The Keep that the integer is one of.
* @throws JSONException * @throws JSONException
*/ */
private void writeAndTick(int integer, Keep keep) throws JSONException { private void write(int integer, Keep keep) throws JSONException {
int width = keep.bitsize(); int width = keep.bitsize();
keep.tick(integer); keep.tick(integer);
if (probe) { if (probe) {
@ -219,10 +208,10 @@ public class Compressor extends JSONzip {
/** /**
* Write a JSON Array. * Write a JSON Array.
* *
* @param jsonarray * @param jsonarray The JSONArray to write.
* @throws JSONException * @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: // JSONzip has three encodings for arrays:
// The array is empty (zipEmptyArray). // The array is empty (zipEmptyArray).
@ -295,9 +284,9 @@ public class Compressor extends JSONzip {
value = new JSONArray(value); value = new JSONArray(value);
} }
if (value instanceof JSONObject) { if (value instanceof JSONObject) {
writeObject((JSONObject) value); write((JSONObject) value);
} else if (value instanceof JSONArray) { } else if (value instanceof JSONArray) {
writeArray((JSONArray) value); write((JSONArray) value);
} else { } else {
throw new JSONException("Unrecognized object"); throw new JSONException("Unrecognized object");
} }
@ -308,7 +297,7 @@ public class Compressor extends JSONzip {
* Write the name of an object property. Names have their own Keep and * 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. * Huffman encoder because they are expected to be a more restricted set.
* *
* @param name * @param name The name string.
* @throws JSONException * @throws JSONException
*/ */
private void writeName(String name) throws JSONException { private void writeName(String name) throws JSONException {
@ -320,13 +309,13 @@ public class Compressor extends JSONzip {
int integer = this.namekeep.find(kim); int integer = this.namekeep.find(kim);
if (integer != none) { if (integer != none) {
one(); one();
writeAndTick(integer, this.namekeep); write(integer, this.namekeep);
} else { } else {
// Otherwise, emit the string with Huffman encoding, and register it. // Otherwise, emit the string with Huffman encoding, and register it.
zero(); zero();
write(kim, this.namehuff); write(kim, this.namehuff, this.namehuffext);
write(end, namehuff); write(end, namehuff);
this.namekeep.register(kim); this.namekeep.register(kim);
} }
@ -335,17 +324,16 @@ public class Compressor extends JSONzip {
/** /**
* Write a JSON object. * Write a JSON object.
* *
* @param jsonobject * @param jsonobject The JSONObject to be written.
* @return
* @throws JSONException * @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 // JSONzip has two encodings for objects: Empty Objects (zipEmptyObject) and
// non-empty objects (zipObject). // non-empty objects (zipObject).
boolean first = true; boolean first = true;
Iterator keys = jsonobject.keys(); Iterator<String> keys = jsonobject.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
if (probe) { if (probe) {
log(); log();
@ -379,7 +367,7 @@ public class Compressor extends JSONzip {
/** /**
* Write a string. * Write a string.
* *
* @param string * @param string The string to write.
* @throws JSONException * @throws JSONException
*/ */
private void writeString(String string) throws JSONException { private void writeString(String string) throws JSONException {
@ -388,9 +376,7 @@ public class Compressor extends JSONzip {
if (string.length() == 0) { if (string.length() == 0) {
zero(); zero();
zero(); write(end, this.stringhuff);
write(end, this.substringhuff);
zero();
} else { } else {
Kim kim = new Kim(string); Kim kim = new Kim(string);
@ -400,92 +386,20 @@ public class Compressor extends JSONzip {
int integer = this.stringkeep.find(kim); int integer = this.stringkeep.find(kim);
if (integer != none) { if (integer != none) {
one(); one();
writeAndTick(integer, this.stringkeep); write(integer, this.stringkeep);
} else { } else {
// But if it is not found, emit the string's substrings. Register the string // But if it is not found, emit the string's characters. Register the string
// so that the next lookup will succeed. // so that a later lookup can succeed.
writeSubstring(kim); zero();
write(kim, this.stringhuff, this.stringhuffext);
write(end, this.stringhuff);
this.stringkeep.register(kim); this.stringkeep.register(kim);
} }
} }
} }
/**
* Write a string, attempting to match registered substrings.
*
* @param kim
* @throws JSONException
*/
private void writeSubstring(Kim kim) throws JSONException {
this.substringkeep.reserve();
zero();
int from = 0;
int thru = kim.length;
int until = thru - JSONzip.minSubstringLength;
int previousFrom = none;
int previousThru = 0;
// Find a substring from the substring keep.
while (true) {
int at;
int integer = none;
for (at = from; at <= until; at += 1) {
integer = this.substringkeep.match(kim, at, thru);
if (integer != none) {
break;
}
}
if (integer == none) {
break;
}
// If a substring is found, emit any characters that were before the matched
// substring. Then emit the substring's integer and loop back to match the
// remainder with another substring.
if (from != at) {
zero();
write(kim, from, at, this.substringhuff);
write(end, this.substringhuff);
if (previousFrom != none) {
this.substringkeep.registerOne(kim, previousFrom,
previousThru);
previousFrom = none;
}
}
one();
writeAndTick(integer, this.substringkeep);
from = at + this.substringkeep.length(integer);
if (previousFrom != none) {
this.substringkeep.registerOne(kim, previousFrom,
previousThru);
previousFrom = none;
}
previousFrom = at;
previousThru = from + 1;
}
// If a substring is not found, then emit the remaining characters.
zero();
if (from < thru) {
write(kim, from, thru, this.substringhuff);
if (previousFrom != none) {
this.substringkeep.registerOne(kim, previousFrom, previousThru);
}
}
write(end, this.substringhuff);
zero();
// Register the string's substrings in the trie in hopes of future substring
// matching.
substringkeep.registerMany(kim);
}
/** /**
* Write a value. * Write a value.
* *
@ -496,10 +410,10 @@ public class Compressor extends JSONzip {
private void writeValue(Object value) throws JSONException { private void writeValue(Object value) throws JSONException {
if (value instanceof Number) { if (value instanceof Number) {
String string = JSONObject.numberToString((Number) value); String string = JSONObject.numberToString((Number) value);
int integer = this.values.find(string); int integer = this.valuekeep.find(string);
if (integer != none) { if (integer != none) {
write(2, 2); write(2, 2);
writeAndTick(integer, this.values); write(integer, this.valuekeep);
return; return;
} }
if (value instanceof Integer || value instanceof Long) { if (value instanceof Integer || value instanceof Long) {
@ -527,7 +441,7 @@ public class Compressor extends JSONzip {
write(bcd(string.charAt(i)), 4); write(bcd(string.charAt(i)), 4);
} }
write(endOfNumber, 4); write(endOfNumber, 4);
this.values.register(string); this.valuekeep.register(string);
} else { } else {
write(3, 2); write(3, 2);
writeJSON(value); writeJSON(value);
@ -538,32 +452,30 @@ public class Compressor extends JSONzip {
* Output a zero bit. * Output a zero bit.
* *
* @throws JSONException * @throws JSONException
*
* @throws IOException
*/ */
private void zero() throws JSONException { private void zero() throws JSONException {
write(0, 1); write(0, 1);
} }
/** /**
* Compress a JSONObject. * Encode a JSONObject.
* *
* @param jsonobject * @param jsonobject The JSONObject.
* @throws JSONException * @throws JSONException
*/ */
public void zip(JSONObject jsonobject) throws JSONException { public void encode(JSONObject jsonobject) throws JSONException {
begin(); generate();
writeJSON(jsonobject); writeJSON(jsonobject);
} }
/** /**
* Compress a JSONArray. * Encode a JSONArray.
* *
* @param jsonarray * @param jsonarray The JSONArray.
* @throws JSONException * @throws JSONException
*/ */
public void zip(JSONArray jsonarray) throws JSONException { public void encode(JSONArray jsonarray) throws JSONException {
begin(); generate();
writeJSON(jsonarray); writeJSON(jsonarray);
} }
} }