mirror of https://github.com/apache/jclouds.git
Issue 797: removed patches to gson and refactored raw json strategy thanks, @jessewilson
This commit is contained in:
parent
ce657bbe08
commit
f54340e6d1
|
@ -142,8 +142,7 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<instructions>
|
<instructions>
|
||||||
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
|
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
|
||||||
<Export-Package>com.google.gson;-split-package:=merge-first,
|
<Export-Package>org.jclouds.*;version=${project.version}</Export-Package>
|
||||||
org.jclouds.*;version=${project.version}</Export-Package>
|
|
||||||
<DynamicImport-Package>org.jclouds.*</DynamicImport-Package>
|
<DynamicImport-Package>org.jclouds.*</DynamicImport-Package>
|
||||||
</instructions>
|
</instructions>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -75,13 +75,18 @@ public class JsonBall implements java.io.Serializable, Comparable<String>, CharS
|
||||||
public JsonBall(long value) {
|
public JsonBall(long value) {
|
||||||
this.value = value + "";
|
this.value = value + "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonBall(String value) {
|
public JsonBall(boolean value) {
|
||||||
this.value = quoteStringIfNotNumber(checkNotNull(value, "value"));
|
this.value = value + "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static String quoteStringIfNotNumber(String in) {
|
public JsonBall(String value) {
|
||||||
if (Patterns.JSON_STRING_PATTERN.matcher(in).find() && !Patterns.JSON_NUMBER_PATTERN.matcher(in).find()) {
|
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 + "\"";
|
||||||
}
|
}
|
||||||
return in;
|
return in;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.jclouds.crypto.CryptoStreams;
|
import org.jclouds.crypto.CryptoStreams;
|
||||||
|
@ -35,20 +36,14 @@ import org.jclouds.domain.JsonBall;
|
||||||
import org.jclouds.json.Json;
|
import org.jclouds.json.Json;
|
||||||
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
|
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
|
||||||
import org.jclouds.json.internal.GsonWrapper;
|
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;
|
||||||
import com.google.common.collect.ImmutableMap.Builder;
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.ImmutableMap.Builder;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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.TypeAdapter;
|
||||||
import com.google.gson.internal.JsonReaderInternalAccess;
|
import com.google.gson.internal.JsonReaderInternalAccess;
|
||||||
import com.google.gson.reflect.TypeToken;
|
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.AbstractModule;
|
||||||
import com.google.inject.ImplementedBy;
|
import com.google.inject.ImplementedBy;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import com.google.inject.TypeLiteral;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains logic for parsing objects from Strings.
|
* Contains logic for parsing objects from Strings.
|
||||||
|
@ -69,9 +63,9 @@ public class GsonModule extends AbstractModule {
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
Gson provideGson(JsonBallAdapter jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
|
Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
|
||||||
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings)
|
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings)
|
||||||
throws ClassNotFoundException, Exception {
|
throws ClassNotFoundException, Exception {
|
||||||
GsonBuilder builder = new GsonBuilder();
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
|
||||||
// simple (type adapters)
|
// simple (type adapters)
|
||||||
|
@ -80,43 +74,38 @@ public class GsonModule extends AbstractModule {
|
||||||
builder.registerTypeAdapter(new TypeToken<List<Byte>>() {
|
builder.registerTypeAdapter(new TypeToken<List<Byte>>() {
|
||||||
}.getType(), byteListAdapter.nullSafe());
|
}.getType(), byteListAdapter.nullSafe());
|
||||||
builder.registerTypeAdapter(byte[].class, byteArrayAdapter.nullSafe());
|
builder.registerTypeAdapter(byte[].class, byteArrayAdapter.nullSafe());
|
||||||
|
builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe());
|
||||||
|
|
||||||
// complicated (serializers/deserializers as they need context to operate)
|
// complicated (serializers/deserializers as they need context to operate)
|
||||||
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue());
|
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue());
|
||||||
builder.registerTypeAdapter(JsonBall.class, jsonAdapter);
|
|
||||||
|
|
||||||
for (Map.Entry<Type, Object> binding : bindings.getBindings().entrySet()) {
|
for (Map.Entry<Type, Object> binding : bindings.getBindings().entrySet()) {
|
||||||
builder.registerTypeAdapter(binding.getKey(), binding.getValue());
|
builder.registerTypeAdapter(binding.getKey(), binding.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.create();
|
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)
|
@ImplementedBy(CDateAdapter.class)
|
||||||
public static abstract class DateAdapter extends TypeAdapter<Date> {
|
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)
|
@ImplementedBy(HexByteListAdapter.class)
|
||||||
public static abstract class ByteListAdapter extends TypeAdapter<List<Byte>> {
|
public static abstract class ByteListAdapter extends TypeAdapter<List<Byte>> {
|
||||||
|
|
||||||
|
@ -185,15 +174,15 @@ public class GsonModule extends AbstractModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public static class PropertiesAdapter extends TypeAdapter<Properties> {
|
public static class PropertiesAdapter extends TypeAdapter<Properties> {
|
||||||
private final Json json;
|
private final Provider<Gson> gson;
|
||||||
private final Type mapType = new TypeLiteral<Map<String, String>>() {
|
private final TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>() {
|
||||||
}.getRawType();
|
};
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PropertiesAdapter(Json json) {
|
public PropertiesAdapter(Provider<Gson> gson) {
|
||||||
this.json = json;
|
this.gson = gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, Properties value) throws IOException {
|
public void write(JsonWriter out, Properties value) throws IOException {
|
||||||
Builder<String, String> srcMap = ImmutableMap.<String, String> builder();
|
Builder<String, String> srcMap = ImmutableMap.<String, String> builder();
|
||||||
|
@ -201,7 +190,7 @@ public class GsonModule extends AbstractModule {
|
||||||
String propName = (String) propNames.nextElement();
|
String propName = (String) propNames.nextElement();
|
||||||
srcMap.put(propName, value.getProperty(propName));
|
srcMap.put(propName, value.getProperty(propName));
|
||||||
}
|
}
|
||||||
out.value(new JsonLiteral(json.toJson(srcMap.build(), mapType)));
|
gson.get().getAdapter(mapType).write(out, srcMap.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -209,8 +198,8 @@ public class GsonModule extends AbstractModule {
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
|
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
|
||||||
props.setProperty(in.nextString(), in.nextString());
|
props.setProperty(in.nextString(), in.nextString());
|
||||||
}
|
}
|
||||||
in.endObject();
|
in.endObject();
|
||||||
return props;
|
return props;
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,7 +54,7 @@ public class JsonBallTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testHash() {
|
public void testObject() {
|
||||||
String json = "{\"tomcat6\":{\"ssl_port\":8433}}";
|
String json = "{\"tomcat6\":{\"ssl_port\":8433}}";
|
||||||
|
|
||||||
Map<String, JsonBall> map = ImmutableMap.<String, JsonBall> of("tomcat6", new JsonBall("{\"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() {
|
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(handler.apply(new HttpResponse(200, "ok", Payloads.newStringPayload(json))), map);
|
||||||
assertEquals(mapper.toJson(map), json);
|
assertEquals(mapper.toJson(map), json);
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.jclouds.json;
|
package org.jclouds.json.internal;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
|
@ -24,7 +24,7 @@ import static org.testng.Assert.assertEquals;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jclouds.json.internal.JsonLiteral;
|
import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
|
||||||
import org.jclouds.util.Patterns;
|
import org.jclouds.util.Patterns;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@ -32,26 +32,16 @@ import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows how we currently allow users to specify json literal types. Note this requires patches to
|
* Shows how we currently allow users to specify json literal types.
|
||||||
* {@link Streams} and {@link JsonWriter} in order to register and render JsonLiteral elements.
|
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
* @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326"/>
|
* @see <a href="http://code.google.com/p/google-gson/issues/detail?id=326"/>
|
||||||
* @see JsonLiteral
|
|
||||||
*/
|
*/
|
||||||
@Test(testName = "GsonLiteralTest")
|
@Test(testName = "NullHackJsonLiteralAdapterTest")
|
||||||
public class GsonLiteralTest {
|
public class NullHackJsonLiteralAdapterTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User supplied type that holds json literally. Ex. number as {@code 8}, boolean as {@code true}
|
* 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 + "";
|
this.value = value + "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public RawJson(int value) {
|
|
||||||
this.value = value + "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawJson(long value) {
|
|
||||||
this.value = value + "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawJson(boolean value) {
|
public RawJson(boolean value) {
|
||||||
this.value = 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
|
* writes or reads the literal directly
|
||||||
* works as {@link Streams} and {@link JsonWriter} are patched to register and render JsonLiteral
|
|
||||||
* elements.
|
|
||||||
*/
|
*/
|
||||||
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) {
|
@Override
|
||||||
return new JsonLiteral(src);
|
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
|
// 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>>() {
|
Type type = new TypeToken<Map<String, RawJson>>() {
|
||||||
}.getType();
|
}.getType();
|
||||||
|
|
||||||
|
@ -151,8 +127,8 @@ public class GsonLiteralTest {
|
||||||
|
|
||||||
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("tomcat6", new RawJson("{\"ssl_port\":8433}"));
|
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("tomcat6", new RawJson("{\"ssl_port\":8433}"));
|
||||||
|
|
||||||
assertEquals(gson.fromJson(json, type), map);
|
assertEquals(gsonAdapter.toJson(map), json);
|
||||||
assertEquals(gson.toJson(map), json);
|
assertEquals(gsonAdapter.fromJson(json, type), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testList() {
|
public void testList() {
|
||||||
|
@ -160,8 +136,8 @@ public class GsonLiteralTest {
|
||||||
|
|
||||||
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("list", new RawJson("[8431,8433]"));
|
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("list", new RawJson("[8431,8433]"));
|
||||||
|
|
||||||
assertEquals(gson.fromJson(json, type), map);
|
assertEquals(gsonAdapter.toJson(map), json);
|
||||||
assertEquals(gson.toJson(map), json);
|
assertEquals(gsonAdapter.fromJson(json, type), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testString() {
|
public void testString() {
|
||||||
|
@ -169,8 +145,8 @@ public class GsonLiteralTest {
|
||||||
|
|
||||||
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("name", new RawJson("fooy"));
|
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("name", new RawJson("fooy"));
|
||||||
|
|
||||||
assertEquals(gson.fromJson(json, type), map);
|
assertEquals(gsonAdapter.toJson(map), json);
|
||||||
assertEquals(gson.toJson(map), json);
|
assertEquals(gsonAdapter.fromJson(json, type), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNumber() {
|
public void testNumber() {
|
||||||
|
@ -178,8 +154,8 @@ public class GsonLiteralTest {
|
||||||
|
|
||||||
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("number", new RawJson(1.0));
|
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("number", new RawJson(1.0));
|
||||||
|
|
||||||
assertEquals(gson.fromJson(json, type), map);
|
assertEquals(gsonAdapter.toJson(map), json);
|
||||||
assertEquals(gson.toJson(map), json);
|
assertEquals(gsonAdapter.fromJson(json, type), map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBoolean() {
|
public void testBoolean() {
|
||||||
|
@ -187,7 +163,7 @@ public class GsonLiteralTest {
|
||||||
|
|
||||||
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("boolean", new RawJson(false));
|
Map<String, RawJson> map = ImmutableMap.<String, RawJson> of("boolean", new RawJson(false));
|
||||||
|
|
||||||
assertEquals(gson.fromJson(json, type), map);
|
assertEquals(gsonAdapter.toJson(map), json);
|
||||||
assertEquals(gson.toJson(map), json);
|
assertEquals(gsonAdapter.fromJson(json, type), map);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue