Issue 797: removed patches to gson and refactored raw json strategy thanks, @jessewilson

This commit is contained in:
Adrian Cole 2012-01-02 14:47:35 -08:00
parent ce657bbe08
commit f54340e6d1
9 changed files with 215 additions and 898 deletions

View File

@ -142,8 +142,7 @@
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>com.google.gson;-split-package:=merge-first,
org.jclouds.*;version=${project.version}</Export-Package>
<Export-Package>org.jclouds.*;version=${project.version}</Export-Package>
<DynamicImport-Package>org.jclouds.*</DynamicImport-Package>
</instructions>
</configuration>

View File

@ -1,124 +0,0 @@
/*
* Copyright (C) 2010 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.internal;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonNull;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
import java.io.IOException;
import java.io.Writer;
import org.jclouds.json.internal.JsonLiteral;
/**
* Reads and writes GSON parse trees over streams.
*/
public final class Streams {
/**
* Takes a reader in any state and returns the next value as a JsonElement.
*/
public static JsonElement parse(JsonReader reader) throws JsonParseException {
boolean isEmpty = true;
try {
reader.peek();
isEmpty = false;
return TypeAdapters.JSON_ELEMENT.read(reader);
} catch (EOFException e) {
/*
* For compatibility with JSON 1.5 and earlier, we return a JsonNull for
* empty documents instead of throwing.
*/
if (isEmpty) {
return JsonNull.INSTANCE;
}
throw new JsonIOException(e);
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
throw new JsonIOException(e);
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
/**
* Writes the JSON element to the writer, recursively.
*/
public static void write(JsonElement element, JsonWriter writer) throws IOException {
//BEGIN JCLOUDS PATCH
// * @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326"/>
if (element instanceof JsonLiteral ) {
writer.value(JsonLiteral.class.cast(element));
//END JCLOUDS PATCH
} else {
TypeAdapters.JSON_ELEMENT.write(writer, element);
}
}
public static Writer writerForAppendable(Appendable appendable) {
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
}
/**
* Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer}
* is used.
*/
private static class AppendableWriter extends Writer {
private final Appendable appendable;
private final CurrentWrite currentWrite = new CurrentWrite();
private AppendableWriter(Appendable appendable) {
this.appendable = appendable;
}
@Override public void write(char[] chars, int offset, int length) throws IOException {
currentWrite.chars = chars;
appendable.append(currentWrite, offset, offset + length);
}
@Override public void write(int i) throws IOException {
appendable.append((char) i);
}
@Override public void flush() {}
@Override public void close() {}
/**
* A mutable char sequence pointing at a single char[].
*/
static class CurrentWrite implements CharSequence {
char[] chars;
public int length() {
return chars.length;
}
public char charAt(int i) {
return chars[i];
}
public CharSequence subSequence(int start, int end) {
return new String(chars, start, end - start);
}
}
}
}

View File

@ -1,629 +0,0 @@
/*
* Copyright (C) 2010 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.stream;
import java.io.Closeable;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.jclouds.json.internal.JsonLiteral;
/**
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
* encoded value to a stream, one token at a time. The stream includes both
* literal values (strings, numbers, booleans and nulls) as well as the begin
* and end delimiters of objects and arrays.
*
* <h3>Encoding JSON</h3>
* To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
* document must contain one top-level array or object. Call methods on the
* writer as you walk the structure's contents, nesting arrays and objects as
* necessary:
* <ul>
* <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
* Write each of the array's elements with the appropriate {@link #value}
* methods or by nesting other arrays and objects. Finally close the array
* using {@link #endArray()}.
* <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
* Write each of the object's properties by alternating calls to
* {@link #name} with the property's value. Write property values with the
* appropriate {@link #value} method or by nesting other objects or arrays.
* Finally close the object using {@link #endObject()}.
* </ul>
*
* <h3>Example</h3>
* Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
* [
* {
* "id": 912345678901,
* "text": "How do I stream JSON in Java?",
* "geo": null,
* "user": {
* "name": "json_newb",
* "followers_count": 41
* }
* },
* {
* "id": 912345678902,
* "text": "@json_newb just use JsonWriter!",
* "geo": [50.454722, -104.606667],
* "user": {
* "name": "jesse",
* "followers_count": 2
* }
* }
* ]}</pre>
* This code encodes the above structure: <pre> {@code
* public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
* JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
* writer.setIndentSpaces(4);
* writeMessagesArray(writer, messages);
* writer.close();
* }
*
* public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
* writer.beginArray();
* for (Message message : messages) {
* writeMessage(writer, message);
* }
* writer.endArray();
* }
*
* public void writeMessage(JsonWriter writer, Message message) throws IOException {
* writer.beginObject();
* writer.name("id").value(message.getId());
* writer.name("text").value(message.getText());
* if (message.getGeo() != null) {
* writer.name("geo");
* writeDoublesArray(writer, message.getGeo());
* } else {
* writer.name("geo").nullValue();
* }
* writer.name("user");
* writeUser(writer, message.getUser());
* writer.endObject();
* }
*
* public void writeUser(JsonWriter writer, User user) throws IOException {
* writer.beginObject();
* writer.name("name").value(user.getName());
* writer.name("followers_count").value(user.getFollowersCount());
* writer.endObject();
* }
*
* public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
* writer.beginArray();
* for (Double value : doubles) {
* writer.value(value);
* }
* writer.endArray();
* }}</pre>
*
* <p>Each {@code JsonWriter} may be used to write a single JSON stream.
* Instances of this class are not thread safe. Calls that would result in a
* malformed JSON string will fail with an {@link IllegalStateException}.
*
* @author Jesse Wilson
* @since 1.6
*/
public class JsonWriter implements Closeable {
/** The output data, containing at most one top-level array or object. */
private final Writer out;
private final List<JsonScope> stack = new ArrayList<JsonScope>();
{
stack.add(JsonScope.EMPTY_DOCUMENT);
}
/**
* A string containing a full set of spaces for a single level of
* indentation, or null for no pretty printing.
*/
private String indent;
/**
* The name/value separator; either ":" or ": ".
*/
private String separator = ":";
private boolean lenient;
private boolean htmlSafe;
private String deferredName;
private boolean serializeNulls = true;
/**
* Creates a new instance that writes a JSON-encoded stream to {@code out}.
* For best performance, ensure {@link Writer} is buffered; wrapping in
* {@link java.io.BufferedWriter BufferedWriter} if necessary.
*/
public JsonWriter(Writer out) {
if (out == null) {
throw new NullPointerException("out == null");
}
this.out = out;
}
/**
* Sets the indentation string to be repeated for each level of indentation
* in the encoded document. If {@code indent.isEmpty()} the encoded document
* will be compact. Otherwise the encoded document will be more
* human-readable.
*
* @param indent a string containing only whitespace.
*/
public final void setIndent(String indent) {
if (indent.length() == 0) {
this.indent = null;
this.separator = ":";
} else {
this.indent = indent;
this.separator = ": ";
}
}
/**
* Configure this writer to relax its syntax rules. By default, this writer
* only emits well-formed JSON as specified by <a
* href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer
* to lenient permits the following:
* <ul>
* <li>Top-level values of any type. With strict writing, the top-level
* value must be an object or an array.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* </ul>
*/
public final void setLenient(boolean lenient) {
this.lenient = lenient;
}
/**
* Returns true if this writer has relaxed syntax rules.
*/
public boolean isLenient() {
return lenient;
}
/**
* Configure this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
* {@code &} and {@code =} before writing them to the stream. Without this
* setting, your XML/HTML encoder should replace these characters with the
* corresponding escape sequences.
*/
public final void setHtmlSafe(boolean htmlSafe) {
this.htmlSafe = htmlSafe;
}
/**
* Returns true if this writer writes JSON that's safe for inclusion in HTML
* and XML documents.
*/
public final boolean isHtmlSafe() {
return htmlSafe;
}
/**
* Sets whether object members are serialized when their value is null.
* This has no impact on array elements. The default is true.
*/
public final void setSerializeNulls(boolean serializeNulls) {
this.serializeNulls = serializeNulls;
}
/**
* Returns true if object members are serialized when their value is null.
* This has no impact on array elements. The default is true.
*/
public final boolean getSerializeNulls() {
return serializeNulls;
}
/**
* Begins encoding a new array. Each call to this method must be paired with
* a call to {@link #endArray}.
*
* @return this writer.
*/
public JsonWriter beginArray() throws IOException {
writeDeferredName();
return open(JsonScope.EMPTY_ARRAY, "[");
}
/**
* Ends encoding the current array.
*
* @return this writer.
*/
public JsonWriter endArray() throws IOException {
return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
}
/**
* Begins encoding a new object. Each call to this method must be paired
* with a call to {@link #endObject}.
*
* @return this writer.
*/
public JsonWriter beginObject() throws IOException {
writeDeferredName();
return open(JsonScope.EMPTY_OBJECT, "{");
}
/**
* Ends encoding the current object.
*
* @return this writer.
*/
public JsonWriter endObject() throws IOException {
return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
}
/**
* Enters a new scope by appending any necessary whitespace and the given
* bracket.
*/
private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
beforeValue(true);
stack.add(empty);
out.write(openBracket);
return this;
}
/**
* Closes the current scope by appending any necessary whitespace and the
* given bracket.
*/
private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
throws IOException {
JsonScope context = peek();
if (context != nonempty && context != empty) {
throw new IllegalStateException("Nesting problem: " + stack);
}
if (deferredName != null) {
throw new IllegalStateException("Dangling name: " + deferredName);
}
stack.remove(stack.size() - 1);
if (context == nonempty) {
newline();
}
out.write(closeBracket);
return this;
}
/**
* Returns the value on the top of the stack.
*/
private JsonScope peek() {
return stack.get(stack.size() - 1);
}
/**
* Replace the value on the top of the stack with the given value.
*/
private void replaceTop(JsonScope topOfStack) {
stack.set(stack.size() - 1, topOfStack);
}
/**
* Encodes the property name.
*
* @param name the name of the forthcoming value. May not be null.
* @return this writer.
*/
public JsonWriter name(String name) throws IOException {
if (name == null) {
throw new NullPointerException("name == null");
}
if (deferredName != null) {
throw new IllegalStateException();
}
deferredName = name;
return this;
}
private void writeDeferredName() throws IOException {
if (deferredName != null) {
beforeName();
string(deferredName);
deferredName = null;
}
}
/**
* Encodes {@code value}.
*
* @param value the literal string value, or null to encode a null literal.
* @return this writer.
*/
public JsonWriter value(String value) throws IOException {
if (value == null) {
return nullValue();
}
writeDeferredName();
beforeValue(false);
string(value);
return this;
}
/**
* Encodes {@code null}.
*
* @return this writer.
*/
public JsonWriter nullValue() throws IOException {
if (deferredName != null) {
if (serializeNulls) {
writeDeferredName();
} else {
deferredName = null;
return this; // skip the name and the value
}
}
beforeValue(false);
out.write("null");
return this;
}
//BEGIN JCLOUDS PATCH
//* @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326"/>
/**
* Writes {@code value} literally
*
* @return this writer.
*/
public JsonWriter value(JsonLiteral value) throws IOException {
if (value == null) {
return nullValue();
}
writeDeferredName();
beforeValue(false);
out.write(value.toString());
return this;
}
//END JCLOUDS PATCH
/**
* Encodes {@code value}.
*
* @return this writer.
*/
public JsonWriter value(boolean value) throws IOException {
writeDeferredName();
beforeValue(false);
out.write(value ? "true" : "false");
return this;
}
/**
* Encodes {@code value}.
*
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return this writer.
*/
public JsonWriter value(double value) throws IOException {
if (Double.isNaN(value) || Double.isInfinite(value)) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
writeDeferredName();
beforeValue(false);
out.append(Double.toString(value));
return this;
}
/**
* Encodes {@code value}.
*
* @return this writer.
*/
public JsonWriter value(long value) throws IOException {
writeDeferredName();
beforeValue(false);
out.write(Long.toString(value));
return this;
}
/**
* Encodes {@code value}.
*
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return this writer.
*/
public JsonWriter value(Number value) throws IOException {
if (value == null) {
return nullValue();
}
writeDeferredName();
String string = value.toString();
if (!lenient
&& (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue(false);
out.append(string);
return this;
}
/**
* Ensures all buffered data is written to the underlying {@link Writer}
* and flushes that writer.
*/
public void flush() throws IOException {
out.flush();
}
/**
* Flushes and closes this writer and the underlying {@link Writer}.
*
* @throws IOException if the JSON document is incomplete.
*/
public void close() throws IOException {
out.close();
if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
throw new IOException("Incomplete document");
}
}
private void string(String value) throws IOException {
out.write("\"");
for (int i = 0, length = value.length(); i < length; i++) {
char c = value.charAt(i);
/*
* From RFC 4627, "All Unicode characters may be placed within the
* quotation marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)."
*
* We also escape '\u2028' and '\u2029', which JavaScript interprets as
* newline characters. This prevents eval() from failing with a syntax
* error. http://code.google.com/p/google-gson/issues/detail?id=341
*/
switch (c) {
case '"':
case '\\':
out.write('\\');
out.write(c);
break;
case '\t':
out.write("\\t");
break;
case '\b':
out.write("\\b");
break;
case '\n':
out.write("\\n");
break;
case '\r':
out.write("\\r");
break;
case '\f':
out.write("\\f");
break;
case '<':
case '>':
case '&':
case '=':
case '\'':
if (htmlSafe) {
out.write(String.format("\\u%04x", (int) c));
} else {
out.write(c);
}
break;
case '\u2028':
case '\u2029':
out.write(String.format("\\u%04x", (int) c));
break;
default:
if (c <= 0x1F) {
out.write(String.format("\\u%04x", (int) c));
} else {
out.write(c);
}
break;
}
}
out.write("\"");
}
private void newline() throws IOException {
if (indent == null) {
return;
}
out.write("\n");
for (int i = 1; i < stack.size(); i++) {
out.write(indent);
}
}
/**
* Inserts any necessary separators and whitespace before a name. Also
* adjusts the stack to expect the name's value.
*/
private void beforeName() throws IOException {
JsonScope context = peek();
if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
out.write(',');
} else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
throw new IllegalStateException("Nesting problem: " + stack);
}
newline();
replaceTop(JsonScope.DANGLING_NAME);
}
/**
* Inserts any necessary separators and whitespace before a literal value,
* inline array, or inline object. Also adjusts the stack to expect either a
* closing bracket or another element.
*
* @param root true if the value is a new array or object, the two values
* permitted as top-level elements.
*/
private void beforeValue(boolean root) throws IOException {
switch (peek()) {
case EMPTY_DOCUMENT: // first in document
if (!lenient && !root) {
throw new IllegalStateException(
"JSON must start with an array or an object.");
}
replaceTop(JsonScope.NONEMPTY_DOCUMENT);
break;
case EMPTY_ARRAY: // first in array
replaceTop(JsonScope.NONEMPTY_ARRAY);
newline();
break;
case NONEMPTY_ARRAY: // another in array
out.append(',');
newline();
break;
case DANGLING_NAME: // value for name
out.append(separator);
replaceTop(JsonScope.NONEMPTY_OBJECT);
break;
case NONEMPTY_DOCUMENT:
throw new IllegalStateException(
"JSON must have only one top-level value.");
default:
throw new IllegalStateException("Nesting problem: " + stack);
}
}
}

