Get GraphAdapterBuilder working for serialization and deserialization using InstanceCreators to get a sneak peek at a value under construction.
This commit is contained in:
parent
d4a1e49e46
commit
6cca23c172
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2011 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.graph;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.InstanceCreator;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.ConstructorConstructor;
|
||||||
|
import com.google.gson.internal.ObjectConstructor;
|
||||||
|
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.HashMap;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a graph of objects as a list of named nodes.
|
||||||
|
*/
|
||||||
|
// TODO: proper documentation
|
||||||
|
public final class GraphAdapterBuilder {
|
||||||
|
private final ConstructorConstructor constructorConstructor = new ConstructorConstructor();
|
||||||
|
private final Map<Type, InstanceCreator<?>> instanceCreators
|
||||||
|
= new HashMap<Type, InstanceCreator<?>>();
|
||||||
|
|
||||||
|
public GraphAdapterBuilder addType(Type type) {
|
||||||
|
final ObjectConstructor<?> objectConstructor = constructorConstructor.get(TypeToken.get(type));
|
||||||
|
InstanceCreator<Object> instanceCreator = new InstanceCreator<Object>() {
|
||||||
|
public Object createInstance(Type type) {
|
||||||
|
return objectConstructor.construct();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return addType(type, instanceCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GraphAdapterBuilder addType(Type type, InstanceCreator<?> instanceCreator) {
|
||||||
|
if (type == null || instanceCreator == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
instanceCreators.put(type, instanceCreator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerOn(GsonBuilder gsonBuilder) {
|
||||||
|
Factory factory = new Factory(instanceCreators);
|
||||||
|
gsonBuilder.registerTypeAdapterFactory(factory);
|
||||||
|
for (Map.Entry<Type, InstanceCreator<?>> entry : instanceCreators.entrySet()) {
|
||||||
|
gsonBuilder.registerTypeAdapter(entry.getKey(), factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Factory implements TypeAdapterFactory, InstanceCreator {
|
||||||
|
private final Map<Type, InstanceCreator<?>> instanceCreators;
|
||||||
|
private final ThreadLocal<Graph> graphThreadLocal = new ThreadLocal<Graph>();
|
||||||
|
|
||||||
|
Factory(Map<Type, InstanceCreator<?>> instanceCreators) {
|
||||||
|
this.instanceCreators = instanceCreators;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
|
if (!instanceCreators.containsKey(type.getType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
out.nullValue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph graph = graphThreadLocal.get();
|
||||||
|
boolean writeEntireGraph = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have one of two cases:
|
||||||
|
* 1. We've encountered the first known object in this graph. Write
|
||||||
|
* out the graph, starting with that object.
|
||||||
|
* 2. We've encountered another graph object in the course of #1.
|
||||||
|
* Just write out this object's name. We'll circle back to writing
|
||||||
|
* out the object's value as a part of #1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (graph == null) {
|
||||||
|
writeEntireGraph = true;
|
||||||
|
graph = new Graph(new IdentityHashMap<Object, Element<?>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") // graph.map guarantees consistency between value and T
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.value(element.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public T read(JsonReader in) throws IOException {
|
||||||
|
if (in.peek() == JsonToken.NULL) {
|
||||||
|
in.nextNull();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Again we have one of two cases:
|
||||||
|
* 1. We've encountered the first known object in this graph. Read
|
||||||
|
* the entire graph in as a map from names to their JsonElements.
|
||||||
|
* Then convert the first JsonElement to its Java object.
|
||||||
|
* 2. We've encountered another graph object in the course of #1.
|
||||||
|
* Read in its name, then deserialize its value from the
|
||||||
|
* JsonElement in our map. We need to do this lazily because we
|
||||||
|
* don't know which TypeAdapter to use until a value is
|
||||||
|
* encountered in the wild.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@SuppressWarnings("unchecked") // graph.map guarantees consistency between value and T
|
||||||
|
Element<T> element = (Element<T>) graph.map.get(currentName);
|
||||||
|
// now that we know the typeAdapter for this name, go from JsonElement to 'T'
|
||||||
|
if (element.value == null) {
|
||||||
|
element.typeAdapter = typeAdapter;
|
||||||
|
element.read(graph);
|
||||||
|
}
|
||||||
|
return element.value;
|
||||||
|
} finally {
|
||||||
|
if (readEntireGraph) {
|
||||||
|
graphThreadLocal.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for the graph adapter to get a reference to a deserialized value
|
||||||
|
* before that value is fully populated. This is useful to deserialize
|
||||||
|
* values that directly or indirectly reference themselves: we can hand
|
||||||
|
* out an instance before read() returns.
|
||||||
|
*
|
||||||
|
* <p>Gson should only ever call this method when we're expecting it to;
|
||||||
|
* that is only when we've called back into Gson to deserialize a tree.
|
||||||
|
*/
|
||||||
|
public Object createInstance(Type type) {
|
||||||
|
Graph graph = graphThreadLocal.get();
|
||||||
|
if (graph == null || graph.nextCreate == null) {
|
||||||
|
throw new IllegalStateException("Unexpected call to createInstance() for " + type);
|
||||||
|
}
|
||||||
|
InstanceCreator<?> creator = instanceCreators.get(type);
|
||||||
|
Object result = creator.createInstance(type);
|
||||||
|
graph.nextCreate.value = result;
|
||||||
|
graph.nextCreate = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Graph {
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The queue of elements to write during serialization. Unused during
|
||||||
|
* deserialization.
|
||||||
|
*/
|
||||||
|
private final Queue<Element> queue = new LinkedList<Element>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instance currently being deserialized. Used as a backdoor between
|
||||||
|
* the graph traversal (which needs to know instances) and instance creators
|
||||||
|
* which create them.
|
||||||
|
*/
|
||||||
|
private Element nextCreate;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An element of the graph during serialization or deserialization.
|
||||||
|
*/
|
||||||
|
static class Element<T> {
|
||||||
|
/**
|
||||||
|
* This element's name in the top level graph object.
|
||||||
|
*/
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value if known. During deserialization this is lazily populated.
|
||||||
|
*/
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This element's type adapter if known. During deserialization this is
|
||||||
|
* lazily populated.
|
||||||
|
*/
|
||||||
|
private TypeAdapter<T> typeAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element to deserialize. Unused in serialization.
|
||||||
|
*/
|
||||||
|
private final JsonElement element;
|
||||||
|
|
||||||
|
Element(T value, String id, TypeAdapter<T> typeAdapter, JsonElement element) {
|
||||||
|
this.value = value;
|
||||||
|
this.id = id;
|
||||||
|
this.typeAdapter = typeAdapter;
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(JsonWriter out) throws IOException {
|
||||||
|
typeAdapter.write(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void read(Graph graph) throws IOException {
|
||||||
|
if (graph.nextCreate != null) {
|
||||||
|
throw new IllegalStateException("Unexpected recursive call to read()");
|
||||||
|
}
|
||||||
|
graph.nextCreate = this;
|
||||||
|
value = typeAdapter.fromJsonTree(element);
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalStateException("non-null value deserialized to null: " + element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,208 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2011 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.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;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a graph of objects as a list of named nodes.
|
|
||||||
*/
|
|
||||||
// TODO: proper documentation
|
|
||||||
public final class GraphTypeAdapterFactory implements TypeAdapterFactory {
|
|
||||||
private final ThreadLocal<Graph> graphThreadLocal = new ThreadLocal<Graph>();
|
|
||||||
private final Set<Type> graphTypes;
|
|
||||||
|
|
||||||
private GraphTypeAdapterFactory(Type... graphTypes) {
|
|
||||||
this.graphTypes = new HashSet<Type>();
|
|
||||||
this.graphTypes.addAll(Arrays.asList(graphTypes));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GraphTypeAdapterFactory of(Type... graphTypes) {
|
|
||||||
return new GraphTypeAdapterFactory(graphTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
|
||||||
if (!graphTypes.contains(type.getType())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
out.nullValue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Graph graph = graphThreadLocal.get();
|
|
||||||
boolean writeEntireGraph = false;
|
|
||||||
|
|
||||||
if (graph == null) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.value(element.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public T read(JsonReader in) throws IOException {
|
|
||||||
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 {
|
|
||||||
/**
|
|
||||||
* 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 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 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,7 @@ import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
public final class GraphTypeAdapterFactoryTest extends TestCase {
|
public final class GraphAdapterBuilderTest extends TestCase {
|
||||||
public void testSerialization() {
|
public void testSerialization() {
|
||||||
Roshambo rock = new Roshambo("ROCK");
|
Roshambo rock = new Roshambo("ROCK");
|
||||||
Roshambo scissors = new Roshambo("SCISSORS");
|
Roshambo scissors = new Roshambo("SCISSORS");
|
||||||
|
@ -29,9 +29,11 @@ public final class GraphTypeAdapterFactoryTest extends TestCase {
|
||||||
scissors.beats = paper;
|
scissors.beats = paper;
|
||||||
paper.beats = rock;
|
paper.beats = rock;
|
||||||
|
|
||||||
Gson gson = new GsonBuilder()
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
.registerTypeAdapterFactory(GraphTypeAdapterFactory.of(Roshambo.class))
|
new GraphAdapterBuilder()
|
||||||
.create();
|
.addType(Roshambo.class)
|
||||||
|
.registerOn(gsonBuilder);
|
||||||
|
Gson gson = gsonBuilder.create();
|
||||||
|
|
||||||
assertEquals("{'0x1':{'name':'ROCK','beats':'0x2'}," +
|
assertEquals("{'0x1':{'name':'ROCK','beats':'0x2'}," +
|
||||||
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
|
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
|
||||||
|
@ -44,9 +46,11 @@ public final class GraphTypeAdapterFactoryTest extends TestCase {
|
||||||
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
|
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
|
||||||
"'0x3':{'name':'PAPER','beats':'0x1'}}";
|
"'0x3':{'name':'PAPER','beats':'0x1'}}";
|
||||||
|
|
||||||
Gson gson = new GsonBuilder()
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
.registerTypeAdapterFactory(GraphTypeAdapterFactory.of(Roshambo.class))
|
new GraphAdapterBuilder()
|
||||||
.create();
|
.addType(Roshambo.class)
|
||||||
|
.registerOn(gsonBuilder);
|
||||||
|
Gson gson = gsonBuilder.create();
|
||||||
|
|
||||||
Roshambo rock = gson.fromJson(json, Roshambo.class);
|
Roshambo rock = gson.fromJson(json, Roshambo.class);
|
||||||
assertEquals("ROCK", rock.name);
|
assertEquals("ROCK", rock.name);
|
||||||
|
@ -54,7 +58,35 @@ public final class GraphTypeAdapterFactoryTest extends TestCase {
|
||||||
assertEquals("SCISSORS", scissors.name);
|
assertEquals("SCISSORS", scissors.name);
|
||||||
Roshambo paper = scissors.beats;
|
Roshambo paper = scissors.beats;
|
||||||
assertEquals("PAPER", paper.name);
|
assertEquals("PAPER", paper.name);
|
||||||
assertSame(rock, paper.beats); // TODO: currently fails
|
assertSame(rock, paper.beats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSerializationDirectSelfReference() {
|
||||||
|
Roshambo suicide = new Roshambo("SUICIDE");
|
||||||
|
suicide.beats = suicide;
|
||||||
|
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
new GraphAdapterBuilder()
|
||||||
|
.addType(Roshambo.class)
|
||||||
|
.registerOn(gsonBuilder);
|
||||||
|
Gson gson = gsonBuilder.create();
|
||||||
|
|
||||||
|
assertEquals("{'0x1':{'name':'SUICIDE','beats':'0x1'}}",
|
||||||
|
gson.toJson(suicide).replace('\"', '\''));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDeserializationDirectSelfReference() {
|
||||||
|
String json = "{'0x1':{'name':'SUICIDE','beats':'0x1'}}";
|
||||||
|
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
new GraphAdapterBuilder()
|
||||||
|
.addType(Roshambo.class)
|
||||||
|
.registerOn(gsonBuilder);
|
||||||
|
Gson gson = gsonBuilder.create();
|
||||||
|
|
||||||
|
Roshambo suicide = gson.fromJson(json, Roshambo.class);
|
||||||
|
assertEquals("SUICIDE", suicide.name);
|
||||||
|
assertSame(suicide, suicide.beats);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Roshambo {
|
static class Roshambo {
|
|
@ -47,7 +47,7 @@ public final class ConstructorConstructor {
|
||||||
this(Collections.<Type, InstanceCreator<?>>emptyMap());
|
this(Collections.<Type, InstanceCreator<?>>emptyMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> ObjectConstructor<T> getConstructor(TypeToken<T> typeToken) {
|
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
|
||||||
final Type type = typeToken.getType();
|
final Type type = typeToken.getType();
|
||||||
final Class<? super T> rawType = typeToken.getRawType();
|
final Class<? super T> rawType = typeToken.getRawType();
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
|
Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
|
||||||
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
|
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
|
||||||
ObjectConstructor<T> constructor = constructorConstructor.getConstructor(typeToken);
|
ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
|
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
|
||||||
TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
|
TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
|
||||||
|
|
|
@ -125,7 +125,7 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
|
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
|
||||||
TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]);
|
TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]);
|
||||||
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
|
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
|
||||||
ObjectConstructor<T> constructor = constructorConstructor.getConstructor(typeToken);
|
ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
// we don't define a type parameter for the key or value types
|
// we don't define a type parameter for the key or value types
|
||||||
|
|
|
@ -68,7 +68,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
return null; // it's a primitive!
|
return null; // it's a primitive!
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectConstructor<T> constructor = constructorConstructor.getConstructor(type);
|
ObjectConstructor<T> constructor = constructorConstructor.get(type);
|
||||||
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
|
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user