[google-gson commit] r94 - in trunk/src: main/java/com/google/gson test/java/com/google/gson

208 views
Skip to first unread message

codesite...@google.com

unread,
Jun 17, 2008, 5:37:00 PM6/17/08
to google-gson...@googlegroups.com
Author: joel.leitch
Date: Tue Jun 17 14:35:43 2008
New Revision: 94

Added:
trunk/src/main/java/com/google/gson/DelegatingJsonElementVisitor.java
trunk/src/main/java/com/google/gson/Escaper.java
trunk/src/main/java/com/google/gson/JsonEscapingVisitor.java
trunk/src/test/java/com/google/gson/EscaperTest.java
trunk/src/test/java/com/google/gson/JsonEscapingVisitorTest.java
Modified:
trunk/src/main/java/com/google/gson/Gson.java
trunk/src/main/java/com/google/gson/JsonCompactFormatter.java
trunk/src/main/java/com/google/gson/JsonPrimitive.java
trunk/src/main/java/com/google/gson/JsonPrintFormatter.java
trunk/src/test/java/com/google/gson/JsonSerializerTest.java

Log:
Perform JSON string escaping.

Added: trunk/src/main/java/com/google/gson/DelegatingJsonElementVisitor.java
==============================================================================
--- (empty file)
+++
trunk/src/main/java/com/google/gson/DelegatingJsonElementVisitor.java
Tue Jun 17 14:35:43 2008
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed 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 com.google.gson;
+
+/**
+ * A simple implementation of the {@link JsonElementVisitor} that
simply delegates the method
+ * invocation onto a {@code delegate} instance of the {@link
JsonElementVisitor}. This object
+ * can be used to build a chain of visitors such that each Visitor
instance can perform some
+ * operation on the {@link JsonElement} and then pass on the input to
the delegate. This kind
+ * of pattern is sometimes referred as a "Chain of Responsibility".
+ *
+ * <p>The following is an example use case:
+ *
+ * <pre>
+ * class JsonEscapingVisitor extends DelegatingJsonElementVisitor {
+ * public JsonEscapingVisitor(JsonElementVisitor) {
+ * super(visitor);
+ * }
+ *
+ * public void visitPrimitive(JsonPrimitive primitive) {
+ * JsonPrimitive escapedPrimitive = escapePrimitiveObject(primitive);
+ * super.visitPrimitive(escapedPrimitive);
+ * }
+ * }
+ *
+ * JsonElementVisitor visitor = new JsonEscapingVisitor(new FormattingVisitor());
+ * </pre></p>
+ *
+ * @author Joel Leitch
+ */
+class DelegatingJsonElementVisitor implements JsonElementVisitor {
+ private final JsonElementVisitor delegate;
+
+ protected DelegatingJsonElementVisitor(JsonElementVisitor delegate) {
+ Preconditions.checkNotNull(delegate);
+ this.delegate = delegate;
+ }
+
+ public void endArray(JsonArray array) {
+ delegate.endArray(array);
+ }
+
+ public void endObject(JsonObject object) {
+ delegate.endObject(object);
+ }
+
+ public void startArray(JsonArray array) {
+ delegate.startArray(array);
+ }
+
+ public void startObject(JsonObject object) {
+ delegate.startObject(object);
+ }
+
+ public void visitArrayMember(JsonArray parent, JsonPrimitive member,
boolean isFirst) {
+ delegate.visitArrayMember(parent, member, isFirst);
+ }
+
+ public void visitArrayMember(JsonArray parent, JsonArray member,
boolean isFirst) {
+ delegate.visitArrayMember(parent, member, isFirst);
+ }
+
+ public void visitArrayMember(JsonArray parent, JsonObject member,
boolean isFirst) {
+ delegate.visitArrayMember(parent, member, isFirst);
+ }
+
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonPrimitive member,
boolean isFirst) {
+ delegate.visitObjectMember(parent, memberName, member, isFirst);
+ }
+
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonArray member, boolean
isFirst) {
+ delegate.visitObjectMember(parent, memberName, member, isFirst);
+ }
+
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonObject member, boolean
isFirst) {
+ delegate.visitObjectMember(parent, memberName, member, isFirst);
+ }
+
+ public void visitPrimitive(JsonPrimitive primitive) {
+ delegate.visitPrimitive(primitive);
+ }
+}

Added: trunk/src/main/java/com/google/gson/Escaper.java
==============================================================================
--- (empty file)
+++ trunk/src/main/java/com/google/gson/Escaper.java Tue Jun 17
14:35:43 2008
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed 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 com.google.gson;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A utility class that is used to perform JSON escaping so that ", <,
>, etc. characters are
+ * properly encoded in the JSON string representation before returning
to the client code.
+ *
+ * <p>This class contains a single method to escape a passed in string value:
+ * <pre>
+ * String jsonStringValue = "beforeQuote\"afterQuote";
+ * String escapedValue = Escaper.escapeJsonString(jsonStringValue);
+ * </pre></p>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+class Escaper {
+
+ private static final char[] HEX_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ private static final Set<Character> JS_ESCAPE_CHARS;
+
+ static {
+ Set<Character> tmpSet = new HashSet<Character>();
+ tmpSet.add('\u0000');
+ tmpSet.add('\r');
+ tmpSet.add('\n');
+ tmpSet.add('\u2028');
+ tmpSet.add('\u2029');
+ tmpSet.add('\u0085');
+ tmpSet.add('\'');
+ tmpSet.add('"');
+ tmpSet.add('<');
+ tmpSet.add('>');
+ tmpSet.add('&');
+ tmpSet.add('=');
+ tmpSet.add('\\');
+ JS_ESCAPE_CHARS = Collections.unmodifiableSet(tmpSet);
+ }
+
+ public static String escapeJsonString(CharSequence plainText) {
+ StringBuffer escapedString = new StringBuffer(plainText.length() + 20);
+ try {
+ escapeJsonString(plainText, escapedString);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return escapedString.toString();
+ }
+
+ private static void escapeJsonString(CharSequence plainText,
StringBuffer out) throws IOException {
+ int pos = 0; // Index just past the last char in plainText
written to out.
+ int len = plainText.length();
+ for (int charCount, i = 0; i < len; i += charCount) {
+ int codePoint = Character.codePointAt(plainText, i);
+ charCount = Character.charCount(codePoint);
+
+ if (!((codePoint < 0x20 || codePoint >= 0x7f)
+ || mustEscapeCharInJsString(codePoint))) {
+ continue;
+ }
+
+ out.append(plainText, pos, i);
+ pos = i + charCount;
+ switch (codePoint) {
+ case '\b':
+ out.append("\\b");
+ break;
+ case '\t':
+ out.append("\\t");
+ break;
+ case '\n':
+ out.append("\\n");
+ break;
+ case '\f':
+ out.append("\\f");
+ break;
+ case '\r':
+ out.append("\\r");
+ break;
+ case '\\':
+ out.append("\\\\");
+ break;
+ case '"':
+ out.append('\\').append((char) codePoint);
+ break;
+ case '\'':
+ out.append((char) codePoint);
+ break;
+ default:
+ appendHexJavaScriptRepresentation(codePoint, out);
+ break;
+ }
+ }
+ out.append(plainText, pos, len);
+ }
+
+ private static void appendHexJavaScriptRepresentation(int codePoint,
Appendable out)
+ throws IOException {
+ if (Character.isSupplementaryCodePoint(codePoint)) {
+ // Handle supplementary unicode values which are not
representable in
+ // javascript. We deal with these by escaping them as two 4B sequences
+ // so that they will round-trip properly when sent from java to javascript
+ // and back.
+ char[] surrogates = Character.toChars(codePoint);
+ appendHexJavaScriptRepresentation(surrogates[0], out);
+ appendHexJavaScriptRepresentation(surrogates[1], out);
+ return;
+ }
+ out.append("\\u")
+ .append(HEX_CHARS[(codePoint >>> 12) & 0xf])
+ .append(HEX_CHARS[(codePoint >>> 8) & 0xf])
+ .append(HEX_CHARS[(codePoint >>> 4) & 0xf])
+ .append(HEX_CHARS[codePoint & 0xf]);
+ }
+
+ private static boolean mustEscapeCharInJsString(int codepoint) {
+ if (!Character.isSupplementaryCodePoint(codepoint)) {
+ return JS_ESCAPE_CHARS.contains((char)codepoint);
+ } else {
+ return false;
+ }
+ }
+}

Modified: trunk/src/main/java/com/google/gson/Gson.java
==============================================================================
--- trunk/src/main/java/com/google/gson/Gson.java (original)
+++ trunk/src/main/java/com/google/gson/Gson.java Tue Jun 17 14:35:43 2008
@@ -264,6 +264,8 @@
JsonSerializationContext context =
new JsonSerializationContextDefault(navigatorFactory, serializers);
JsonElement jsonElement = context.serialize(src, typeOfSrc);
+
+ //TODO(Joel): instead of navigating the "JsonElement" inside the
formatter, do it here.
StringWriter writer = new StringWriter();
formatter.format(jsonElement, new PrintWriter(writer));
return jsonElement == null ? "" : writer.toString();

Modified: trunk/src/main/java/com/google/gson/JsonCompactFormatter.java
==============================================================================
--- trunk/src/main/java/com/google/gson/JsonCompactFormatter.java (original)
+++ trunk/src/main/java/com/google/gson/JsonCompactFormatter.java Tue
Jun 17 14:35:43 2008
@@ -16,101 +16,99 @@

package com.google.gson;

-
import java.io.PrintWriter;

-
/**
* Formats Json in a compact way eliminating all unnecessary whitespace.
- *
+ *
* @author Inderjeet Singh
*/
final class JsonCompactFormatter implements JsonFormatter {

private static class FormattingVisitor implements JsonElementVisitor {
private final PrintWriter writer;
-
+
FormattingVisitor(PrintWriter writer) {
this.writer = writer;
}
-
+
public void visitPrimitive(JsonPrimitive primitive) {
writer.append(primitive.toString());
}
-
- public void startArray(JsonArray array) {
+
+ public void startArray(JsonArray array) {
writer.append('[');
}

public void visitArrayMember(JsonArray parent, JsonPrimitive
member, boolean isFirst) {
if (!isFirst) {
writer.append(',');
- }
+ }
writer.append(member.toString());
}
-
+
public void visitArrayMember(JsonArray parent, JsonArray member,
boolean isFirst) {
if (!isFirst) {
writer.append(',');
- }
+ }
}
-
+
public void visitArrayMember(JsonArray parent, JsonObject member,
boolean isFirst) {
if (!isFirst) {
writer.append(',');
- }
+ }
}
-
+
public void endArray(JsonArray array) {
writer.append(']');
}
-
+
public void startObject(JsonObject object) {
writer.append('{');
}
-
- public void visitObjectMember(JsonObject parent, String
memberName, JsonPrimitive member,
+
+ public void visitObjectMember(JsonObject parent, String
memberName, JsonPrimitive member,
boolean isFirst) {
if (!isFirst) {
writer.append(',');
- }
+ }
writer.append('"');
writer.append(memberName);
writer.append("\":");
writer.append(member.toString());
}
-
- public void visitObjectMember(JsonObject parent, String
memberName, JsonArray member,
+
+ public void visitObjectMember(JsonObject parent, String
memberName, JsonArray member,
boolean isFirst) {
if (!isFirst) {
writer.append(',');
- }
+ }
writer.append('"');
writer.append(memberName);
writer.append("\":");
}

- public void visitObjectMember(JsonObject parent, String
memberName, JsonObject member,
+ public void visitObjectMember(JsonObject parent, String
memberName, JsonObject member,
boolean isFirst) {
if (!isFirst) {
writer.append(',');
- }
+ }
writer.append('"');
writer.append(memberName);
- writer.append("\":");
+ writer.append("\":");
}
-
+
public void endObject(JsonObject object) {
writer.append('}');
}
}
-
+
public void format(JsonElement root, PrintWriter writer) {
if (root == null) {
return;
}
- FormattingVisitor visitor = new FormattingVisitor(writer);
+ JsonElementVisitor visitor = new JsonEscapingVisitor(new FormattingVisitor(writer));
JsonTreeNavigator navigator = new JsonTreeNavigator(visitor);
navigator.navigate(root);
- }
+ }
}

