Implement all but the most difficult part of graph type adapter's deserialization. The catch is we want to return an instance that we don't have yet. It's on the stack, but we don't have a handle to it because it's inside the 'nextTypeAdapter' who is busy populating its fields.
This commit is contained in:
parent
6ec6caa49d
commit
4057b98bab
@ -17,14 +17,18 @@
|
||||
package com.google.gson.graph;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.bind.JsonTreeReader;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
@ -55,6 +59,7 @@ public final class GraphTypeAdapterFactory implements TypeAdapterFactory {
|
||||
}
|
||||
|
||||
final TypeAdapter<T> typeAdapter = gson.getNextAdapter(this, type);
|
||||
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
|
||||
return new TypeAdapter<T>() {
|
||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
||||
if (value == null) {
|
||||
@ -63,62 +68,141 @@ public final class GraphTypeAdapterFactory implements TypeAdapterFactory {
|
||||
}
|
||||
|
||||
Graph graph = graphThreadLocal.get();
|
||||
boolean writeEntireGraph = false;
|
||||
|
||||
// this is the top-level object in the graph; write the whole graph recursively
|
||||
if (graph == null) {
|
||||
graph = new Graph();
|
||||
graphThreadLocal.set(graph);
|
||||
Element element = new Element<T>(value, graph.elements.size() + 1, typeAdapter);
|
||||
graph.elements.put(value, element);
|
||||
writeEntireGraph = true;
|
||||
graph = new Graph(new IdentityHashMap<Object, Element<?>>());
|
||||
}
|
||||
|
||||
Element<T> element = (Element<T>) graph.map.get(value);
|
||||
if (element == null) {
|
||||
element = new Element<T>(value, graph.nextName(), typeAdapter, null);
|
||||
graph.map.put(value, element);
|
||||
graph.queue.add(element);
|
||||
}
|
||||
|
||||
out.beginObject();
|
||||
Element current;
|
||||
while ((current = graph.queue.poll()) != null) {
|
||||
out.name(current.getName());
|
||||
current.write(out);
|
||||
if (writeEntireGraph) {
|
||||
graphThreadLocal.set(graph);
|
||||
try {
|
||||
out.beginObject();
|
||||
Element<?> current;
|
||||
while ((current = graph.queue.poll()) != null) {
|
||||
out.name(current.id);
|
||||
current.write(out);
|
||||
}
|
||||
out.endObject();
|
||||
} finally {
|
||||
graphThreadLocal.remove();
|
||||
}
|
||||
out.endObject();
|
||||
graphThreadLocal.remove();
|
||||
|
||||
// this is an element nested in the graph; just reference it by ID
|
||||
} else {
|
||||
Element element = graph.elements.get(value);
|
||||
if (element == null) {
|
||||
element = new Element<T>(value, graph.elements.size() + 1, typeAdapter);
|
||||
graph.elements.put(value, element);
|
||||
graph.queue.add(element);
|
||||
}
|
||||
out.value(element.getName());
|
||||
out.value(element.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public T read(JsonReader in) throws IOException {
|
||||
// TODO:
|
||||
return null;
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
|
||||
String currentName = null;
|
||||
Graph graph = graphThreadLocal.get();
|
||||
boolean readEntireGraph = false;
|
||||
|
||||
if (graph == null) {
|
||||
graph = new Graph(new HashMap<Object, Element<?>>());
|
||||
readEntireGraph = true;
|
||||
|
||||
// read the entire tree into memory
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
if (currentName == null) {
|
||||
currentName = name;
|
||||
}
|
||||
JsonElement element = elementAdapter.read(in);
|
||||
graph.map.put(name, new Element<T>(null, name, typeAdapter, element));
|
||||
}
|
||||
in.endObject();
|
||||
} else {
|
||||
currentName = in.nextString();
|
||||
}
|
||||
|
||||
if (readEntireGraph) {
|
||||
graphThreadLocal.set(graph);
|
||||
}
|
||||
try {
|
||||
Element<T> element = (Element<T>) graph.map.get(currentName);
|
||||
if (element.value == null) {
|
||||
element.typeAdapter = typeAdapter;
|
||||
element.read();
|
||||
}
|
||||
return element.value;
|
||||
} finally {
|
||||
if (readEntireGraph) {
|
||||
graphThreadLocal.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static class Graph {
|
||||
private final Map<Object, Element> elements = new IdentityHashMap<Object, Element>();
|
||||
/**
|
||||
* The graph elements. On serialization keys are objects (using an identity
|
||||
* hash map) and on deserialization keys are the string names (using a
|
||||
* standard hash map).
|
||||
*/
|
||||
private final Map<Object, Element<?>> map;
|
||||
private final Queue<Element> queue = new LinkedList<Element>();
|
||||
|
||||
private Graph(Map<Object, Element<?>> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique name for an element to be inserted into the graph.
|
||||
*/
|
||||
public String nextName() {
|
||||
return "0x" + Integer.toHexString(map.size() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static class Element<T> {
|
||||
private final T value;
|
||||
private final int id;
|
||||
private final TypeAdapter<T> typeAdapter;
|
||||
Element(T value, int id, TypeAdapter<T> typeAdapter) {
|
||||
private final String id;
|
||||
private T value;
|
||||
private TypeAdapter<T> typeAdapter;
|
||||
private final JsonElement element;
|
||||
private boolean reading = false;
|
||||
|
||||
Element(T value, String id, TypeAdapter<T> typeAdapter, JsonElement element) {
|
||||
this.value = value;
|
||||
this.id = id;
|
||||
this.typeAdapter = typeAdapter;
|
||||
this.element = element;
|
||||
}
|
||||
private String getName() {
|
||||
return "0x" + Integer.toHexString(id);
|
||||
}
|
||||
|
||||
private void write(JsonWriter out) throws IOException {
|
||||
typeAdapter.write(out, value);
|
||||
}
|
||||
|
||||
private void read() throws IOException {
|
||||
if (reading) {
|
||||
// TODO: this currently fails because we don't have the instance we want yet
|
||||
System.out.println("ALREADY READING " + id);
|
||||
return;
|
||||
}
|
||||
reading = true;
|
||||
try {
|
||||
// TODO: use TypeAdapter.fromJsonTree() when that's public
|
||||
value = typeAdapter.read(new JsonTreeReader(element));
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("non-null value deserialized to null: " + element);
|
||||
}
|
||||
} finally {
|
||||
reading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.internal.bind.JsonElementWriter;
|
||||
import com.google.gson.internal.bind.JsonTreeWriter;
|
||||
import com.google.gson.internal.bind.JsonTreeReader;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
@ -252,7 +252,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
// TODO: remove this when TypeAdapter.toJsonTree() is public
|
||||
private JsonElement toJsonTree(TypeAdapter<T> delegate, T value) {
|
||||
try {
|
||||
JsonElementWriter jsonWriter = new JsonElementWriter();
|
||||
JsonTreeWriter jsonWriter = new JsonTreeWriter();
|
||||
jsonWriter.setLenient(true);
|
||||
delegate.write(jsonWriter, value);
|
||||
return jsonWriter.get();
|
||||
|
@ -21,7 +21,7 @@ import com.google.gson.GsonBuilder;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public final class GraphTypeAdapterFactoryTest extends TestCase {
|
||||
public void testBasicCycle() {
|
||||
public void testSerialization() {
|
||||
Roshambo rock = new Roshambo("ROCK");
|
||||
Roshambo scissors = new Roshambo("SCISSORS");
|
||||
Roshambo paper = new Roshambo("PAPER");
|
||||
@ -39,6 +39,24 @@ public final class GraphTypeAdapterFactoryTest extends TestCase {
|
||||
gson.toJson(rock).replace('\"', '\''));
|
||||
}
|
||||
|
||||
public void testDeserialization() {
|
||||
String json = "{'0x1':{'name':'ROCK','beats':'0x2'}," +
|
||||
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
|
||||
"'0x3':{'name':'PAPER','beats':'0x1'}}";
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(GraphTypeAdapterFactory.of(Roshambo.class))
|
||||
.create();
|
||||
|
||||
Roshambo rock = gson.fromJson(json, Roshambo.class);
|
||||
assertEquals("ROCK", rock.name);
|
||||
Roshambo scissors = rock.beats;
|
||||
assertEquals("SCISSORS", scissors.name);
|
||||
Roshambo paper = scissors.beats;
|
||||
assertEquals("PAPER", paper.name);
|
||||
assertSame(rock, paper.beats); // TODO: currently fails
|
||||
}
|
||||
|
||||
static class Roshambo {
|
||||
String name;
|
||||
Roshambo beats;
|
||||
|
@ -26,7 +26,7 @@ import com.google.gson.internal.bind.BigIntegerTypeAdapter;
|
||||
import com.google.gson.internal.bind.CollectionTypeAdapterFactory;
|
||||
import com.google.gson.internal.bind.DateTypeAdapter;
|
||||
import com.google.gson.internal.bind.JsonTreeReader;
|
||||
import com.google.gson.internal.bind.JsonElementWriter;
|
||||
import com.google.gson.internal.bind.JsonTreeWriter;
|
||||
import com.google.gson.internal.bind.MapTypeAdapterFactory;
|
||||
import com.google.gson.internal.bind.ObjectTypeAdapter;
|
||||
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
|
||||
@ -437,7 +437,7 @@ public final class Gson {
|
||||
* @since 1.4
|
||||
*/
|
||||
public JsonElement toJsonTree(Object src, Type typeOfSrc) {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
toJson(src, typeOfSrc, writer);
|
||||
return writer.get();
|
||||
}
|
||||
|
@ -16,9 +16,8 @@
|
||||
|
||||
package com.google.gson;
|
||||
|
||||
import com.google.gson.internal.bind.JsonElementWriter;
|
||||
import com.google.gson.internal.bind.JsonTreeWriter;
|
||||
import com.google.gson.internal.bind.JsonTreeReader;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
@ -224,7 +223,7 @@ public abstract class TypeAdapter<T> {
|
||||
*/
|
||||
/*public*/ final JsonElement toJsonTree(T value) {
|
||||
try {
|
||||
JsonElementWriter jsonWriter = new JsonElementWriter();
|
||||
JsonTreeWriter jsonWriter = new JsonTreeWriter();
|
||||
jsonWriter.setLenient(true);
|
||||
write(jsonWriter, value);
|
||||
return jsonWriter.get();
|
||||
|
@ -30,7 +30,7 @@ import java.util.List;
|
||||
/**
|
||||
* This writer creates a JsonElement.
|
||||
*/
|
||||
public final class JsonElementWriter extends JsonWriter {
|
||||
public final class JsonTreeWriter extends JsonWriter {
|
||||
private static final Writer UNWRITABLE_WRITER = new Writer() {
|
||||
@Override public void write(char[] buffer, int offset, int counter) {
|
||||
throw new AssertionError();
|
||||
@ -54,7 +54,7 @@ public final class JsonElementWriter extends JsonWriter {
|
||||
/** the JSON element constructed by this writer. */
|
||||
private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;
|
||||
|
||||
public JsonElementWriter() {
|
||||
public JsonTreeWriter() {
|
||||
super(UNWRITABLE_WRITER);
|
||||
}
|
||||
|
@ -266,7 +266,7 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||
// TODO: remove this when TypeAdapter.toJsonTree() is public
|
||||
private static <T> JsonElement toJsonTree(TypeAdapter<T> typeAdapter, T value) {
|
||||
try {
|
||||
JsonElementWriter jsonWriter = new JsonElementWriter();
|
||||
JsonTreeWriter jsonWriter = new JsonTreeWriter();
|
||||
jsonWriter.setLenient(true);
|
||||
typeAdapter.write(jsonWriter, value);
|
||||
return jsonWriter.get();
|
||||
|
@ -32,7 +32,6 @@ import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -20,9 +20,9 @@ import com.google.gson.JsonNull;
|
||||
import java.io.IOException;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public final class JsonElementWriterTest extends TestCase {
|
||||
public final class JsonTreeWriterTest extends TestCase {
|
||||
public void testArray() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.beginArray();
|
||||
writer.value(1);
|
||||
writer.value(2);
|
||||
@ -32,7 +32,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testNestedArray() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.beginArray();
|
||||
writer.beginArray();
|
||||
writer.endArray();
|
||||
@ -45,7 +45,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testObject() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.beginObject();
|
||||
writer.name("A").value(1);
|
||||
writer.name("B").value(2);
|
||||
@ -54,7 +54,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testNestedObject() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.beginObject();
|
||||
writer.name("A");
|
||||
writer.beginObject();
|
||||
@ -70,7 +70,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testWriteAfterClose() throws Exception {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setLenient(true);
|
||||
writer.beginArray();
|
||||
writer.value("A");
|
||||
@ -84,7 +84,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testPrematureClose() throws Exception {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setLenient(true);
|
||||
writer.beginArray();
|
||||
try {
|
||||
@ -95,7 +95,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testSerializeNullsFalse() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setSerializeNulls(false);
|
||||
writer.beginObject();
|
||||
writer.name("A");
|
||||
@ -105,7 +105,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testSerializeNullsTrue() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setSerializeNulls(true);
|
||||
writer.beginObject();
|
||||
writer.name("A");
|
||||
@ -115,12 +115,12 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testEmptyWriter() {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
assertEquals(JsonNull.INSTANCE, writer.get());
|
||||
}
|
||||
|
||||
public void testLenientNansAndInfinities() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setLenient(true);
|
||||
writer.beginArray();
|
||||
writer.value(Double.NaN);
|
||||
@ -131,7 +131,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testStrictNansAndInfinities() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setLenient(false);
|
||||
writer.beginArray();
|
||||
try {
|
||||
@ -152,7 +152,7 @@ public final class JsonElementWriterTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testStrictBoxedNansAndInfinities() throws IOException {
|
||||
JsonElementWriter writer = new JsonElementWriter();
|
||||
JsonTreeWriter writer = new JsonTreeWriter();
|
||||
writer.setLenient(false);
|
||||
writer.beginArray();
|
||||
try {
|
Loading…
Reference in New Issue
Block a user