You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2014/07/23 17:24:19 UTC
svn commit: r1612850 - in /sling/trunk/bundles/commons/json/src:
main/java/org/apache/sling/commons/json/
main/java/org/apache/sling/commons/json/io/
test/java/org/apache/sling/commons/json/io/
Author: bdelacretaz
Date: Wed Jul 23 15:24:18 2014
New Revision: 1612850
URL: http://svn.apache.org/r1612850
Log:
SLING-3786 - extract JSON formatting code to JSONRenderer and add some missing tests. All unit and related integration tests pass
Added:
sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/io/JSONRenderer.java
sling/trunk/bundles/commons/json/src/test/java/org/apache/sling/commons/json/io/
sling/trunk/bundles/commons/json/src/test/java/org/apache/sling/commons/json/io/JSONRendererTest.java
Modified:
sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONArray.java
sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONObject.java
Modified: sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONArray.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONArray.java?rev=1612850&r1=1612849&r2=1612850&view=diff
==============================================================================
--- sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONArray.java (original)
+++ sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONArray.java Wed Jul 23 15:24:18 2014
@@ -24,12 +24,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWAR
SOFTWARE.
*/
-import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
+import org.apache.sling.commons.json.io.JSONRenderer;
+
/**
* A JSONArray is an ordered sequence of values. Its external text form is a
* string wrapped in square brackets with commas separating the values. The
@@ -82,6 +83,8 @@ import java.util.Map;
*/
public class JSONArray {
+ /** Default renderer */
+ private static final JSONRenderer renderer = new JSONRenderer();
/**
* The arrayList where the JSONArray's properties are kept.
@@ -320,24 +323,11 @@ public class JSONArray {
/**
- * Make a string from the contents of this JSONArray. The
- * <code>separator</code> string is inserted between each element.
- * Warning: This method assumes that the data structure is acyclical.
- * @param separator A string that will be inserted between the elements.
- * @return a string.
- * @throws JSONException If the array contains an invalid number.
+ * Make a string from the contents of this JSONArray
+ * using {@link JSONRenderer#join}
*/
public String join(String separator) throws JSONException {
- int len = length();
- StringBuffer sb = new StringBuffer();
-
- for (int i = 0; i < len; i += 1) {
- if (i > 0) {
- sb.append(separator);
- }
- sb.append(JSONObject.valueToString(this.myArrayList.get(i)));
- }
- return sb.toString();
+ return renderer.join(this, separator);
}
@@ -774,25 +764,13 @@ public class JSONArray {
/**
- * Make a JSON text of this JSONArray. For compactness, no
- * unnecessary whitespace is added. If it is not possible to produce a
- * syntactically correct JSON text then null will be returned instead. This
- * could occur if the array contains an invalid number.
- * <p>
- * Warning: This method assumes that the data structure is acyclical.
- *
- * @return a printable, displayable, transmittable
- * representation of the array.
+ * Make a JSON text of this JSONArray using
+ * {@link JSONRenderer#toString(JSONArray)}
*/
public String toString() {
- try {
- return '[' + join(",") + ']';
- } catch (Exception e) {
- return null;
- }
+ return renderer.toString(this);
}
-
/**
* Make a prettyprinted JSON text of this JSONArray.
* Warning: This method assumes that the data structure is acyclical.
@@ -820,72 +798,15 @@ public class JSONArray {
* @throws JSONException
*/
String toString(int indentFactor, int indent) throws JSONException {
- int len = length();
- if (len == 0) {
- return "[]";
- }
- int i;
- StringBuffer sb = new StringBuffer("[");
- if (len == 1) {
- sb.append(JSONObject.valueToString(this.myArrayList.get(0),
- indentFactor, indent));
- } else {
- int newindent = indent + indentFactor;
- sb.append('\n');
- for (i = 0; i < len; i += 1) {
- if (i > 0) {
- sb.append(",\n");
- }
- for (int j = 0; j < newindent; j += 1) {
- sb.append(' ');
- }
- sb.append(JSONObject.valueToString(this.myArrayList.get(i),
- indentFactor, newindent));
- }
- sb.append('\n');
- for (i = 0; i < indent; i += 1) {
- sb.append(' ');
- }
- }
- sb.append(']');
- return sb.toString();
+ return renderer.prettyPrint(this,
+ renderer.options().withIndent(indentFactor).withInitialIndent(indent));
}
-
/**
- * Write the contents of the JSONArray as JSON text to a writer.
- * For compactness, no whitespace is added.
- * <p>
- * Warning: This method assumes that the data structure is acyclical.
- *
- * @return The writer.
- * @throws JSONException
+ * Write the contents of the JSONObject as JSON text to a writer
+ * using {@link JSONRenderer#write(JSONArray)}
*/
public Writer write(Writer writer) throws JSONException {
- try {
- boolean b = false;
- int len = length();
-
- writer.write('[');
-
- for (int i = 0; i < len; i += 1) {
- if (b) {
- writer.write(',');
- }
- Object v = this.myArrayList.get(i);
- if (v instanceof JSONObject) {
- ((JSONObject)v).write(writer);
- } else if (v instanceof JSONArray) {
- ((JSONArray)v).write(writer);
- } else {
- writer.write(JSONObject.valueToString(v));
- }
- b = true;
- }
- writer.write(']');
- return writer;
- } catch (IOException e) {
- throw new JSONException(e);
- }
+ return renderer.write(writer, this);
}
}
\ No newline at end of file
Modified: sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONObject.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONObject.java?rev=1612850&r1=1612849&r2=1612850&view=diff
==============================================================================
--- sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONObject.java (original)
+++ sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/JSONObject.java Wed Jul 23 15:24:18 2014
@@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWAR
SOFTWARE.
*/
-import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.Collection;
@@ -32,6 +31,8 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.apache.sling.commons.json.io.JSONRenderer;
+
/**
* A JSONObject is an unordered collection of name/value pairs. Its
* external form is a string wrapped in curly braces with colons between the
@@ -86,6 +87,11 @@ import java.util.Map;
* @version 2
*/
public class JSONObject {
+
+ /** A renderer with default settings, used to avoid having to create
+ * one for simple rendering operations
+ */
+ private static final JSONRenderer renderer = new JSONRenderer();
/**
* JSONObject.NULL is equivalent to the value that JavaScript calls null,
@@ -334,31 +340,12 @@ public class JSONObject {
/**
- * Produce a string from a double. The string "null" will be returned if
- * the number is not finite.
- * @param d A double.
- * @return A String.
+ * Produce a string from a double using {@link JSONRenderer#doubleToString}
*/
static public String doubleToString(double d) {
- if (Double.isInfinite(d) || Double.isNaN(d)) {
- return "null";
- }
-
- // Shave off trailing zeros and decimal point, if possible.
-
- String s = Double.toString(d);
- if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
- while (s.endsWith("0")) {
- s = s.substring(0, s.length() - 1);
- }
- if (s.endsWith(".")) {
- s = s.substring(0, s.length() - 1);
- }
- }
- return s;
+ return renderer.doubleToString(d);
}
-
/**
* Get the value object associated with a key.
*
@@ -564,25 +551,8 @@ public class JSONObject {
* @return A String.
* @throws JSONException If n is a non-finite number.
*/
- static public String numberToString(Number n)
- throws JSONException {
- if (n == null) {
- throw new JSONException("Null pointer");
- }
- testValidity(n);
-
- // Shave off trailing zeros and decimal point, if possible.
-
- String s = n.toString();
- if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
- while (s.endsWith("0")) {
- s = s.substring(0, s.length() - 1);
- }
- if (s.endsWith(".")) {
- s = s.substring(0, s.length() - 1);
- }
- }
- return s;
+ static public String numberToString(Number n) throws JSONException {
+ return renderer.numberToString(n);
}
@@ -919,60 +889,7 @@ public class JSONObject {
* @return A String correctly formatted for insertion in a JSON text.
*/
public static String quote(String string) {
- if (string == null || string.length() == 0) {
- return "\"\"";
- }
-
- char b;
- char c = 0;
- int i;
- int len = string.length();
- StringBuffer sb = new StringBuffer(len + 4);
- String t;
-
- sb.append('"');
- for (i = 0; i < len; i += 1) {
- b = c;
- c = string.charAt(i);
- switch (c) {
- case '\\':
- case '"':
- sb.append('\\');
- sb.append(c);
- break;
- case '/':
- if (b == '<') {
- sb.append('\\');
- }
- sb.append(c);
- break;
- case '\b':
- sb.append("\\b");
- break;
- case '\t':
- sb.append("\\t");
- break;
- case '\n':
- sb.append("\\n");
- break;
- case '\f':
- sb.append("\\f");
- break;
- case '\r':
- sb.append("\\r");
- break;
- default:
- if (c < ' ' || (c >= '\u0080' && c < '\u00a0') ||
- (c >= '\u2000' && c < '\u2100')) {
- t = "000" + Integer.toHexString(c);
- sb.append("\\u" + t.substring(t.length() - 4));
- } else {
- sb.append(c);
- }
- }
- }
- sb.append('"');
- return sb.toString();
+ return renderer.quote(string);
}
/**
@@ -992,22 +909,9 @@ public class JSONObject {
* @throws JSONException If o is a non-finite number.
*/
static void testValidity(Object o) throws JSONException {
- if (o != null) {
- if (o instanceof Double) {
- if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
- throw new JSONException(
- "JSON does not allow non-finite numbers");
- }
- } else if (o instanceof Float) {
- if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
- throw new JSONException(
- "JSON does not allow non-finite numbers.");
- }
- }
- }
+ renderer.testNumberValidity(o);
}
-
/**
* Produce a JSONArray containing the values of the members of this
* JSONObject.
@@ -1028,39 +932,12 @@ public class JSONObject {
}
/**
- * Make a JSON text of this JSONObject. For compactness, no whitespace
- * is added. If this would not result in a syntactically correct JSON text,
- * then null will be returned instead.
- * <p>
- * Warning: This method assumes that the data structure is acyclical.
- *
- * @return a printable, displayable, portable, transmittable
- * representation of the object, beginning
- * with <code>{</code> <small>(left brace)</small> and ending
- * with <code>}</code> <small>(right brace)</small>.
+ * Make a JSON text of this JSONObject using {@link JSONRenderer#toString}
*/
public String toString() {
- try {
- Iterator<String> keys = keys();
- StringBuffer sb = new StringBuffer("{");
-
- while (keys.hasNext()) {
- if (sb.length() > 1) {
- sb.append(',');
- }
- String o = keys.next();
- sb.append(quote(o));
- sb.append(':');
- sb.append(valueToString(this.myHashMap.get(o)));
- }
- sb.append('}');
- return sb.toString();
- } catch (Exception e) {
- return null;
- }
+ return renderer.toString(this);
}
-
/**
* Make a prettyprinted JSON text of this JSONObject.
* <p>
@@ -1079,59 +956,12 @@ public class JSONObject {
/**
- * Make a prettyprinted JSON text of this JSONObject.
- * <p>
- * Warning: This method assumes that the data structure is acyclical.
- * @param indentFactor The number of spaces to add to each level of
- * indentation.
- * @param indent The indentation of the top level.
- * @return a printable, displayable, transmittable
- * representation of the object, beginning
- * with <code>{</code> <small>(left brace)</small> and ending
- * with <code>}</code> <small>(right brace)</small>.
- * @throws JSONException If the object contains an invalid number.
+ * Make a prettyprinted JSON text of this JSONObject using
+ * {@link JSONRenderer#prettyPrint}
*/
- String toString(int indentFactor, int indent) throws JSONException {
- int i;
- int n = length();
- if (n == 0) {
- return "{}";
- }
- Iterator<String> keys = keys();
- StringBuffer sb = new StringBuffer("{");
- int newindent = indent + indentFactor;
- String o;
- if (n == 1) {
- o = keys.next();
- sb.append(quote(o));
- sb.append(": ");
- sb.append(valueToString(this.myHashMap.get(o), indentFactor,
- indent));
- } else {
- while (keys.hasNext()) {
- o = keys.next();
- if (sb.length() > 1) {
- sb.append(",\n");
- } else {
- sb.append('\n');
- }
- for (i = 0; i < newindent; i += 1) {
- sb.append(' ');
- }
- sb.append(quote(o.toString()));
- sb.append(": ");
- sb.append(valueToString(this.myHashMap.get(o), indentFactor,
- newindent));
- }
- if (sb.length() > 1) {
- sb.append('\n');
- for (i = 0; i < indent; i += 1) {
- sb.append(' ');
- }
- }
- }
- sb.append('}');
- return sb.toString();
+ String toString(int indentFactor, int initialIndent) throws JSONException {
+ return renderer.prettyPrint(this,
+ renderer.options().withIndent(indentFactor).withInitialIndent(initialIndent));
}
@@ -1152,113 +982,24 @@ public class JSONObject {
* @throws JSONException If the value is or contains an invalid number.
*/
public static String valueToString(Object value) throws JSONException {
- if (value == null || value.equals(null)) {
- return "null";
- }
- if (value instanceof JSONString) {
- Object o;
- try {
- o = ((JSONString)value).toJSONString();
- } catch (Exception e) {
- throw new JSONException(e);
- }
- if (o instanceof String) {
- return (String)o;
- }
- throw new JSONException("Bad value from toJSONString: " + o);
- }
- if (value instanceof Number) {
- return numberToString((Number) value);
- }
- if (value instanceof Boolean || value instanceof JSONObject ||
- value instanceof JSONArray) {
- return value.toString();
- }
- return quote(value.toString());
+ return renderer.valueToString(value);
}
-
/**
- * Make a prettyprinted JSON text of an object value.
- * <p>
- * Warning: This method assumes that the data structure is acyclical.
- * @param value The value to be serialized.
- * @param indentFactor The number of spaces to add to each level of
- * indentation.
- * @param indent The indentation of the top level.
- * @return a printable, displayable, transmittable
- * representation of the object, beginning
- * with <code>{</code> <small>(left brace)</small> and ending
- * with <code>}</code> <small>(right brace)</small>.
- * @throws JSONException If the object contains an invalid number.
+ * Make a prettyprinted JSON text of an object value
+ * using {@link JSONRenderer#valueToString}
*/
- static String valueToString(Object value, int indentFactor, int indent)
+ static String valueToString(Object value, int indentFactor, int initialIndent)
throws JSONException {
- if (value == null || value.equals(null)) {
- return "null";
- }
- try {
- if (value instanceof JSONString) {
- Object o = ((JSONString)value).toJSONString();
- if (o instanceof String) {
- return (String)o;
- }
- }
- } catch (Exception e) {
- /* forget about it */
- }
- if (value instanceof Number) {
- return numberToString((Number) value);
- }
- if (value instanceof Boolean) {
- return value.toString();
- }
- if (value instanceof JSONObject) {
- return ((JSONObject)value).toString(indentFactor, indent);
- }
- if (value instanceof JSONArray) {
- return ((JSONArray)value).toString(indentFactor, indent);
- }
- return quote(value.toString());
+ return renderer.valueToString(value,
+ renderer.options().withIndent(indentFactor).withInitialIndent(initialIndent));
}
-
/**
- * Write the contents of the JSONObject as JSON text to a writer.
- * For compactness, no whitespace is added.
- * <p>
- * Warning: This method assumes that the data structure is acyclical.
- *
- * @return The writer.
- * @throws JSONException
+ * Write the contents of the JSONObject as JSON text to a writer
+ * using {@link JSONRenderer#write(JSONObject)}
*/
public Writer write(Writer writer) throws JSONException {
- try {
- boolean b = false;
- Iterator<String> keys = keys();
- writer.write('{');
-
- while (keys.hasNext()) {
- if (b) {
- writer.write(',');
- }
- String k = keys.next();
- writer.write(quote(k));
- writer.write(':');
- Object v = this.myHashMap.get(k);
- if (v instanceof JSONObject) {
- ((JSONObject)v).write(writer);
- } else if (v instanceof JSONArray) {
- ((JSONArray)v).write(writer);
- } else {
- writer.write(valueToString(v));
- }
- b = true;
- }
- writer.write('}');
- return writer;
- } catch (IOException e) {
- throw new JSONException(e);
- }
+ return renderer.write(writer, this);
}
}
\ No newline at end of file
Added: sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/io/JSONRenderer.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/io/JSONRenderer.java?rev=1612850&view=auto
==============================================================================
--- sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/io/JSONRenderer.java (added)
+++ sling/trunk/bundles/commons/json/src/main/java/org/apache/sling/commons/json/io/JSONRenderer.java Wed Jul 23 15:24:18 2014
@@ -0,0 +1,513 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.commons.json.io;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Iterator;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.commons.json.JSONString;
+
+/** Various JSON-to-String primitives, used by other classes
+ * when outputting/formatting JSON.
+ *
+ * Streaming variants of some methods are provided.
+ * The existing code in this module is often not streaming, but
+ * we should write newer code using streams, as much as
+ * possible.
+ */
+public class JSONRenderer {
+
+ /** Rendering options */
+ static public class Options {
+ int indent;
+ int initialIndent;
+
+ /** Clients use JSONRenderer.options() to create objects */
+ private Options() {
+ }
+
+ public Options withIndent(int n) {
+ indent = n;
+ return this;
+ }
+
+ public Options withInitialIndent(int n) {
+ initialIndent = n;
+ return this;
+ }
+ }
+
+ /** Return an Options object with default values */
+ public Options options() {
+ return new Options();
+ }
+
+ /** Render the supplied JSONObject to a String, in
+ * the simplest possible way.
+ */
+ public String toString(JSONObject jo) {
+ try {
+ final Iterator<String> keys = jo.keys();
+ final StringBuffer sb = new StringBuffer("{");
+
+ while (keys.hasNext()) {
+ if (sb.length() > 1) {
+ sb.append(',');
+ }
+ String o = keys.next();
+ sb.append(quote(o));
+ sb.append(':');
+ sb.append(valueToString(jo.get(o)));
+ }
+ sb.append('}');
+ return sb.toString();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /** Make a JSON text of the supplied JSONArray. For compactness, no
+ * unnecessary whitespace is added. If it is not possible to produce a
+ * syntactically correct JSON text then null will be returned instead. This
+ * could occur if the array contains an invalid number.
+ * <p>Warning: This method assumes that the data structure is acyclical.
+ *
+ * @return a printable, displayable, transmittable
+ * representation of the array.
+ */
+ public String toString(JSONArray ja) {
+ try {
+ return '[' + join(ja,",") + ']';
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /** Quote the supplied string for JSON */
+ public String quote(String string) {
+ final StringWriter sw = new StringWriter();
+ try {
+ quote(sw, string);
+ } catch(IOException ioex) {
+ throw new RuntimeException("IOException in quote()", ioex);
+ }
+ return sw.toString();
+ }
+
+ /** Quote the supplied string for JSON, to the supplied Writer */
+ public void quote(Writer w, String string) throws IOException {
+ if (string == null || string.length() == 0) {
+ w.write("\"\"");
+ return;
+ }
+
+ char b;
+ char c = 0;
+ int i;
+ int len = string.length();
+ String t;
+
+ w.write('"');
+ for (i = 0; i < len; i += 1) {
+ b = c;
+ c = string.charAt(i);
+ switch (c) {
+ case '\\':
+ case '"':
+ w.write('\\');
+ w.write(c);
+ break;
+ case '/':
+ if (b == '<') {
+ w.write('\\');
+ }
+ w.write(c);
+ break;
+ case '\b':
+ w.write("\\b");
+ break;
+ case '\t':
+ w.write("\\t");
+ break;
+ case '\n':
+ w.write("\\n");
+ break;
+ case '\f':
+ w.write("\\f");
+ break;
+ case '\r':
+ w.write("\\r");
+ break;
+ default:
+ if (c < ' ' || (c >= '\u0080' && c < '\u00a0') ||
+ (c >= '\u2000' && c < '\u2100')) {
+ t = "000" + Integer.toHexString(c);
+ w.write("\\u" + t.substring(t.length() - 4));
+ } else {
+ w.write(c);
+ }
+ }
+ }
+ w.write('"');
+ }
+
+ /**
+ * Make a JSON text of an Object value. If the object has an
+ * value.toJSONString() method, then that method will be used to produce
+ * the JSON text. The method is required to produce a strictly
+ * conforming text. If the object does not contain a toJSONString
+ * method (which is the most common case), then a text will be
+ * produced by the rules.
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ * @param value The value to be serialized.
+ * @return a printable, displayable, transmittable
+ * representation of the object, beginning
+ * with <code>{</code> <small>(left brace)</small> and ending
+ * with <code>}</code> <small>(right brace)</small>.
+ * @throws JSONException If the value is or contains an invalid number.
+ */
+ public String valueToString(Object value) throws JSONException {
+ // TODO call the other valueToString instead
+ if (value == null || value.equals(null)) {
+ return "null";
+ }
+ if (value instanceof JSONString) {
+ Object o;
+ try {
+ o = ((JSONString)value).toJSONString();
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+ if (o instanceof String) {
+ return (String)o;
+ }
+ throw new JSONException("Bad value from toJSONString: " + o);
+ }
+ if (value instanceof Number) {
+ return numberToString((Number) value);
+ }
+ if (value instanceof Boolean || value instanceof JSONObject ||
+ value instanceof JSONArray) {
+ return value.toString();
+ }
+ return quote(value.toString());
+ }
+
+ /** Make a JSON String of an Object value, with rendering options
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ * @param value The value to be serialized.
+ * @param indentFactor The number of spaces to add to each level of
+ * indentation.
+ * @param indent The indentation of the top level.
+ * @return a printable, displayable, transmittable
+ * representation of the object, beginning
+ * with <code>{</code> <small>(left brace)</small> and ending
+ * with <code>}</code> <small>(right brace)</small>.
+ * @throws JSONException If the object contains an invalid number.
+ */
+ public String valueToString(Object value, Options opt) throws JSONException {
+ if (value == null || value.equals(null)) {
+ return "null";
+ }
+ try {
+ if (value instanceof JSONString) {
+ Object o = ((JSONString)value).toJSONString();
+ if (o instanceof String) {
+ return (String)o;
+ }
+ }
+ } catch (Exception e) {
+ /* forget about it */
+ }
+ if (value instanceof Number) {
+ return numberToString((Number) value);
+ }
+ if (value instanceof Boolean) {
+ return value.toString();
+ }
+ if (value instanceof JSONObject) {
+ return prettyPrint((JSONObject)value, opt);
+ }
+ if (value instanceof JSONArray) {
+ return prettyPrint((JSONArray)value, opt);
+ }
+ return quote(value.toString());
+
+ }
+
+ /**
+ * Produce a string from a Number.
+ * @param n A Number
+ * @return A String.
+ * @throws JSONException If n is a non-finite number.
+ */
+ public String numberToString(Number n)
+ throws JSONException {
+ if (n == null) {
+ throw new JSONException("Null pointer");
+ }
+ testNumberValidity(n);
+
+ // Shave off trailing zeros and decimal point, if possible.
+
+ String s = n.toString();
+ if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
+ while (s.endsWith("0")) {
+ s = s.substring(0, s.length() - 1);
+ }
+ if (s.endsWith(".")) {
+ s = s.substring(0, s.length() - 1);
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Make a prettyprinted JSON text of this JSONObject.
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ * @param indentFactor The number of spaces to add to each level of
+ * indentation.
+ * @param indent The indentation of the top level.
+ * @return a printable, displayable, transmittable
+ * representation of the object, beginning
+ * with <code>{</code> <small>(left brace)</small> and ending
+ * with <code>}</code> <small>(right brace)</small>.
+ * @throws JSONException If the object contains an invalid number.
+ */
+ public String prettyPrint(JSONObject jo, Options opt) throws JSONException {
+ int i;
+ int n = jo.length();
+ if (n == 0) {
+ return "{}";
+ }
+ Iterator<String> keys = jo.keys();
+ StringBuffer sb = new StringBuffer("{");
+ int newindent = opt.initialIndent + opt.indent;
+ String o;
+ if (n == 1) {
+ o = keys.next();
+ sb.append(quote(o));
+ sb.append(": ");
+ sb.append(valueToString(jo.get(o), opt));
+ } else {
+ while (keys.hasNext()) {
+ o = keys.next();
+ if (sb.length() > 1) {
+ sb.append(",\n");
+ } else {
+ sb.append('\n');
+ }
+ for (i = 0; i < newindent; i += 1) {
+ sb.append(' ');
+ }
+ sb.append(quote(o.toString()));
+ sb.append(": ");
+ sb.append(valueToString(jo.get(o),
+ options().withIndent(opt.indent).withInitialIndent(newindent)));
+ }
+ if (sb.length() > 1) {
+ sb.append('\n');
+ for (i = 0; i < opt.indent; i += 1) {
+ sb.append(' ');
+ }
+ }
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public String prettyPrint(JSONArray ja, Options opt) throws JSONException {
+ int len = ja.length();
+ if (len == 0) {
+ return "[]";
+ }
+ int i;
+ StringBuffer sb = new StringBuffer("[");
+ if (len == 1) {
+ sb.append(valueToString(ja.get(0), opt));
+ } else {
+ final int newindent = opt.initialIndent + opt.indent;
+ sb.append('\n');
+ for (i = 0; i < len; i += 1) {
+ if (i > 0) {
+ sb.append(",\n");
+ }
+ for (int j = 0; j < newindent; j += 1) {
+ sb.append(' ');
+ }
+ sb.append(valueToString(ja.get(i), opt));
+ }
+ sb.append('\n');
+ for (i = 0; i < opt.initialIndent; i += 1) {
+ sb.append(' ');
+ }
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ /**
+ * Throw an exception if the object is an NaN or infinite number.
+ * @param o The object to test.
+ * @throws JSONException If o is a non-finite number.
+ */
+ public void testNumberValidity(Object o) throws JSONException {
+ if (o != null) {
+ if (o instanceof Double) {
+ if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
+ throw new JSONException(
+ "JSON does not allow non-finite numbers");
+ }
+ } else if (o instanceof Float) {
+ if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
+ throw new JSONException(
+ "JSON does not allow non-finite numbers.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Make a string from the contents of this JSONArray. The
+ * <code>separator</code> string is inserted between each element.
+ * Warning: This method assumes that the data structure is acyclical.
+ * @param separator A string that will be inserted between the elements.
+ * @return a string.
+ * @throws JSONException If the array contains an invalid number.
+ */
+ public String join(JSONArray ja, String separator) throws JSONException {
+ final int len = ja.length();
+ StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; i < len; i += 1) {
+ if (i > 0) {
+ sb.append(separator);
+ }
+ sb.append(JSONObject.valueToString(ja.get(i)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Write the contents of the supplied JSONObject as JSON text to a writer.
+ * For compactness, no whitespace is added.
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @return The writer.
+ * @throws JSONException
+ */
+ public Writer write(Writer writer, JSONObject jo) throws JSONException {
+ try {
+ boolean b = false;
+ Iterator<String> keys = jo.keys();
+ writer.write('{');
+
+ while (keys.hasNext()) {
+ if (b) {
+ writer.write(',');
+ }
+ String k = keys.next();
+ writer.write(quote(k));
+ writer.write(':');
+ final Object v = jo.get(k);
+ if (v instanceof JSONObject) {
+ ((JSONObject)v).write(writer);
+ } else if (v instanceof JSONArray) {
+ ((JSONArray)v).write(writer);
+ } else {
+ writer.write(valueToString(v));
+ }
+ b = true;
+ }
+ writer.write('}');
+ return writer;
+ } catch (IOException e) {
+ throw new JSONException(e);
+ }
+ }
+
+ /**
+ * Write the contents of the supplied JSONArray as JSON text to a writer.
+ * For compactness, no whitespace is added.
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @return The writer.
+ * @throws JSONException
+ */
+ public Writer write(Writer writer, JSONArray ja) throws JSONException {
+ try {
+ boolean b = false;
+ int len = ja.length();
+
+ writer.write('[');
+
+ for (int i = 0; i < len; i += 1) {
+ if (b) {
+ writer.write(',');
+ }
+ final Object v = ja.get(i);
+ if (v instanceof JSONObject) {
+ ((JSONObject)v).write(writer);
+ } else if (v instanceof JSONArray) {
+ ((JSONArray)v).write(writer);
+ } else {
+ writer.write(JSONObject.valueToString(v));
+ }
+ b = true;
+ }
+ writer.write(']');
+ return writer;
+ } catch (IOException e) {
+ throw new JSONException(e);
+ }
+ }
+
+ /**
+ * Produce a string from a double. The string "null" will be returned if
+ * the number is not finite.
+ * @param d A double.
+ * @return A String.
+ */
+ public String doubleToString(double d) {
+ if (Double.isInfinite(d) || Double.isNaN(d)) {
+ return "null";
+ }
+
+ // Shave off trailing zeros and decimal point, if possible.
+
+ String s = Double.toString(d);
+ if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
+ while (s.endsWith("0")) {
+ s = s.substring(0, s.length() - 1);
+ }
+ if (s.endsWith(".")) {
+ s = s.substring(0, s.length() - 1);
+ }
+ }
+ return s;
+ }
+}
\ No newline at end of file
Added: sling/trunk/bundles/commons/json/src/test/java/org/apache/sling/commons/json/io/JSONRendererTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/json/src/test/java/org/apache/sling/commons/json/io/JSONRendererTest.java?rev=1612850&view=auto
==============================================================================
--- sling/trunk/bundles/commons/json/src/test/java/org/apache/sling/commons/json/io/JSONRendererTest.java (added)
+++ sling/trunk/bundles/commons/json/src/test/java/org/apache/sling/commons/json/io/JSONRendererTest.java Wed Jul 23 15:24:18 2014
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.commons.json.io;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.junit.Test;
+
+
+/** Most of the JSONRenderer code is tested indirectly
+ * via existing JSONObject and JSONArray tests - this
+ * tests what's left */
+public class JSONRendererTest {
+ private final JSONRenderer renderer = new JSONRenderer();
+
+ @Test
+ public void testA() {
+ assertEquals("12.34", renderer.doubleToString(12.34));
+ }
+
+ @Test
+ public void testB() {
+ assertEquals("123", renderer.doubleToString(123));
+ }
+
+ @Test
+ public void testC() {
+ assertEquals("null", renderer.doubleToString(Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testD() {
+ assertEquals("null", renderer.doubleToString(Double.NEGATIVE_INFINITY));
+ }
+
+ @Test
+ public void testE() {
+ assertEquals("null", renderer.doubleToString(Double.NaN));
+ }
+
+ @Test(expected=JSONException.class)
+ public void testF() throws JSONException {
+ renderer.numberToString(null);
+ }
+
+ @Test
+ public void testEmptyJSONArray() throws JSONException {
+ assertEquals("[]", renderer.prettyPrint(new JSONArray(), renderer.options()));
+ }
+
+ @Test
+ public void testSingleJSONArray() throws JSONException {
+ final JSONArray ja = new JSONArray();
+ ja.put(42);
+ assertEquals("[42]", renderer.prettyPrint(ja, renderer.options()));
+ }
+
+}
\ No newline at end of file