Added: trunk/src/main/java/com/google/gson/JsonEscapingVisitor.java
==============================================================================
--- (empty file)
+++ trunk/src/main/java/com/google/gson/JsonEscapingVisitor.java Tue
Jun 17 14:35:43 2008
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed 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 com.google.gson;
+
+/**
+ * Performs JSON escaping and passes on the new escaped value to the
delegate {@link JsonVisitor}.
+ *
+ * @author Joel Leitch
+ */
+class JsonEscapingVisitor extends DelegatingJsonElementVisitor {
+
+ /**
+ * Constructs a Visitor that will properly escape any JSON primitive values.
+ *
+ * @param delegate the JsonElementVisitor that this instance will
use for delegation
+ */
+ protected JsonEscapingVisitor(JsonElementVisitor delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public void visitArrayMember(JsonArray parent, JsonPrimitive member,
boolean isFirst) {
+ super.visitArrayMember(parent, escapeJsonPrimitive(member), isFirst);
+ }
+
+ @Override
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonPrimitive member,
boolean isFirst) {
+ super.visitObjectMember(parent, memberName,
escapeJsonPrimitive(member), isFirst);
+ }
+
+ @Override
+ public void visitPrimitive(JsonPrimitive primitive) {
+ super.visitPrimitive(escapeJsonPrimitive(primitive));
+ }
+
+ private JsonPrimitive escapeJsonPrimitive(JsonPrimitive member) {
+ if (member.isString()) {
+ String memberValue = member.getAsString();
+ String escapedValue = Escaper.escapeJsonString(memberValue);
+ if (!escapedValue.equals(memberValue)) {
+ member.setValue(escapedValue);
+ }
+ }
+ return member;
+ }
+}

