You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2015/09/13 11:32:03 UTC
[1/2] incubator-groovy git commit: Improve StreamingJsonBuilder
support for @CompileStatic
Repository: incubator-groovy
Updated Branches:
refs/heads/GROOVY_2_4_X aa81ad951 -> c5c0cefb3
Improve StreamingJsonBuilder support for @CompileStatic
Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/0bf2cb5d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/0bf2cb5d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/0bf2cb5d
Branch: refs/heads/GROOVY_2_4_X
Commit: 0bf2cb5d334a9ac095904d607867e4aee1807979
Parents: aa81ad9
Author: Graeme Rocher <gr...@gmail.com>
Authored: Fri Aug 21 12:45:49 2015 +0200
Committer: Cedric Champeau <cc...@apache.org>
Committed: Sun Sep 13 11:31:20 2015 +0200
----------------------------------------------------------------------
.../src/main/java/groovy/json/JsonOutput.java | 80 ++--
.../java/groovy/json/StreamingJsonBuilder.java | 413 ++++++++++++++-----
.../groovy/json/StreamingJsonBuilderTest.groovy | 41 ++
3 files changed, 404 insertions(+), 130 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0bf2cb5d/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java b/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
index d186c75..503f79c 100644
--- a/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
+++ b/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
@@ -40,11 +40,28 @@ import java.util.*;
* @author Roshan Dawrani
* @author Andrey Bloschetsov
* @author Rick Hightower
+ * @author Graeme Rocher
*
* @since 1.8.0
*/
public class JsonOutput {
+ static final char OPEN_BRACKET = '[';
+ static final char CLOSE_BRACKET = ']';
+ static final char OPEN_BRACE = '{';
+ static final char CLOSE_BRACE = '}';
+ static final char COLON = ':';
+ static final char COMMA = ',';
+ static final char SPACE = ' ';
+ static final char NEW_LINE = '\n';
+ static final char QUOTE = '"';
+
+ private static final char[] EMPTY_STRING_CHARS = Chr.array(QUOTE, QUOTE);
+
+ private static final String NULL_VALUE = "null";
+ private static final String JSON_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
+ private static final String DEFAULT_TIMEZONE = "GMT";
+
/**
* @return "true" or "false" for a boolean value
*/
@@ -55,8 +72,6 @@ public class JsonOutput {
return buffer.toString();
}
- private static final String NULL_VALUE = "null";
-
/**
* @return a string representation for a number
* @throws JsonException if the number is infinite or not a number.
@@ -312,7 +327,6 @@ public class JsonOutput {
}
}
- private static final char[] EMPTY_STRING_CHARS = Chr.array('"', '"');
/**
* Serializes any char sequence and writes it into specified buffer.
@@ -329,8 +343,8 @@ public class JsonOutput {
* Serializes date and writes it into specified buffer.
*/
private static void writeDate(Date date, CharBuf buffer) {
- SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
- formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
+ SimpleDateFormat formatter = new SimpleDateFormat(JSON_DATE_FORMAT, Locale.US);
+ formatter.setTimeZone(TimeZone.getTimeZone(DEFAULT_TIMEZONE));
buffer.addQuoted(formatter.format(date));
}
@@ -338,13 +352,13 @@ public class JsonOutput {
* Serializes array and writes it into specified buffer.
*/
private static void writeArray(Class<?> arrayClass, Object array, CharBuf buffer) {
- buffer.addChar('[');
+ buffer.addChar(OPEN_BRACKET);
if (Object[].class.isAssignableFrom(arrayClass)) {
Object[] objArray = (Object[]) array;
if (objArray.length > 0) {
writeObject(objArray[0], buffer);
for (int i = 1; i < objArray.length; i++) {
- buffer.addChar(',');
+ buffer.addChar(COMMA);
writeObject(objArray[i], buffer);
}
}
@@ -353,7 +367,7 @@ public class JsonOutput {
if (intArray.length > 0) {
buffer.addInt(intArray[0]);
for (int i = 1; i < intArray.length; i++) {
- buffer.addChar(',').addInt(intArray[i]);
+ buffer.addChar(COMMA).addInt(intArray[i]);
}
}
} else if (long[].class.isAssignableFrom(arrayClass)) {
@@ -361,7 +375,7 @@ public class JsonOutput {
if (longArray.length > 0) {
buffer.addLong(longArray[0]);
for (int i = 1; i < longArray.length; i++) {
- buffer.addChar(',').addLong(longArray[i]);
+ buffer.addChar(COMMA).addLong(longArray[i]);
}
}
} else if (boolean[].class.isAssignableFrom(arrayClass)) {
@@ -369,7 +383,7 @@ public class JsonOutput {
if (booleanArray.length > 0) {
buffer.addBoolean(booleanArray[0]);
for (int i = 1; i < booleanArray.length; i++) {
- buffer.addChar(',').addBoolean(booleanArray[i]);
+ buffer.addChar(COMMA).addBoolean(booleanArray[i]);
}
}
} else if (char[].class.isAssignableFrom(arrayClass)) {
@@ -377,7 +391,7 @@ public class JsonOutput {
if (charArray.length > 0) {
buffer.addJsonEscapedString(Chr.array(charArray[0]));
for (int i = 1; i < charArray.length; i++) {
- buffer.addChar(',').addJsonEscapedString(Chr.array(charArray[i]));
+ buffer.addChar(COMMA).addJsonEscapedString(Chr.array(charArray[i]));
}
}
} else if (double[].class.isAssignableFrom(arrayClass)) {
@@ -385,7 +399,7 @@ public class JsonOutput {
if (doubleArray.length > 0) {
buffer.addDouble(doubleArray[0]);
for (int i = 1; i < doubleArray.length; i++) {
- buffer.addChar(',').addDouble(doubleArray[i]);
+ buffer.addChar(COMMA).addDouble(doubleArray[i]);
}
}
} else if (float[].class.isAssignableFrom(arrayClass)) {
@@ -393,7 +407,7 @@ public class JsonOutput {
if (floatArray.length > 0) {
buffer.addFloat(floatArray[0]);
for (int i = 1; i < floatArray.length; i++) {
- buffer.addChar(',').addFloat(floatArray[i]);
+ buffer.addChar(COMMA).addFloat(floatArray[i]);
}
}
} else if (byte[].class.isAssignableFrom(arrayClass)) {
@@ -401,7 +415,7 @@ public class JsonOutput {
if (byteArray.length > 0) {
buffer.addByte(byteArray[0]);
for (int i = 1; i < byteArray.length; i++) {
- buffer.addChar(',').addByte(byteArray[i]);
+ buffer.addChar(COMMA).addByte(byteArray[i]);
}
}
} else if (short[].class.isAssignableFrom(arrayClass)) {
@@ -409,21 +423,21 @@ public class JsonOutput {
if (shortArray.length > 0) {
buffer.addShort(shortArray[0]);
for (int i = 1; i < shortArray.length; i++) {
- buffer.addChar(',').addShort(shortArray[i]);
+ buffer.addChar(COMMA).addShort(shortArray[i]);
}
}
}
- buffer.addChar(']');
+ buffer.addChar(CLOSE_BRACKET);
}
- private static final char[] EMPTY_MAP_CHARS = {'{', '}'};
+ private static final char[] EMPTY_MAP_CHARS = {OPEN_BRACE, CLOSE_BRACE};
/**
* Serializes map and writes it into specified buffer.
*/
private static void writeMap(Map<?, ?> map, CharBuf buffer) {
if (!map.isEmpty()) {
- buffer.addChar('{');
+ buffer.addChar(OPEN_BRACE);
boolean firstItem = true;
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() == null) {
@@ -431,7 +445,7 @@ public class JsonOutput {
}
if (!firstItem) {
- buffer.addChar(',');
+ buffer.addChar(COMMA);
} else {
firstItem = false;
}
@@ -439,28 +453,28 @@ public class JsonOutput {
buffer.addJsonFieldName(entry.getKey().toString());
writeObject(entry.getValue(), buffer);
}
- buffer.addChar('}');
+ buffer.addChar(CLOSE_BRACE);
} else {
buffer.addChars(EMPTY_MAP_CHARS);
}
}
- private static final char[] EMPTY_LIST_CHARS = {'[', ']'};
+ private static final char[] EMPTY_LIST_CHARS = {OPEN_BRACKET, CLOSE_BRACKET};
/**
* Serializes iterator and writes it into specified buffer.
*/
private static void writeIterator(Iterator<?> iterator, CharBuf buffer) {
if (iterator.hasNext()) {
- buffer.addChar('[');
+ buffer.addChar(OPEN_BRACKET);
Object it = iterator.next();
writeObject(it, buffer);
while (iterator.hasNext()) {
it = iterator.next();
- buffer.addChar(',');
+ buffer.addChar(COMMA);
writeObject(it, buffer);
}
- buffer.addChar(']');
+ buffer.addChar(CLOSE_BRACKET);
} else {
buffer.addChars(EMPTY_LIST_CHARS);
}
@@ -485,38 +499,38 @@ public class JsonOutput {
switch (token.getType()) {
case OPEN_CURLY:
indentSize += 4;
- output.addChars(Chr.array('{', '\n')).addChars(getIndent(indentSize, indentCache));
+ output.addChars(Chr.array(OPEN_BRACE, NEW_LINE)).addChars(getIndent(indentSize, indentCache));
break;
case CLOSE_CURLY:
indentSize -= 4;
- output.addChar('\n');
+ output.addChar(NEW_LINE);
if (indentSize > 0) {
output.addChars(getIndent(indentSize, indentCache));
}
- output.addChar('}');
+ output.addChar(CLOSE_BRACE);
break;
case OPEN_BRACKET:
indentSize += 4;
- output.addChars(Chr.array('[', '\n')).addChars(getIndent(indentSize, indentCache));
+ output.addChars(Chr.array(OPEN_BRACKET, NEW_LINE)).addChars(getIndent(indentSize, indentCache));
break;
case CLOSE_BRACKET:
indentSize -= 4;
- output.addChar('\n');
+ output.addChar(NEW_LINE);
if (indentSize > 0) {
output.addChars(getIndent(indentSize, indentCache));
}
- output.addChar(']');
+ output.addChar(CLOSE_BRACKET);
break;
case COMMA:
- output.addChars(Chr.array(',', '\n')).addChars(getIndent(indentSize, indentCache));
+ output.addChars(Chr.array(COMMA, NEW_LINE)).addChars(getIndent(indentSize, indentCache));
break;
case COLON:
- output.addChars(Chr.array(':', ' '));
+ output.addChars(Chr.array(COLON, SPACE));
break;
case STRING:
@@ -546,7 +560,7 @@ public class JsonOutput {
char[] indent = indentCache.get(indentSize);
if (indent == null) {
indent = new char[indentSize];
- Arrays.fill(indent, ' ');
+ Arrays.fill(indent, SPACE);
indentCache.put(indentSize, indent);
}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0bf2cb5d/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java b/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
index e8b6306..e9da080 100644
--- a/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
+++ b/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
@@ -19,6 +19,7 @@
package groovy.json;
import groovy.lang.Closure;
+import groovy.lang.DelegatesTo;
import groovy.lang.GroovyObjectSupport;
import java.io.IOException;
@@ -64,10 +65,15 @@ import java.util.*;
*
* @author Tim Yates
* @author Andrey Bloschetsov
+ * @author Graeme Rocher
+ *
* @since 1.8.1
*/
public class StreamingJsonBuilder extends GroovyObjectSupport {
+ private static final String DOUBLE_CLOSE_BRACKET = "}}";
+ private static final String COLON_WITH_OPEN_BRACE = ":{";
+
private Writer writer;
/**
@@ -115,6 +121,24 @@ public class StreamingJsonBuilder extends GroovyObjectSupport {
}
/**
+ * The empty args call will create a key whose value will be an empty JSON object:
+ * <pre class="groovyTestCase">
+ * new StringWriter().with { w ->
+ * def json = new groovy.json.StreamingJsonBuilder(w)
+ * json.person()
+ *
+ * assert w.toString() == '{"person":{}}'
+ * }
+ * </pre>
+ *
+ * @param name The name of the empty object to create
+ * @throws IOException
+ */
+ public void call(String name) throws IOException {
+ writer.write(JsonOutput.toJson(Collections.singletonMap(name, Collections.emptyMap())));
+ }
+
+ /**
* A list of elements as arguments to the JSON builder creates a root JSON array
* <p>
* Example:
@@ -181,7 +205,7 @@ public class StreamingJsonBuilder extends GroovyObjectSupport {
* @param coll a collection
* @param c a closure used to convert the objects of coll
*/
- public Object call(Collection coll, Closure c) throws IOException {
+ public Object call(Collection coll, @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws IOException {
StreamingJsonDelegate.writeCollectionWithClosure(writer, coll, c);
return null;
@@ -205,15 +229,116 @@ public class StreamingJsonBuilder extends GroovyObjectSupport {
*
* @param c a closure whose method call statements represent key / values of a JSON object
*/
- public Object call(Closure c) throws IOException {
- writer.write("{");
+ public Object call(@DelegatesTo(StreamingJsonDelegate.class) Closure c) throws IOException {
+ writer.write(JsonOutput.OPEN_BRACE);
StreamingJsonDelegate.cloneDelegateAndGetContent(writer, c);
- writer.write("}");
+ writer.write(JsonOutput.CLOSE_BRACE);
return null;
}
/**
+ * A name and a closure passed to a JSON builder will create a key with a JSON object
+ * <p>
+ * Example:
+ * <pre class="groovyTestCase">
+ * new StringWriter().with { w ->
+ * def json = new groovy.json.StreamingJsonBuilder(w)
+ * json.person {
+ * name "Tim"
+ * age 39
+ * }
+ *
+ * assert w.toString() == '{"person":{"name":"Tim","age":39}}'
+ * }
+ * </pre>
+ *
+ * @param name The key for the JSON object
+ * @param c a closure whose method call statements represent key / values of a JSON object
+ */
+ public void call(String name, @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws IOException {
+ writer.write(JsonOutput.OPEN_BRACE);
+ writer.write(JsonOutput.toJson(name));
+ writer.write(JsonOutput.COLON);
+ call(c);
+ writer.write(JsonOutput.CLOSE_BRACE);
+ }
+
+ /**
+ * A name, a collection and closure passed to a JSON builder will create a root JSON array applying
+ * the closure to each object in the collection
+ * <p>
+ * Example:
+ * <pre class="groovyTestCase">
+ * class Author {
+ * String name
+ * }
+ * def authors = [new Author (name: "Guillaume"), new Author (name: "Jochen"), new Author (name: "Paul")]
+ *
+ * new StringWriter().with { w ->
+ * def json = new groovy.json.StreamingJsonBuilder(w)
+ * json.people authors, { Author author ->
+ * name author.name
+ * }
+ *
+ * assert w.toString() == '{"people":[{"name":"Guillaume"},{"name":"Jochen"},{"name":"Paul"}]}'
+ * }
+ * </pre>
+ * @param coll a collection
+ * @param c a closure used to convert the objects of coll
+ */
+ public void call(String name, Collection coll, @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws IOException {
+ writer.write(JsonOutput.OPEN_BRACE);
+ writer.write(JsonOutput.toJson(name));
+ writer.write(JsonOutput.COLON);
+ call(coll, c);
+ writer.write(JsonOutput.CLOSE_BRACE);
+ }
+
+ /**
+ * If you use named arguments and a closure as last argument,
+ * the key/value pairs of the map (as named arguments)
+ * and the key/value pairs represented in the closure
+ * will be merged together —
+ * the closure properties overriding the map key/values
+ * in case the same key is used.
+ *
+ * <pre class="groovyTestCase">
+ * new StringWriter().with { w ->
+ * def json = new groovy.json.StreamingJsonBuilder(w)
+ * json.person(name: "Tim", age: 35) { town "Manchester" }
+ *
+ * assert w.toString() == '{"person":{"name":"Tim","age":35,"town":"Manchester"}}'
+ * }
+ * </pre>
+ *
+ * @param name The name of the JSON object
+ * @param map The attributes of the JSON object
+ * @param callable Additional attributes of the JSON object represented by the closure
+ * @throws IOException
+ */
+ public void call(String name, Map map, @DelegatesTo(StreamingJsonDelegate.class) Closure callable) throws IOException {
+ writer.write(JsonOutput.OPEN_BRACE);
+ writer.write(JsonOutput.toJson(name));
+ writer.write(COLON_WITH_OPEN_BRACE);
+ boolean first = true;
+ for (Object it : map.entrySet()) {
+ if (!first) {
+ writer.write(JsonOutput.COMMA);
+ } else {
+ first = false;
+ }
+
+ Map.Entry entry = (Map.Entry) it;
+ writer.write(JsonOutput.toJson(entry.getKey()));
+ writer.write(JsonOutput.COLON);
+ writer.write(JsonOutput.toJson(entry.getValue()));
+ }
+ StreamingJsonDelegate.cloneDelegateAndGetContent(writer, callable, map.size() == 0);
+ writer.write(DOUBLE_CLOSE_BRACKET);
+ }
+
+ /**
* A method call on the JSON builder instance will create a root object with only one key
* whose name is the name of the method being called.
* This method takes as arguments:
@@ -281,45 +406,25 @@ public class StreamingJsonBuilder extends GroovyObjectSupport {
Object[] arr = (Object[]) args;
try {
if (arr.length == 0) {
- writer.write(JsonOutput.toJson(Collections.singletonMap(name, Collections.emptyMap())));
+ call(name);
} else if (arr.length == 1) {
if (arr[0] instanceof Closure) {
- writer.write("{");
- writer.write(JsonOutput.toJson(name));
- writer.write(":");
- call((Closure) arr[0]);
- writer.write("}");
+ final Closure callable = (Closure) arr[0];
+ call(name, callable);
} else if (arr[0] instanceof Map) {
- writer.write(JsonOutput.toJson(Collections.singletonMap(name, (Map) arr[0])));
+ final Map<String, Map> map = Collections.singletonMap(name, (Map) arr[0]);
+ call(map);
} else {
notExpectedArgs = true;
}
} else if (arr.length == 2 && arr[0] instanceof Map && arr[1] instanceof Closure) {
- writer.write("{");
- writer.write(JsonOutput.toJson(name));
- writer.write(":{");
- boolean first = true;
Map map = (Map) arr[0];
- for (Object it : map.entrySet()) {
- if (!first) {
- writer.write(",");
- } else {
- first = false;
- }
-
- Map.Entry entry = (Map.Entry) it;
- writer.write(JsonOutput.toJson(entry.getKey()));
- writer.write(":");
- writer.write(JsonOutput.toJson(entry.getValue()));
- }
- StreamingJsonDelegate.cloneDelegateAndGetContent(writer, (Closure) arr[1], map.size() == 0);
- writer.write("}}");
+ final Closure callable = (Closure) arr[1];
+ call(name, map, callable);
} else if (StreamingJsonDelegate.isCollectionWithClosure(arr)) {
- writer.write("{");
- writer.write(JsonOutput.toJson(name));
- writer.write(":");
- call((Collection) arr[0], (Closure) arr[1]);
- writer.write("}");
+ final Collection coll = (Collection) arr[0];
+ final Closure callable = (Closure) arr[1];
+ call(name, coll, callable);
} else {
notExpectedArgs = true;
}
@@ -336,89 +441,203 @@ public class StreamingJsonBuilder extends GroovyObjectSupport {
throw new JsonException("Expected no arguments, a single map, a single closure, or a map and closure as arguments.");
}
}
-}
-class StreamingJsonDelegate extends GroovyObjectSupport {
+ /**
+ * The delegate used when invoking closures
+ */
+ public static class StreamingJsonDelegate extends GroovyObjectSupport {
- private Writer writer;
- private boolean first;
+ private Writer writer;
+ private boolean first;
+ private State state;
- public StreamingJsonDelegate(Writer w, boolean first) {
- this.writer = w;
- this.first = first;
- }
- public Object invokeMethod(String name, Object args) {
- if (args != null && Object[].class.isAssignableFrom(args.getClass())) {
- try {
- if (!first) {
- writer.write(",");
- } else {
- first = false;
- }
- writer.write(JsonOutput.toJson(name));
- writer.write(":");
- Object[] arr = (Object[]) args;
-
- if (arr.length == 1) {
- writer.write(JsonOutput.toJson(arr[0]));
- } else if (isCollectionWithClosure(arr)) {
- writeCollectionWithClosure(writer, (Collection) arr[0], (Closure) arr[1]);
- } else {
- writer.write(JsonOutput.toJson(Arrays.asList(arr)));
+ public StreamingJsonDelegate(Writer w, boolean first) {
+ this.writer = w;
+ this.first = first;
+ }
+
+ public Object invokeMethod(String name, Object args) {
+ if (args != null && Object[].class.isAssignableFrom(args.getClass())) {
+ try {
+ Object[] arr = (Object[]) args;
+
+ if (arr.length == 1) {
+ final Object value = arr[0];
+ call(name, value);
+ } else if (isCollectionWithClosure(arr)) {
+ final Collection coll = (Collection) arr[0];
+ final Closure callable = (Closure) arr[1];
+ call(name, coll, callable);
+ } else {
+ final List<Object> list = Arrays.asList(arr);
+ call(name, list);
+ }
+ } catch (IOException ioe) {
+ throw new JsonException(ioe);
}
- } catch (IOException ioe) {
- throw new JsonException(ioe);
}
+
+ return this;
}
- return this;
- }
+ /**
+ * Writes the name and a JSON array
+ * @param name The name of the JSON attribute
+ * @param list The list representing the array
+ * @throws IOException
+ */
+ public void call(String name, List<Object> list) throws IOException {
+ writeName(name);
+ writeArray(list);
+ }
- public static boolean isCollectionWithClosure(Object[] args) {
- return args.length == 2 && args[0] instanceof Collection && args[1] instanceof Closure;
- }
+ /**
+ * Writes the name and a JSON array
+ * @param name The name of the JSON attribute
+ * @param array The list representing the array
+ * @throws IOException
+ */
+ public void call(String name, Object...array) throws IOException {
+ writeName(name);
+ writeArray(Arrays.asList(array));
+ }
- public static Object writeCollectionWithClosure(Writer writer, Collection coll, Closure closure) throws IOException {
- writer.write("[");
- boolean first = true;
- for (Object it : coll) {
+ /**
+ * A collection and closure passed to a JSON builder will create a root JSON array applying
+ * the closure to each object in the collection
+ * <p>
+ * Example:
+ * <pre class="groovyTestCase">
+ * class Author {
+ * String name
+ * }
+ * def authorList = [new Author (name: "Guillaume"), new Author (name: "Jochen"), new Author (name: "Paul")]
+ *
+ * new StringWriter().with { w ->
+ * def json = new groovy.json.StreamingJsonBuilder(w)
+ * json.book {
+ * authors authorList, { Author author ->
+ * name author.name
+ * }
+ * }
+ *
+ * assert w.toString() == '{"book":{"authors":[{"name":"Guillaume"},{"name":"Jochen"},{"name":"Paul"}]}}'
+ * }
+ * </pre>
+ * @param coll a collection
+ * @param c a closure used to convert the objects of coll
+ */
+ public void call(String name, Collection coll, Closure c) throws IOException {
+ writeName(name);
+ writeObjects(coll, c);
+ }
+
+ /**
+ * Writes the name and value of a JSON attribute
+ *
+ * @param name The attribute name
+ * @param value The value
+ * @throws IOException
+ */
+ public void call(String name, Object value) throws IOException {
+ writeName(name);
+ writeValue(value);
+ }
+
+
+ private void writeObjects(Collection coll, @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws IOException {
+ verifyValue();
+ writeCollectionWithClosure(writer, coll, c);
+ }
+
+ private void verifyValue() {
+ if(state == State.VALUE) {
+ throw new IllegalStateException("Cannot write value when value has just been written. Write a name first!");
+ }
+ else {
+ state = State.VALUE;
+ }
+ }
+
+
+ private void writeName(String name) throws IOException {
+ if(state == State.NAME) {
+ throw new IllegalStateException("Cannot write a name when a name has just been written. Write a value first!");
+ }
+ else {
+ this.state = State.NAME;
+ }
if (!first) {
- writer.write(",");
+ writer.write(JsonOutput.COMMA);
} else {
first = false;
}
+ writer.write(JsonOutput.toJson(name));
+ writer.write(JsonOutput.COLON);
+ }
- writer.write("{");
- curryDelegateAndGetContent(writer, closure, it);
- writer.write("}");
+ private void writeValue(Object value) throws IOException {
+ verifyValue();
+ writer.write(JsonOutput.toJson(value));
}
- writer.write("]");
- return writer;
- }
+ private void writeArray(List<Object> list) throws IOException {
+ verifyValue();
+ writer.write(JsonOutput.toJson(list));
+ }
- public static void cloneDelegateAndGetContent(Writer w, Closure c) {
- cloneDelegateAndGetContent(w, c, true);
- }
+ public static boolean isCollectionWithClosure(Object[] args) {
+ return args.length == 2 && args[0] instanceof Collection && args[1] instanceof Closure;
+ }
- public static void cloneDelegateAndGetContent(Writer w, Closure c, boolean first) {
- StreamingJsonDelegate delegate = new StreamingJsonDelegate(w, first);
- Closure cloned = (Closure) c.clone();
- cloned.setDelegate(delegate);
- cloned.setResolveStrategy(Closure.DELEGATE_FIRST);
- cloned.call();
- }
+ public static Object writeCollectionWithClosure(Writer writer, Collection coll, Closure closure) throws IOException {
+ writer.write(JsonOutput.OPEN_BRACKET);
+ boolean first = true;
+ for (Object it : coll) {
+ if (!first) {
+ writer.write(JsonOutput.COMMA);
+ } else {
+ first = false;
+ }
- public static void curryDelegateAndGetContent(Writer w, Closure c, Object o) {
- curryDelegateAndGetContent(w, c, o, true);
- }
+ writer.write(JsonOutput.OPEN_BRACE);
+ curryDelegateAndGetContent(writer, closure, it);
+ writer.write(JsonOutput.CLOSE_BRACE);
+ }
+ writer.write(JsonOutput.CLOSE_BRACKET);
+
+ return writer;
+ }
+
+ public static void cloneDelegateAndGetContent(Writer w, Closure c) {
+ cloneDelegateAndGetContent(w, c, true);
+ }
- public static void curryDelegateAndGetContent(Writer w, Closure c, Object o, boolean first) {
- StreamingJsonDelegate delegate = new StreamingJsonDelegate(w, first);
- Closure curried = c.curry(o);
- curried.setDelegate(delegate);
- curried.setResolveStrategy(Closure.DELEGATE_FIRST);
- curried.call();
+ public static void cloneDelegateAndGetContent(Writer w, Closure c, boolean first) {
+ StreamingJsonDelegate delegate = new StreamingJsonDelegate(w, first);
+ Closure cloned = (Closure) c.clone();
+ cloned.setDelegate(delegate);
+ cloned.setResolveStrategy(Closure.DELEGATE_FIRST);
+ cloned.call();
+ }
+
+ public static void curryDelegateAndGetContent(Writer w, Closure c, Object o) {
+ curryDelegateAndGetContent(w, c, o, true);
+ }
+
+ public static void curryDelegateAndGetContent(Writer w, Closure c, Object o, boolean first) {
+ StreamingJsonDelegate delegate = new StreamingJsonDelegate(w, first);
+ Closure curried = c.curry(o);
+ curried.setDelegate(delegate);
+ curried.setResolveStrategy(Closure.DELEGATE_FIRST);
+ curried.call();
+ }
+
+ private enum State {
+ NAME, VALUE
+ }
}
}
+
+
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0bf2cb5d/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
index 2aa2dc7..10afbbf 100644
--- a/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
+++ b/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
@@ -18,12 +18,53 @@
*/
package groovy.json
+import groovy.transform.CompileStatic
+
/**
* @author Tim Yates
* @author Guillaume Laforge
*/
class StreamingJsonBuilderTest extends GroovyTestCase {
+ @CompileStatic
+ void testJsonBuilderUsageWithCompileStatic() {
+ new StringWriter().with { w ->
+ def json = new StreamingJsonBuilder(w)
+ json.call('response') {
+ call "status", "ok"
+ call "userTier", "free"
+ call "total", 2413
+ call "startIndex", 1
+ call "pageSize", 10
+ call "currentPage", 1
+ call "pages", 242
+ call "orderBy", "newest"
+ call("results",
+ [
+ id: "world/video/2011/jan/19/tunisia-demonstrators-democracy-video",
+ sectionId: "world",
+ sectionName: "World news",
+ webPublicationDate: "2011-01-19T15:12:46Z",
+ webTitle: "Tunisian demonstrators demand new democracy - video",
+ webUrl: "http://www.guardian.co.uk/world/video/2011/jan/19/tunisia-demonstrators-democracy-video",
+ apiUrl: "http://content.guardianapis.com/world/video/2011/jan/19/tunisia-demonstrators-democracy-video"
+ ],
+ [
+ id: "world/gallery/2011/jan/19/tunisia-protests-pictures",
+ sectionId: "world",
+ sectionName: "World news",
+ webPublicationDate: "2011-01-19T15:01:09Z",
+ webTitle: "Tunisia protests continue in pictures ",
+ webUrl: "http://www.guardian.co.uk/world/gallery/2011/jan/19/tunisia-protests-pictures",
+ apiUrl: "http://content.guardianapis.com/world/gallery/2011/jan/19/tunisia-protests-pictures"
+ ])
+ }
+
+ assert w.toString() ==
+ '''{"response":{"status":"ok","userTier":"free","total":2413,"startIndex":1,"pageSize":10,"currentPage":1,"pages":242,"orderBy":"newest","results":[{"id":"world/video/2011/jan/19/tunisia-demonstrators-democracy-video","sectionId":"world","sectionName":"World news","webPublicationDate":"2011-01-19T15:12:46Z","webTitle":"Tunisian demonstrators demand new democracy - video","webUrl":"http://www.guardian.co.uk/world/video/2011/jan/19/tunisia-demonstrators-democracy-video","apiUrl":"http://content.guardianapis.com/world/video/2011/jan/19/tunisia-demonstrators-democracy-video"},{"id":"world/gallery/2011/jan/19/tunisia-protests-pictures","sectionId":"world","sectionName":"World news","webPublicationDate":"2011-01-19T15:01:09Z","webTitle":"Tunisia protests continue in pictures ","webUrl":"http://www.guardian.co.uk/world/gallery/2011/jan/19/tunisia-protests-pictures","apiUrl":"http://content.guardianapis.com/world/gallery/2011/jan/19/tunisia-protests-pictures"}]}}'''
+ }
+ }
+
void testJsonBuilderConstructor() {
new StringWriter().with { w ->
new StreamingJsonBuilder(w, [a: 1, b: true])
[2/2] incubator-groovy git commit: Support for unescaped output in
StreamingJsonBuilder
Posted by cc...@apache.org.
Support for unescaped output in StreamingJsonBuilder
Closes #95
Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/c5c0cefb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/c5c0cefb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/c5c0cefb
Branch: refs/heads/GROOVY_2_4_X
Commit: c5c0cefb365b6a4c00335abeeed124cc8356a4c8
Parents: 0bf2cb5
Author: Graeme Rocher <gr...@gmail.com>
Authored: Tue Aug 25 16:57:45 2015 +0200
Committer: Cedric Champeau <cc...@apache.org>
Committed: Sun Sep 13 11:31:26 2015 +0200
----------------------------------------------------------------------
.../src/main/java/groovy/json/JsonOutput.java | 32 ++++++++++++++++++++
.../java/groovy/json/StreamingJsonBuilder.java | 12 ++++++++
.../groovy/json/StreamingJsonBuilderTest.groovy | 10 ++++++
3 files changed, 54 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/c5c0cefb/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java b/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
index 503f79c..b9a4e4b 100644
--- a/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
+++ b/subprojects/groovy-json/src/main/java/groovy/json/JsonOutput.java
@@ -291,6 +291,8 @@ public class JsonOutput {
buffer.addJsonEscapedString(object.toString());
} else if (objectClass == UUID.class) {
buffer.addQuoted(object.toString());
+ } else if (objectClass == JsonUnescaped.class) {
+ buffer.add(object.toString());
} else if (Closure.class.isAssignableFrom(objectClass)) {
writeMap(JsonDelegate.cloneDelegateAndGetContent((Closure<?>) object), buffer);
} else if (Expando.class.isAssignableFrom(objectClass)) {
@@ -567,4 +569,34 @@ public class JsonOutput {
return indent;
}
+ /**
+ * Obtains JSON unescaped text for the given text
+ *
+ * @param text The text
+ * @return The unescaped text
+ */
+ public static JsonUnescaped unescaped(CharSequence text) {
+ return new JsonUnescaped(text);
+ }
+
+ /**
+ * Represents unescaped JSON
+ */
+ public static class JsonUnescaped {
+ private CharSequence text;
+
+ public JsonUnescaped(CharSequence text) {
+ this.text = text;
+ }
+
+ public CharSequence getText() {
+ return text;
+ }
+
+ @Override
+ public String toString() {
+ return text.toString();
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/c5c0cefb/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java b/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
index e9da080..f96d8f6 100644
--- a/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
+++ b/subprojects/groovy-json/src/main/java/groovy/json/StreamingJsonBuilder.java
@@ -545,6 +545,18 @@ public class StreamingJsonBuilder extends GroovyObjectSupport {
writeValue(value);
}
+ /**
+ * Writes an unescaped value. Note: can cause invalid JSON if passed JSON is invalid
+ *
+ * @param name The attribute name
+ * @param json The value
+ * @throws IOException
+ */
+ public void call(String name, JsonOutput.JsonUnescaped json) throws IOException {
+ writeName(name);
+ writer.write(json.toString());
+ }
+
private void writeObjects(Collection coll, @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws IOException {
verifyValue();
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/c5c0cefb/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
index 10afbbf..19f4477 100644
--- a/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
+++ b/subprojects/groovy-json/src/test/groovy/groovy/json/StreamingJsonBuilderTest.groovy
@@ -72,6 +72,16 @@ class StreamingJsonBuilderTest extends GroovyTestCase {
}
}
+ void testUnescapedJson() {
+ new StringWriter().with { w ->
+ new StreamingJsonBuilder(w).call {
+ a 1
+ b JsonOutput.unescaped('{"name":"Fred"}')
+ }
+ assert w.toString() == '{"a":1,"b":{"name":"Fred"}}'
+ }
+ }
+
void testEmptyArray() {
new StringWriter().with { w ->
def json = new StreamingJsonBuilder(w)