View File

@ -75,13 +75,18 @@ public class JsonBall implements java.io.Serializable, Comparable<String>, CharS
public JsonBall(long value) {
this.value = value + "";
}
public JsonBall(String value) {
this.value = quoteStringIfNotNumber(checkNotNull(value, "value"));
public JsonBall(boolean value) {
this.value = value + "";
}
static String quoteStringIfNotNumber(String in) {
if (Patterns.JSON_STRING_PATTERN.matcher(in).find() && !Patterns.JSON_NUMBER_PATTERN.matcher(in).find()) {
public JsonBall(String value) {
this.value = quoteStringIfNotNumberOrBoolean(checkNotNull(value, "value"));
}
static String quoteStringIfNotNumberOrBoolean(String in) {
if (Patterns.JSON_STRING_PATTERN.matcher(in).find() && !Patterns.JSON_NUMBER_PATTERN.matcher(in).find()
&& !Patterns.JSON_BOOLEAN_PATTERN.matcher(in).find()) {
return "\"" + in + "\"";
}
return in;

View File

@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Properties;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.jclouds.crypto.CryptoStreams;
@ -35,20 +36,14 @@ import org.jclouds.domain.JsonBall;
import org.jclouds.json.Json;
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
import org.jclouds.json.internal.GsonWrapper;
import org.jclouds.json.internal.JsonLiteral;
import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Maps;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.primitives.Bytes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
@ -57,7 +52,6 @@ import com.google.gson.stream.JsonWriter;
import com.google.inject.AbstractModule;
import com.google.inject.ImplementedBy;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
/**
* Contains logic for parsing objects from Strings.
@ -69,9 +63,9 @@ public class GsonModule extends AbstractModule {
@SuppressWarnings("rawtypes")
@Provides
@Singleton
Gson provideGson(JsonBallAdapter jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings)
throws ClassNotFoundException, Exception {
Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings)
throws ClassNotFoundException, Exception {
GsonBuilder builder = new GsonBuilder();
// simple (type adapters)
@ -80,43 +74,38 @@ public class GsonModule extends AbstractModule {
builder.registerTypeAdapter(new TypeToken<List<Byte>>() {
}.getType(), byteListAdapter.nullSafe());
builder.registerTypeAdapter(byte[].class, byteArrayAdapter.nullSafe());
builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe());
// complicated (serializers/deserializers as they need context to operate)
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue());
builder.registerTypeAdapter(JsonBall.class, jsonAdapter);
for (Map.Entry<Type, Object> binding : bindings.getBindings().entrySet()) {
builder.registerTypeAdapter(binding.getKey(), binding.getValue());
}
return builder.create();
}
// http://code.google.com/p/google-gson/issues/detail?id=326
@ImplementedBy(JsonBallAdapterImpl.class)
public static interface JsonBallAdapter extends JsonSerializer<JsonBall>, JsonDeserializer<JsonBall> {
}
@Singleton
public static class JsonBallAdapterImpl implements JsonBallAdapter {
public JsonElement serialize(JsonBall src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonLiteral(src);
}
public JsonBall deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new JsonBall(json.toString());
}
}
@ImplementedBy(CDateAdapter.class)
public static abstract class DateAdapter extends TypeAdapter<Date> {
}
@Provides
@Singleton
protected TypeAdapter<JsonBall> provideJsonBallAdapter(NullHackJsonBallAdapter in) {
return in;
}
public static class NullHackJsonBallAdapter extends NullHackJsonLiteralAdapter<JsonBall> {
@Override
protected JsonBall createJsonLiteralFromRawJson(String json) {
return new JsonBall(json);
}
}
@ImplementedBy(HexByteListAdapter.class)
public static abstract class ByteListAdapter extends TypeAdapter<List<Byte>> {
@ -185,15 +174,15 @@ public class GsonModule extends AbstractModule {
@Singleton
public static class PropertiesAdapter extends TypeAdapter<Properties> {
private final Json json;
private final Type mapType = new TypeLiteral<Map<String, String>>() {
}.getRawType();
private final Provider<Gson> gson;
private final TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>() {
};
@Inject
public PropertiesAdapter(Json json) {
this.json = json;
public PropertiesAdapter(Provider<Gson> gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, Properties value) throws IOException {
Builder<String, String> srcMap = ImmutableMap.<String, String> builder();
@ -201,7 +190,7 @@ public class GsonModule extends AbstractModule {
String propName = (String) propNames.nextElement();
srcMap.put(propName, value.getProperty(propName));
}
out.value(new JsonLiteral(json.toJson(srcMap.build(), mapType)));
gson.get().getAdapter(mapType).write(out, srcMap.build());
}
@Override
@ -209,8 +198,8 @@ public class GsonModule extends AbstractModule {
Properties props = new Properties();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
props.setProperty(in.nextString(), in.nextString());
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
props.setProperty(in.nextString(), in.nextString());
}
in.endObject();
return props;

View File

@ -1,45 +0,0 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.jclouds.json.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.gson.JsonElement;
/**
* The gson project use package to control access to their objects. However, this prevents us from
* doing valid work, like controlling the json emitted on a per-object basis. This is here to afford
* us to do this.
*
* @author Adrian Cole
* @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326"/>
*/
public final class JsonLiteral extends JsonElement {
private final CharSequence literal;
public JsonLiteral(CharSequence literal) {
this.literal = checkNotNull(literal, "literal");
}
@Override
public String toString() {
return literal.toString();
}
}

View File

@ -0,0 +1,135 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.jclouds.json.internal;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import javax.inject.Singleton;
import com.google.common.base.Throwables;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* writes or reads the literal json directly
*
* @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326" />
*
*/
@Singleton
public abstract class NullHackJsonLiteralAdapter<L> extends TypeAdapter<L> {
@Override
public L read(JsonReader reader) throws IOException {
return createJsonLiteralFromRawJson(TypeAdapters.JSON_ELEMENT.read(reader).toString());
}
/**
* User supplied type that holds json literally. Ex. number as {@code 8}, boolean as {@code true}
* , string as {@code "value"}, object as {@code , list {@code []}.
*/
protected abstract L createJsonLiteralFromRawJson(String json);
// the writer inside JsonWriter is not accessible currently
private static final Field outField;
static {
try {
outField = JsonWriter.class.getDeclaredField("out");
} catch (SecurityException e) {
throw Throwables.propagate(e);
} catch (NoSuchFieldException e) {
throw Throwables.propagate(e);
}
outField.setAccessible(true);
}
@Override
public void write(JsonWriter jsonWriter, L value) throws IOException {
Writer writer = getWriter(jsonWriter);
boolean serializeNulls = jsonWriter.getSerializeNulls();
try {
// we are using an implementation hack which depends on replacing null with the raw json
// supplied as a parameter. In this case, we must ensure we indeed serialize nulls.
NullReplacingWriter nullReplacingWriter = new NullReplacingWriter(writer, value.toString());
setWriter(jsonWriter, nullReplacingWriter);
jsonWriter.setSerializeNulls(true);
jsonWriter.nullValue();
} finally {
setWriter(jsonWriter, writer);
jsonWriter.setSerializeNulls(serializeNulls);
}
}
private Writer getWriter(JsonWriter arg0) {
try {
return Writer.class.cast(outField.get(arg0));
} catch (IllegalAccessException e) {
throw Throwables.propagate(e);
}
}
private void setWriter(JsonWriter arg0, Writer arg1) {
try {
outField.set(arg0, arg1);
} catch (IllegalAccessException e) {
throw Throwables.propagate(e);
}
}
private final class NullReplacingWriter extends Writer {
private final Writer delegate;
private final String nullReplacement;
public NullReplacingWriter(Writer delegate, String nullReplacement) {
this.delegate = delegate;
this.nullReplacement = nullReplacement;
}
@Override
public void write(char[] buffer, int offset, int count) throws IOException {
delegate.write(buffer, offset, count);
}
@Override
public void write(String s) throws IOException {
if (nullReplacement != null && s.equals("null")) {
s = nullReplacement;
}
super.write(s);
}
@Override
public void flush() throws IOException {
delegate.flush();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
}

View File

@ -54,7 +54,7 @@ public class JsonBallTest {
}
public void testHash() {
public void testObject() {
String json = "{\"tomcat6\":{\"ssl_port\":8433}}";
Map<String, JsonBall> map = ImmutableMap.<String, JsonBall> of("tomcat6", new JsonBall("{\"ssl_port\":8433}"));
@ -85,9 +85,20 @@ public class JsonBallTest {
}
public void testNumber() {
String json = "{\"number\":1}";
String json = "{\"number\":1.0}";
Map<String, JsonBall> map = ImmutableMap.<String, JsonBall> of("number", new JsonBall("1"));
Map<String, JsonBall> map = ImmutableMap.<String, JsonBall> of("number", new JsonBall(1.0));
assertEquals(handler.apply(new HttpResponse(200, "ok", Payloads.newStringPayload(json))), map);
assertEquals(mapper.toJson(map), json);
}
public void testBoolean() {
String json = "{\"boolean\":false}";
Map<String, JsonBall> map = ImmutableMap.<String, JsonBall> of("boolean", new JsonBall(false));
assertEquals(handler.apply(new HttpResponse(200, "ok", Payloads.newStringPayload(json))), map);
assertEquals(mapper.toJson(map), json);

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json;
package org.jclouds.json.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.testng.Assert.assertEquals;
@ -24,7 +24,7 @@ import static org.testng.Assert.assertEquals;
import java.lang.reflect.Type;
import java.util.Map;
import org.jclouds.json.internal.JsonLiteral;
import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
import org.jclouds.util.Patterns;
import org.testng.annotations.Test;
@ -32,26 +32,16 @@ import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonWriter;
/**
* Shows how we currently allow users to specify json literal types. Note this requires patches to
* {@link Streams} and {@link JsonWriter} in order to register and render JsonLiteral elements.
* Shows how we currently allow users to specify json literal types.
*
* @author Adrian Cole
* @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326"/>
* @see JsonLiteral
*/
@Test(testName = "GsonLiteralTest")
public class GsonLiteralTest {
@Test(testName = "NullHackJsonLiteralAdapterTest")
public class NullHackJsonLiteralAdapterTest {
/**
* User supplied type that holds json literally. Ex. number as {@code 8}, boolean as {@code true}
@ -65,14 +55,6 @@ public class GsonLiteralTest {
this.value = value + "";
}
public RawJson(int value) {
this.value = value + "";
}
public RawJson(long value) {
this.value = value + "";
}
public RawJson(boolean value) {
this.value = value + "";
}
@ -124,25 +106,19 @@ public class GsonLiteralTest {
}
/**
* writes or reads the literal directly and without formatting it in any way. Note this only
* works as {@link Streams} and {@link JsonWriter} are patched to register and render JsonLiteral
* elements.
* writes or reads the literal directly
*/
public static class RawJsonAdapter implements JsonSerializer<RawJson>, JsonDeserializer<RawJson> {
public static class RawJsonAdapter extends NullHackJsonLiteralAdapter<RawJson> {
public JsonElement serialize(RawJson src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonLiteral(src);
@Override
protected RawJson createJsonLiteralFromRawJson(String json) {
return new RawJson(json);
}
public RawJson deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new RawJson(json.toString());
}
}
}
// register the type adapter so that gson can serialize/deserialize to it
private Gson gson = new GsonBuilder().registerTypeAdapter(RawJson.class, new RawJsonAdapter()).create();
private Gson gsonAdapter = new GsonBuilder().registerTypeAdapter(RawJson.class, new RawJsonAdapter()).create();
Type type = new TypeToken<Map<String, RawJson>>() {
}.getType();
@ -151,8 +127,8 @@ public class GsonLiteralTest {
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("tomcat6", new RawJson("{\"ssl_port\":8433}"));
assertEquals(gson.fromJson(json, type), map);
assertEquals(gson.toJson(map), json);
assertEquals(gsonAdapter.toJson(map), json);
assertEquals(gsonAdapter.fromJson(json, type), map);
}
public void testList() {
@ -160,8 +136,8 @@ public class GsonLiteralTest {
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("list", new RawJson("[8431,8433]"));
assertEquals(gson.fromJson(json, type), map);
assertEquals(gson.toJson(map), json);
assertEquals(gsonAdapter.toJson(map), json);
assertEquals(gsonAdapter.fromJson(json, type), map);
}
public void testString() {
@ -169,8 +145,8 @@ public class GsonLiteralTest {
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("name", new RawJson("fooy"));
assertEquals(gson.fromJson(json, type), map);
assertEquals(gson.toJson(map), json);
assertEquals(gsonAdapter.toJson(map), json);
assertEquals(gsonAdapter.fromJson(json, type), map);
}
public void testNumber() {
@ -178,8 +154,8 @@ public class GsonLiteralTest {
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("number", new RawJson(1.0));
assertEquals(gson.fromJson(json, type), map);
assertEquals(gson.toJson(map), json);
assertEquals(gsonAdapter.toJson(map), json);
assertEquals(gsonAdapter.fromJson(json, type), map);
}
public void testBoolean() {
@ -187,7 +163,7 @@ public class GsonLiteralTest {
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("boolean", new RawJson(false));
assertEquals(gson.fromJson(json, type), map);
assertEquals(gson.toJson(map), json);
assertEquals(gsonAdapter.toJson(map), json);
assertEquals(gsonAdapter.fromJson(json, type), map);
}
}