Modified: trunk/src/main/java/com/google/gson/JsonPrimitive.java
==============================================================================
--- trunk/src/main/java/com/google/gson/JsonPrimitive.java (original)
+++ trunk/src/main/java/com/google/gson/JsonPrimitive.java Tue Jun 17
14:35:43 2008
@@ -17,147 +17,151 @@
package com.google.gson;

/**
- * A class representing a Json primitive value. A primitive value
- * is either a String, a Java primitive, or a Java primitive
+ * A class representing a Json primitive value. A primitive value
+ * is either a String, a Java primitive, or a Java primitive
* wrapper type.
- *
+ *
* @author Inderjeet Singh
*/
public final class JsonPrimitive extends JsonElement {
-
+
private Object value;

/**
* Create a primitive containing a boolean value.
- *
+ *
* @param bool the value to create the primitive with.
*/
public JsonPrimitive(Boolean bool) {
this.value = bool;
}
-
+
/**
* Create a primitive containing a {@link Number}.
- *
+ *
* @param number the value to create the primitive with.
*/
public JsonPrimitive(Number number) {
this.value = number;
}
-
+
/**
* Create a primitive containing a String value.
- *
+ *
* @param string the value to create the primitive with.
*/
public JsonPrimitive(String string) {
this.value = string;
}
-
+
/**
* Create a primitive containing a character. The character is
turned into a one character String
- * since Json only supports String.
- *
+ * since Json only supports String.
+ *
* @param c the value to create the primitive with.
*/
public JsonPrimitive(Character c) {
this.value = String.valueOf(c);
}
-
+
/**
* Create a primitive containing a character. The character is
turned into a one character String
- * since Json only supports String.
- *
+ * since Json only supports String.
+ *
* @param c the value to create the primitive with.
*/
public JsonPrimitive(char c) {
this.value = String.valueOf(c);
}
-
+
/**
* Create a primitive using the specified Object. It must be an
instance of {@link Number}, a
- * Java primitive type, or a String.
- *
+ * Java primitive type, or a String.
+ *
* @param primitive the value to create the primitive with.
*/
JsonPrimitive(Object primitive) {
+ setValue(primitive);
+ }
+
+ void setValue(Object primitive) {
if (primitive instanceof Character) {
- // convert characters to strings since in JSON, characters are
represented as a single
- // character string
+ // convert characters to strings since in JSON, characters are
represented as a single
+ // character string
char c = ((Character)primitive).charValue();
this.value = String.valueOf(c);
} else {
- Preconditions.checkArgument(primitive instanceof Number
+ Preconditions.checkArgument(primitive instanceof Number
|| ObjectNavigator.isPrimitiveOrString(primitive));
this.value = primitive;
}
}
-
+
/**
- * Check whether this primitive contains a boolean value.
- *
- * @return true if this primitive contains a boolean value, false otherwise.
+ * Check whether this primitive contains a boolean value.
+ *
+ * @return true if this primitive contains a boolean value, false otherwise.
*/
public boolean isBoolean() {
return value instanceof Boolean;
}
-
+
/**
- * convenience method to get this element as a {@link Boolean}.
- *
- * @return get this element as a {@link Boolean}.
- * @throws ClassCastException if the value contained is not a valid
boolean value.
+ * convenience method to get this element as a {@link Boolean}.
+ *
+ * @return get this element as a {@link Boolean}.
+ * @throws ClassCastException if the value contained is not a valid
boolean value.
*/
@Override
Boolean getAsBooleanWrapper() {
return (Boolean) value;
}
-
+
/**
- * convenience method to get this element as a boolean value.
- *
- * @return get this element as a primitive boolean value.
- * @throws ClassCastException if the value contained is not a valid
boolean value.
+ * convenience method to get this element as a boolean value.
+ *
+ * @return get this element as a primitive boolean value.
+ * @throws ClassCastException if the value contained is not a valid
boolean value.
*/
@Override
public boolean getAsBoolean() {
return ((Boolean) value).booleanValue();
}
-
+
/**
- * Check whether this primitive contains a Number.
- *
- * @return true if this primitive contains a Number, false otherwise.
+ * Check whether this primitive contains a Number.
+ *
+ * @return true if this primitive contains a Number, false otherwise.
*/
public boolean isNumber() {
return value instanceof Number;
}
-
+
/**
- * convenience method to get this element as a Number.
- *
- * @return get this element as a Number.
- * @throws ClassCastException if the value contained is not a valid Number.
+ * convenience method to get this element as a Number.
+ *
+ * @return get this element as a Number.
+ * @throws ClassCastException if the value contained is not a valid Number.
*/
@Override
public Number getAsNumber() {
return (Number) value;
}
-
+
/**
- * Check whether this primitive contains a String value.
- *
- * @return true if this primitive contains a String value, false otherwise.
+ * Check whether this primitive contains a String value.
+ *
+ * @return true if this primitive contains a String value, false otherwise.
*/
public boolean isString() {
return value instanceof String;
}
-
+
/**
- * convenience method to get this element as a String.
- *
- * @return get this element as a String.
- * @throws ClassCastException if the value contained is not a valid String.
+ * convenience method to get this element as a String.
+ *
+ * @return get this element as a String.
+ * @throws ClassCastException if the value contained is not a valid String.
*/
@Override
public String getAsString() {
@@ -165,70 +169,70 @@
}

/**
- * convenience method to get this element as a primitive double.
- *
- * @return get this element as a primitive double.
- * @throws ClassCastException if the value contained is not a valid double.
+ * convenience method to get this element as a primitive double.
+ *
+ * @return get this element as a primitive double.
+ * @throws ClassCastException if the value contained is not a valid double.
*/
@Override
public double getAsDouble() {
return ((Number) value).doubleValue();
}
-
+
/**
- * convenience method to get this element as a float.
- *
- * @return get this element as a float.
- * @throws ClassCastException if the value contained is not a valid float.
+ * convenience method to get this element as a float.
+ *
+ * @return get this element as a float.
+ * @throws ClassCastException if the value contained is not a valid float.
*/
@Override
public float getAsFloat() {
return ((Number) value).floatValue();
}
-
+
/**
- * convenience method to get this element as a primitive long.
- *
- * @return get this element as a primitive long.
- * @throws ClassCastException if the value contained is not a valid long.
+ * convenience method to get this element as a primitive long.
+ *
+ * @return get this element as a primitive long.
+ * @throws ClassCastException if the value contained is not a valid long.
*/
@Override
public long getAsLong() {
return ((Number) value).longValue();
}
-
+
/**
- * convenience method to get this element as a primitive short.
- *
- * @return get this element as a primitive short.
- * @throws ClassCastException if the value contained is not a valid
short value.
+ * convenience method to get this element as a primitive short.
+ *
+ * @return get this element as a primitive short.
+ * @throws ClassCastException if the value contained is not a valid
short value.
*/
@Override
public short getAsShort() {
return ((Number) value).shortValue();
}
-
+
/**
- * convenience method to get this element as a primitive integer.
- *
- * @return get this element as a primitive integer.
- * @throws ClassCastException if the value contained is not a valid integer.
+ * convenience method to get this element as a primitive integer.
+ *
+ * @return get this element as a primitive integer.
+ * @throws ClassCastException if the value contained is not a valid integer.
*/
@Override
public int getAsInt() {
return ((Number) value).intValue();
}
-
+
/**
- * convenience method to get this element as an Object.
- *
- * @return get this element as an Object that can be converted to a
suitable value.
+ * convenience method to get this element as an Object.
+ *
+ * @return get this element as an Object that can be converted to a
suitable value.
*/
@Override
Object getAsObject() {
return value;
}
-
+
@Override
protected void toString(StringBuilder sb) {
if (value != null) {
@@ -236,7 +240,7 @@
sb.append('"');
sb.append(value);
sb.append('"');
-
+
} else {
sb.append(value);
}

Modified: trunk/src/main/java/com/google/gson/JsonPrintFormatter.java
==============================================================================
--- trunk/src/main/java/com/google/gson/JsonPrintFormatter.java (original)
+++ trunk/src/main/java/com/google/gson/JsonPrintFormatter.java Tue Jun
17 14:35:43 2008
@@ -19,10 +19,10 @@
import java.io.PrintWriter;

/**
- * Formats Json in a nicely indented way with a specified print margin.
- * This printer tries to keep elements on the same line as much as possible
- * while respecting right margin.
- *
+ * Formats Json in a nicely indented way with a specified print margin.
+ * This printer tries to keep elements on the same line as much as possible
+ * while respecting right margin.
+ *
* @author Inderjeet Singh
*/
final class JsonPrintFormatter implements JsonFormatter {
@@ -30,7 +30,7 @@
private final int printMargin;
private final int indentationSize;
private final int rightMargin;
-
+
public static final int DEFAULT_PRINT_MARGIN = 80;
public static final int DEFAULT_INDENTATION_SIZE = 2;
public static final int DEFAULT_RIGHT_MARGIN = 4;
@@ -38,7 +38,7 @@
public JsonPrintFormatter() {
this(DEFAULT_PRINT_MARGIN, DEFAULT_INDENTATION_SIZE, DEFAULT_RIGHT_MARGIN);
}
-
+
public JsonPrintFormatter(int printMargin, int indentationSize, int
rightMargin) {
this.printMargin = printMargin;
this.indentationSize = indentationSize;
@@ -54,55 +54,55 @@
level = 0;
line = new StringBuilder();
}
-
+
void key(String key) {
getLine().append('"');
getLine().append(key);
getLine().append('"');
}
-
+
void value(String value) {
getLine().append(value);
}
-
+
void fieldSeparator() {
getLine().append(':');
breakLineIfNeeded();
}
-
+
void elementSeparator() {
getLine().append(',');
breakLineIfNeeded();
}
-
+
void beginObject() {
++level;
breakLineIfNeeded();
- getLine().append('{');
+ getLine().append('{');
}
-
+
void endObject() {
getLine().append('}');
--level;
}
-
+
void beginArray() {
++level;
breakLineIfNeeded();
getLine().append('[');
}
-
+
void endArray() {
getLine().append(']');
--level;
}
-
+
private void breakLineIfNeeded() {
if (getLine().length() > printMargin - rightMargin) {
finishLine();
}
}
-
+
void finishLine() {
if (line != null) {
writer.append(line).append("\n");
@@ -116,7 +116,7 @@
}
return line;
}
-
+
private void createNewLine() {
line = new StringBuilder();
for (int i = 0; i < level; ++i) {
@@ -126,15 +126,15 @@
}
}
}
-
+
private class PrintFormattingVisitor implements JsonElementVisitor {
private final JsonWriter writer;
private boolean first;
-
+
PrintFormattingVisitor(JsonWriter writer) {
this.writer = writer;
}
-
+
private void addCommaCheckingFirst() {
if (first) {
first = false;
@@ -142,7 +142,7 @@
writer.elementSeparator();
}
}
-
+
public void startArray(JsonArray array) {
writer.beginArray();
}
@@ -195,15 +195,15 @@
writer.value(primitive.toString());
}
}
-
+
public void format(JsonElement root, PrintWriter writer) {
if (root == null) {
return;
}
JsonWriter jsonWriter = new JsonWriter(writer);
- PrintFormattingVisitor visitor = new PrintFormattingVisitor(jsonWriter);
+ JsonElementVisitor visitor = new JsonEscapingVisitor(new PrintFormattingVisitor(jsonWriter));
JsonTreeNavigator navigator = new JsonTreeNavigator(visitor);
navigator.navigate(root);
jsonWriter.finishLine();
- }
+ }
}

