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:
Jesse Wilson 2011-12-30 08:27:24 +00:00
parent 6ec6caa49d
commit 4057b98bab
9 changed files with 156 additions and 56 deletions

View File

@ -17,14 +17,18 @@
package com.google.gson.graph; package com.google.gson.graph;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.LinkedList; import java.util.LinkedList;
@ -55,6 +59,7 @@ public final class GraphTypeAdapterFactory implements TypeAdapterFactory {
} }
final TypeAdapter<T> typeAdapter = gson.getNextAdapter(this, type); final TypeAdapter<T> typeAdapter = gson.getNextAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() { return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException { @Override public void write(JsonWriter out, T value) throws IOException {
if (value == null) { if (value == null) {
@ -63,62 +68,141 @@ public final class GraphTypeAdapterFactory implements TypeAdapterFactory {
} }
Graph graph = graphThreadLocal.get(); Graph graph = graphThreadLocal.get();
boolean writeEntireGraph = false;
// this is the top-level object in the graph; write the whole graph recursively
if (graph == null) { if (graph == null) {
graph = new Graph(); writeEntireGraph = true;
graphThreadLocal.set(graph); graph = new Graph(new IdentityHashMap<Object, Element<?>>());
Element element = new Element<T>(value, graph.elements.size() + 1, typeAdapter); }
graph.elements.put(value, 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); graph.queue.add(element);
}
out.beginObject(); if (writeEntireGraph) {
Element current; graphThreadLocal.set(graph);
while ((current = graph.queue.poll()) != null) { try {
out.name(current.getName()); out.beginObject();
current.write(out); 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 { } else {
Element element = graph.elements.get(value); out.value(element.id);
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());
} }
} }
@Override public T read(JsonReader in) throws IOException { @Override public T read(JsonReader in) throws IOException {
// TODO: if (in.peek() == JsonToken.NULL) {
return 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 { 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 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> { static class Element<T> {
private final T value; private final String id;
private final int id; private T value;
private final TypeAdapter<T> typeAdapter; private TypeAdapter<T> typeAdapter;
Element(T value, int id, 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.value = value;
this.id = id; this.id = id;
this.typeAdapter = typeAdapter; this.typeAdapter = typeAdapter;
this.element = element;
} }
private String getName() {
return "0x" + Integer.toHexString(id);
}
private void write(JsonWriter out) throws IOException { private void write(JsonWriter out) throws IOException {
typeAdapter.write(out, value); 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;
}
}
} }
} }

View File

@ -25,7 +25,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams; 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.internal.bind.JsonTreeReader;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; 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 // TODO: remove this when TypeAdapter.toJsonTree() is public
private JsonElement toJsonTree(TypeAdapter<T> delegate, T value) { private JsonElement toJsonTree(TypeAdapter<T> delegate, T value) {
try { try {
JsonElementWriter jsonWriter = new JsonElementWriter(); JsonTreeWriter jsonWriter = new JsonTreeWriter();
jsonWriter.setLenient(true); jsonWriter.setLenient(true);
delegate.write(jsonWriter, value); delegate.write(jsonWriter, value);
return jsonWriter.get(); return jsonWriter.get();

View File

@ -21,7 +21,7 @@ import com.google.gson.GsonBuilder;
import junit.framework.TestCase; import junit.framework.TestCase;
public final class GraphTypeAdapterFactoryTest extends TestCase { public final class GraphTypeAdapterFactoryTest extends TestCase {
public void testBasicCycle() { public void testSerialization() {
Roshambo rock = new Roshambo("ROCK"); Roshambo rock = new Roshambo("ROCK");
Roshambo scissors = new Roshambo("SCISSORS"); Roshambo scissors = new Roshambo("SCISSORS");
Roshambo paper = new Roshambo("PAPER"); Roshambo paper = new Roshambo("PAPER");
@ -39,6 +39,24 @@ public final class GraphTypeAdapterFactoryTest extends TestCase {
gson.toJson(rock).replace('\"', '\'')); 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 { static class Roshambo {
String name; String name;
Roshambo beats; Roshambo beats;

View File

@ -26,7 +26,7 @@ import com.google.gson.internal.bind.BigIntegerTypeAdapter;
import com.google.gson.internal.bind.CollectionTypeAdapterFactory; import com.google.gson.internal.bind.CollectionTypeAdapterFactory;
import com.google.gson.internal.bind.DateTypeAdapter; import com.google.gson.internal.bind.DateTypeAdapter;
import com.google.gson.internal.bind.JsonTreeReader; 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.MapTypeAdapterFactory;
import com.google.gson.internal.bind.ObjectTypeAdapter; import com.google.gson.internal.bind.ObjectTypeAdapter;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
@ -437,7 +437,7 @@ public final class Gson {
* @since 1.4 * @since 1.4
*/ */
public JsonElement toJsonTree(Object src, Type typeOfSrc) { public JsonElement toJsonTree(Object src, Type typeOfSrc) {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
toJson(src, typeOfSrc, writer); toJson(src, typeOfSrc, writer);
return writer.get(); return writer.get();
} }

View File

@ -16,9 +16,8 @@
package com.google.gson; 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.internal.bind.JsonTreeReader;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
@ -224,7 +223,7 @@ public abstract class TypeAdapter<T> {
*/ */
/*public*/ final JsonElement toJsonTree(T value) { /*public*/ final JsonElement toJsonTree(T value) {
try { try {
JsonElementWriter jsonWriter = new JsonElementWriter(); JsonTreeWriter jsonWriter = new JsonTreeWriter();
jsonWriter.setLenient(true); jsonWriter.setLenient(true);
write(jsonWriter, value); write(jsonWriter, value);
return jsonWriter.get(); return jsonWriter.get();

View File

@ -30,7 +30,7 @@ import java.util.List;
/** /**
* This writer creates a JsonElement. * 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() { private static final Writer UNWRITABLE_WRITER = new Writer() {
@Override public void write(char[] buffer, int offset, int counter) { @Override public void write(char[] buffer, int offset, int counter) {
throw new AssertionError(); throw new AssertionError();
@ -54,7 +54,7 @@ public final class JsonElementWriter extends JsonWriter {
/** the JSON element constructed by this writer. */ /** the JSON element constructed by this writer. */
private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?; private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;
public JsonElementWriter() { public JsonTreeWriter() {
super(UNWRITABLE_WRITER); super(UNWRITABLE_WRITER);
} }

View File

@ -266,7 +266,7 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
// TODO: remove this when TypeAdapter.toJsonTree() is public // TODO: remove this when TypeAdapter.toJsonTree() is public
private static <T> JsonElement toJsonTree(TypeAdapter<T> typeAdapter, T value) { private static <T> JsonElement toJsonTree(TypeAdapter<T> typeAdapter, T value) {
try { try {
JsonElementWriter jsonWriter = new JsonElementWriter(); JsonTreeWriter jsonWriter = new JsonTreeWriter();
jsonWriter.setLenient(true); jsonWriter.setLenient(true);
typeAdapter.write(jsonWriter, value); typeAdapter.write(jsonWriter, value);
return jsonWriter.get(); return jsonWriter.get();

View File

@ -32,7 +32,6 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;

View File

@ -20,9 +20,9 @@ import com.google.gson.JsonNull;
import java.io.IOException; import java.io.IOException;
import junit.framework.TestCase; import junit.framework.TestCase;
public final class JsonElementWriterTest extends TestCase { public final class JsonTreeWriterTest extends TestCase {
public void testArray() throws IOException { public void testArray() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.beginArray(); writer.beginArray();
writer.value(1); writer.value(1);
writer.value(2); writer.value(2);
@ -32,7 +32,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testNestedArray() throws IOException { public void testNestedArray() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.beginArray(); writer.beginArray();
writer.beginArray(); writer.beginArray();
writer.endArray(); writer.endArray();
@ -45,7 +45,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testObject() throws IOException { public void testObject() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.beginObject(); writer.beginObject();
writer.name("A").value(1); writer.name("A").value(1);
writer.name("B").value(2); writer.name("B").value(2);
@ -54,7 +54,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testNestedObject() throws IOException { public void testNestedObject() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.beginObject(); writer.beginObject();
writer.name("A"); writer.name("A");
writer.beginObject(); writer.beginObject();
@ -70,7 +70,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testWriteAfterClose() throws Exception { public void testWriteAfterClose() throws Exception {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true); writer.setLenient(true);
writer.beginArray(); writer.beginArray();
writer.value("A"); writer.value("A");
@ -84,7 +84,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testPrematureClose() throws Exception { public void testPrematureClose() throws Exception {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true); writer.setLenient(true);
writer.beginArray(); writer.beginArray();
try { try {
@ -95,7 +95,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testSerializeNullsFalse() throws IOException { public void testSerializeNullsFalse() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setSerializeNulls(false); writer.setSerializeNulls(false);
writer.beginObject(); writer.beginObject();
writer.name("A"); writer.name("A");
@ -105,7 +105,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testSerializeNullsTrue() throws IOException { public void testSerializeNullsTrue() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setSerializeNulls(true); writer.setSerializeNulls(true);
writer.beginObject(); writer.beginObject();
writer.name("A"); writer.name("A");
@ -115,12 +115,12 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testEmptyWriter() { public void testEmptyWriter() {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
assertEquals(JsonNull.INSTANCE, writer.get()); assertEquals(JsonNull.INSTANCE, writer.get());
} }
public void testLenientNansAndInfinities() throws IOException { public void testLenientNansAndInfinities() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true); writer.setLenient(true);
writer.beginArray(); writer.beginArray();
writer.value(Double.NaN); writer.value(Double.NaN);
@ -131,7 +131,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testStrictNansAndInfinities() throws IOException { public void testStrictNansAndInfinities() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(false); writer.setLenient(false);
writer.beginArray(); writer.beginArray();
try { try {
@ -152,7 +152,7 @@ public final class JsonElementWriterTest extends TestCase {
} }
public void testStrictBoxedNansAndInfinities() throws IOException { public void testStrictBoxedNansAndInfinities() throws IOException {
JsonElementWriter writer = new JsonElementWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(false); writer.setLenient(false);
writer.beginArray(); writer.beginArray();
try { try {