Added: trunk/src/test/java/com/google/gson/EscaperTest.java
==============================================================================
--- (empty file)
+++ trunk/src/test/java/com/google/gson/EscaperTest.java Tue Jun 17
14:35:43 2008
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed 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 com.google.gson;
+
+import junit.framework.TestCase;
+
+/**
+ * Performs some unit testing for the {@link Escaper} class.
+ *
+ * @author Joel Leitch
+ */
+public class EscaperTest extends TestCase {
+
+ public void testNoSpecialCharacters() {
+ String value = "Testing123";
+ String escapedString = Escaper.escapeJsonString(value);
+ assertEquals(value, escapedString);
+ }
+
+ public void testNewlineEscaping() throws Exception {
+ String containsNewline = "123\n456";
+ String escapedString = Escaper.escapeJsonString(containsNewline);
+ assertEquals("123\\n456", escapedString);
+ }
+
+ public void testCarrageReturnEscaping() throws Exception {
+ String containsCarrageReturn = "123\r456";
+ String escapedString = Escaper.escapeJsonString(containsCarrageReturn);
+ assertEquals("123\\r456", escapedString);
+ }
+
+ public void testTabEscaping() throws Exception {
+ String containsTab = "123\t456";
+ String escapedString = Escaper.escapeJsonString(containsTab);
+ assertEquals("123\\t456", escapedString);
+ }
+
+ public void testQuoteEscaping() throws Exception {
+ String containsQuote = "123\"456";
+ String escapedString = Escaper.escapeJsonString(containsQuote);
+ assertEquals("123\\\"456", escapedString);
+ }
+
+ public void testEqualsEscaping() throws Exception {
+ String containsEquals = "123=456";
+ int index = containsEquals.indexOf('=');
+ String unicodeValue =
convertToUnicodeString(Character.codePointAt(containsEquals, index));
+ String escapedString = Escaper.escapeJsonString(containsEquals);
+ assertEquals("123" + unicodeValue + "456", escapedString);
+ }
+
+ public void testGreaterThanAndLessThanEscaping() throws Exception {
+ String containsLtGt = "123>456<";
+ int gtIndex = containsLtGt.indexOf('>');
+ int ltIndex = containsLtGt.indexOf('<');
+ String gtAsUnicode =
convertToUnicodeString(Character.codePointAt(containsLtGt, gtIndex));
+ String ltAsUnicode =
convertToUnicodeString(Character.codePointAt(containsLtGt, ltIndex));
+
+ String escapedString = Escaper.escapeJsonString(containsLtGt);
+ assertEquals("123" + gtAsUnicode + "456" + ltAsUnicode, escapedString);
+ }
+
+ public void testAmpersandEscaping() throws Exception {
+ String containsAmp = "123&456";
+ int ampIndex = containsAmp.indexOf('&');
+ String ampAsUnicode =
convertToUnicodeString(Character.codePointAt(containsAmp, ampIndex));
+
+ String escapedString = Escaper.escapeJsonString(containsAmp);
+ assertEquals("123" + ampAsUnicode + "456", escapedString);
+ }
+
+ public void testSlashEscaping() throws Exception {
+ String containsSlash = "123\\456";
+ String escapedString = Escaper.escapeJsonString(containsSlash);
+ assertEquals("123\\\\456", escapedString);
+ }
+
+ public void testSingleQuoteNotEscaped() throws Exception {
+ String containsSingleQuote = "123'456";
+ String escapedString = Escaper.escapeJsonString(containsSingleQuote);
+ assertEquals(containsSingleQuote, escapedString);
+ }
+
+ private String convertToUnicodeString(int codepoint) {
+ String hexValue = Integer.toHexString(codepoint);
+ StringBuilder sb = new StringBuilder("\\u");
+ for (int i = 0; i < 4 - hexValue.length(); i++) {
+ sb.append(0);
+ }
+ sb.append(hexValue);
+
+ return sb.toString().toLowerCase();
+ }
+}

Added: trunk/src/test/java/com/google/gson/JsonEscapingVisitorTest.java
==============================================================================
--- (empty file)
+++ trunk/src/test/java/com/google/gson/JsonEscapingVisitorTest.java
Tue Jun 17 14:35:43 2008
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed 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 com.google.gson;
+
+import junit.framework.TestCase;
+
+/**
+ * Performs some unit testing for the {@link JsonEscapingVisitor} class.
+ *
+ * @author Joel Leitch
+ */
+public class JsonEscapingVisitorTest extends TestCase {
+ private StubbedJsonElementVisitor stubVisitor;
+ private JsonEscapingVisitor escapingVisitor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ stubVisitor = new StubbedJsonElementVisitor();
+ escapingVisitor = new JsonEscapingVisitor(stubVisitor);
+ }
+
+ public void testNonStringPrimitiveVisitation() throws Exception {
+ boolean value = true;
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ escapingVisitor.visitPrimitive(primitive);
+ assertEquals(value, stubVisitor.primitiveReceived.getAsBoolean());
+ }
+
+ public void testStringPrimitiveVisitationNoEscapingRequired() throws
Exception {
+ String value = "Testing123";
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ escapingVisitor.visitPrimitive(primitive);
+ assertEquals(value, stubVisitor.primitiveReceived.getAsObject());
+ }
+
+ public void testStringPrimitiveVisitationEscapingRequired() throws
Exception {
+ String value = "Testing\"123";
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ escapingVisitor.visitPrimitive(primitive);
+ assertEquals(Escaper.escapeJsonString(value), stubVisitor.primitiveReceived.getAsString());
+ }
+
+ public void testNonStringArrayVisitation() throws Exception {
+ int value = 123;
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ JsonArray array = new JsonArray();
+ array.add(primitive);
+ escapingVisitor.visitArrayMember(array, primitive, true);
+ assertEquals(value, stubVisitor.primitiveReceived.getAsInt());
+ }
+
+ public void testStringArrayVisitationNoEscaping() throws Exception {
+ String value = "Testing123";
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ JsonArray array = new JsonArray();
+ array.add(primitive);
+ escapingVisitor.visitArrayMember(array, primitive, true);
+ assertEquals(value, stubVisitor.primitiveReceived.getAsString());
+ }
+
+ public void testStringArrayVisitationEscapingRequired() throws
Exception {
+ String value = "Testing\"123";
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ JsonArray array = new JsonArray();
+ array.add(primitive);
+ escapingVisitor.visitArrayMember(array, primitive, true);
+ assertEquals(Escaper.escapeJsonString(value), stubVisitor.primitiveReceived.getAsString());
+ }
+
+ public void testNonStringFieldVisitation() throws Exception {
+ String fieldName = "fieldName";
+ int value = 123;
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ JsonObject object = new JsonObject();
+ object.addProperty(fieldName, value);
+
+ escapingVisitor.visitObjectMember(object, fieldName, primitive, true);
+ assertEquals(value, stubVisitor.primitiveReceived.getAsInt());
+ }
+
+ public void testStringFieldVisitationNoEscaping() throws Exception {
+ String fieldName = "fieldName";
+ String value = "Testing123";
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ JsonObject object = new JsonObject();
+ object.addProperty(fieldName, value);
+
+ escapingVisitor.visitObjectMember(object, fieldName, primitive, true);
+ assertEquals(value, stubVisitor.primitiveReceived.getAsString());
+ }
+
+ public void testStringFieldVisitationEscapingRequired() throws
Exception {
+ String fieldName = "fieldName";
+ String value = "Testing\"123";
+ JsonPrimitive primitive = new JsonPrimitive(value);
+ JsonObject object = new JsonObject();
+ object.addProperty(fieldName, value);
+
+ escapingVisitor.visitObjectMember(object, fieldName, primitive, true);
+ assertEquals(Escaper.escapeJsonString(value), stubVisitor.primitiveReceived.getAsString());
+ }
+
+ private static class StubbedJsonElementVisitor implements
JsonElementVisitor {
+ public JsonPrimitive primitiveReceived;
+
+ public void endArray(JsonArray array) {
+ // Do nothing
+ }
+
+ public void endObject(JsonObject object) {
+ // Do nothing
+ }
+
+ public void startArray(JsonArray array) {
+ // Do nothing
+ }
+
+ public void startObject(JsonObject object) {
+ // Do nothing
+ }
+
+ public void visitArrayMember(JsonArray parent, JsonPrimitive
member, boolean isFirst) {
+ primitiveReceived = member;
+ }
+
+ public void visitArrayMember(JsonArray parent, JsonArray member,
boolean isFirst) {
+ // Do nothing
+ }
+
+ public void visitArrayMember(JsonArray parent, JsonObject member,
boolean isFirst) {
+ // Do nothing
+ }
+
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonPrimitive member,
boolean isFirst) {
+ primitiveReceived = member;
+ }
+
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonArray member,
boolean isFirst) {
+ // Do nothing
+ }
+
+ public void visitObjectMember(
+ JsonObject parent, String memberName, JsonObject member,
boolean isFirst) {
+ // Do nothing
+ }
+
+ public void visitPrimitive(JsonPrimitive primitive) {
+ primitiveReceived = primitive;
+ }
+ }
+}

Modified: trunk/src/test/java/com/google/gson/JsonSerializerTest.java
==============================================================================
--- trunk/src/test/java/com/google/gson/JsonSerializerTest.java (original)
+++ trunk/src/test/java/com/google/gson/JsonSerializerTest.java Tue Jun
17 14:35:43 2008
@@ -407,8 +407,8 @@
assertTrue(json.contains("\"a\":1"));
assertTrue(json.contains("\"b\":2"));
}
-
- public void testExposeAnnotation() {
+
+ public void testExposeAnnotation() throws Exception {
// First test that Gson works without the expose annotation as well
ClassWithExposedFields target = new ClassWithExposedFields();
assertEquals(target.getExpectedJsonWithoutAnnotations(), gson.toJson(target));
@@ -416,5 +416,26 @@
// Now recreate gson with the proper setting
gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testSingleQuoteInStrings() throws Exception {
+ String valueWithQuotes = "beforeQuote'afterQuote";
+ String jsonRepresentation = gson.toJson(valueWithQuotes);
+ assertEquals(valueWithQuotes, gson.fromJson(jsonRepresentation, String.class));
+ }
+
+ public void testEscapingQuotesInStrings() throws Exception {
+ String valueWithQuotes = "beforeQuote\"afterQuote";
+ String jsonRepresentation = gson.toJson(valueWithQuotes);
+ String target = gson.fromJson(jsonRepresentation, String.class);
+ assertEquals(valueWithQuotes, target);
+ }
+
+ public void testEscapingQuotesInStringArray() throws Exception {
+ String[] valueWithQuotes = { "beforeQuote\"afterQuote" };
+ String jsonRepresentation = gson.toJson(valueWithQuotes);
+ String[] target = gson.fromJson(jsonRepresentation, String[].class);
+ assertEquals(1, target.length);
+ assertEquals(valueWithQuotes[0], target[0]);
}
}

Reply all
Reply to author
Forward
0 new messages