Added code to ensure that circular reference situations with custom handlers are detected.

Split CircularReferenceTest out of ObjectTest.
This commit is contained in:
Inderjeet Singh 2009-09-29 20:43:31 +00:00
parent 933a3e5150
commit 907082102d
5 changed files with 236 additions and 77 deletions

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2009 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;
import java.lang.reflect.Field;
/**
* Exception class to indicate a circular reference error.
* This class is not part of the public API and hence is not public.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
final class CircularReferenceException extends RuntimeException {
private static final long serialVersionUID = 7444343294106513081L;
private final Object offendingNode;
CircularReferenceException(Object offendingNode) {
super("circular reference error");
this.offendingNode = offendingNode;
}
public IllegalStateException createDetailedException(Field offendingField) {
StringBuilder msg = new StringBuilder(getMessage());
if (offendingField != null) {
msg.append("\n ").append("Offending field: ").append(offendingField.getName() + "\n");
}
if (offendingNode != null) {
msg.append("\n ").append("Offending object: ").append(offendingNode);
}
return new IllegalStateException(msg.toString(), this);
}
}

View File

@ -17,8 +17,6 @@
package com.google.gson;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/**

View File

@ -54,7 +54,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
return;
}
if (ancestors.contains(node)) {
throw new IllegalStateException("Circular reference found: " + node);
throw new CircularReferenceException(node);
}
ancestors.push(node);
}
@ -85,27 +85,35 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
}
public void visitArrayField(Field f, Type typeOfF, Object obj) {
if (isFieldNull(f, obj)) {
if (serializeNulls) {
addChildAsElement(f, JsonNull.createJsonNull());
try {
if (isFieldNull(f, obj)) {
if (serializeNulls) {
addChildAsElement(f, JsonNull.createJsonNull());
}
} else {
Object array = getFieldValue(f, obj);
addAsChildOfObject(f, typeOfF, array);
}
} else {
Object array = getFieldValue(f, obj);
addAsChildOfObject(f, typeOfF, array);
} catch (CircularReferenceException e) {
throw e.createDetailedException(f);
}
}
public void visitObjectField(Field f, Type typeOfF, Object obj) {
if (isFieldNull(f, obj)) {
if (serializeNulls) {
addChildAsElement(f, JsonNull.createJsonNull());
try {
if (isFieldNull(f, obj)) {
if (serializeNulls) {
addChildAsElement(f, JsonNull.createJsonNull());
}
} else {
Object fieldValue = getFieldValue(f, obj);
if (fieldValue != null) {
typeOfF = getActualTypeIfMoreSpecific(typeOfF, fieldValue.getClass());
}
addAsChildOfObject(f, typeOfF, fieldValue);
}
} else {
Object fieldValue = getFieldValue(f, obj);
if (fieldValue != null) {
typeOfF = getActualTypeIfMoreSpecific(typeOfF, fieldValue.getClass());
}
addAsChildOfObject(f, typeOfF, fieldValue);
} catch (CircularReferenceException e) {
throw e.createDetailedException(f);
}
}
@ -160,20 +168,34 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
@SuppressWarnings("unchecked")
public boolean visitUsingCustomHandler(Object obj, Type objType) {
JsonSerializer serializer = serializers.getHandlerFor(objType);
if (serializer == null && obj != null) {
serializer = serializers.getHandlerFor(obj.getClass());
}
if (serializer != null) {
if (obj == null) {
assignToRoot(JsonNull.createJsonNull());
} else {
assignToRoot(serializer.serialize(obj, objType, context));
try {
JsonSerializer serializer = serializers.getHandlerFor(objType);
if (serializer == null && obj != null) {
serializer = serializers.getHandlerFor(obj.getClass());
}
return true;
if (serializer != null) {
if (obj == null) {
assignToRoot(JsonNull.createJsonNull());
} else {
assignToRoot(invokeCustomHandler(obj, objType, serializer));
}
return true;
}
return false;
} catch (CircularReferenceException e) {
throw e.createDetailedException(null);
}
}
@SuppressWarnings("unchecked")
private JsonElement invokeCustomHandler(Object obj, Type objType, JsonSerializer serializer) {
start(obj);
try {
return serializer.serialize(obj, objType, context);
} finally {
end(obj);
}
return false;
}
@SuppressWarnings("unchecked")
@ -189,13 +211,15 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
}
JsonSerializer serializer = serializers.getHandlerFor(actualTypeOfField);
if (serializer != null) {
JsonElement child = serializer.serialize(obj, actualTypeOfField, context);
JsonElement child = invokeCustomHandler(obj, actualTypeOfField, serializer);
addChildAsElement(f, child);
return true;
}
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException();
} catch (CircularReferenceException e) {
throw e.createDetailedException(f);
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2008 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.functional;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.ClassOverridingEquals;
/**
* Functional tests related to circular reference detection and error reporting.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public class CircularReferenceTest extends TestCase {
private Gson gson;
@Override
protected void setUp() throws Exception {
super.setUp();
gson = new Gson();
}
public void testCircularSerialization() throws Exception {
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
a.children.add(b);
b.children.add(a);
try {
gson.toJson(a);
fail("Circular types should not get printed!");
} catch (IllegalStateException expected) {
assertTrue(expected.getMessage().contains("children"));
}
}
public void testSelfReferenceSerialization() throws Exception {
ClassOverridingEquals objA = new ClassOverridingEquals();
objA.ref = objA;
try {
gson.toJson(objA);
fail("Circular reference to self can not be serialized!");
} catch (IllegalStateException expected) { }
}
public void testSelfReferenceArrayFieldSerialization() throws Exception {
ClassWithSelfReferenceArray objA = new ClassWithSelfReferenceArray();
objA.children = new ClassWithSelfReferenceArray[]{objA};
try {
gson.toJson(objA);
fail("Circular reference to self can not be serialized!");
} catch (IllegalStateException expected) {
assertTrue(expected.getMessage().contains("children"));
}
}
public void testSelfReferenceCustomHandlerSerialization() throws Exception {
ClassWithSelfReference obj = new ClassWithSelfReference();
obj.child = obj;
Gson gson = new GsonBuilder().registerTypeAdapter(ClassWithSelfReference.class, new JsonSerializer<ClassWithSelfReference>() {
public JsonElement serialize(ClassWithSelfReference src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("property", "value");
obj.add("child", context.serialize(src.child));
return obj;
}
}).create();
try {
gson.toJson(obj);
fail("Circular reference to self can not be serialized!");
} catch (IllegalStateException expected) {
assertTrue(expected.getMessage().contains("Offending"));
}
}
public void testDirectedAcyclicGraphSerialization() throws Exception {
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
ContainsReferenceToSelfType c = new ContainsReferenceToSelfType();
a.children.add(b);
a.children.add(c);
b.children.add(c);
assertNotNull(gson.toJson(a));
}
public void testDirectedAcyclicGraphDeserialization() throws Exception {
String json = "{\"children\":[{\"children\":[{\"children\":[]}]},{\"children\":[]}]}";
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
assertNotNull(target);
assertEquals(2, target.children.size());
}
private static class ContainsReferenceToSelfType {
Collection<ContainsReferenceToSelfType> children = new ArrayList<ContainsReferenceToSelfType>();
}
private static class ClassWithSelfReference {
ClassWithSelfReference child;
}
private static class ClassWithSelfReferenceArray {
@SuppressWarnings("unused")
ClassWithSelfReferenceArray[] children;
}
}

View File

@ -28,7 +28,6 @@ import com.google.gson.InstanceCreator;
import com.google.gson.common.TestTypes.ArrayOfObjects;
import com.google.gson.common.TestTypes.BagOfPrimitiveWrappers;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassOverridingEquals;
import com.google.gson.common.TestTypes.ClassWithArray;
import com.google.gson.common.TestTypes.ClassWithNoFields;
import com.google.gson.common.TestTypes.ClassWithObjects;
@ -91,23 +90,6 @@ public class ObjectTest extends TestCase {
assertEquals(jsonString, target.getExpectedJson());
}
public void testDirectedAcyclicGraphSerialization() throws Exception {
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
ContainsReferenceToSelfType c = new ContainsReferenceToSelfType();
a.children.add(b);
a.children.add(c);
b.children.add(c);
assertNotNull(gson.toJson(a));
}
public void testDirectedAcyclicGraphDeserialization() throws Exception {
String json = "{\"children\":[{\"children\":[{\"children\":[]}]},{\"children\":[]}]}";
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
assertNotNull(target);
assertEquals(2, target.children.size());
}
public void testClassWithTransientFieldsSerialization() throws Exception {
ClassWithTransientFields<Long> target = new ClassWithTransientFields<Long>(1L);
assertEquals(target.getExpectedJson(), gson.toJson(target));
@ -231,11 +213,15 @@ public class ObjectTest extends TestCase {
public void testEmptyCollectionInAnObjectDeserialization() throws Exception {
String json = "{\"children\":[]}";
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
ClassWithCollectionField target = gson.fromJson(json, ClassWithCollectionField.class);
assertNotNull(target);
assertTrue(target.children.isEmpty());
}
private static class ClassWithCollectionField {
Collection<String> children = new ArrayList<String>();
}
public void testPrimitiveArrayInAnObjectDeserialization() throws Exception {
String json = "{\"longArray\":[0,1,2,3,4,5,6,7,8,9]}";
PrimitiveArray target = gson.fromJson(json, PrimitiveArray.class);
@ -252,31 +238,10 @@ public class ObjectTest extends TestCase {
}
public void testEmptyCollectionInAnObjectSerialization() throws Exception {
ContainsReferenceToSelfType target = new ContainsReferenceToSelfType();
ClassWithCollectionField target = new ClassWithCollectionField();
assertEquals("{\"children\":[]}", gson.toJson(target));
}
public void testCircularSerialization() throws Exception {
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
a.children.add(b);
b.children.add(a);
try {
gson.toJson(a);
fail("Circular types should not get printed!");
} catch (IllegalStateException expected) { }
}
public void testSelfReferenceSerialization() throws Exception {
ClassOverridingEquals objA = new ClassOverridingEquals();
objA.ref = objA;
try {
gson.toJson(objA);
fail("Circular reference to self can not be serialized!");
} catch (IllegalStateException expected) { }
}
public void testPrivateNoArgConstructorDeserialization() throws Exception {
ClassWithPrivateNoArgsConstructor target =
gson.fromJson("{\"a\":20}", ClassWithPrivateNoArgsConstructor.class);
@ -339,11 +304,6 @@ public class ObjectTest extends TestCase {
}
}
private static class ContainsReferenceToSelfType {
public Collection<ContainsReferenceToSelfType> children =
new ArrayList<ContainsReferenceToSelfType>();
}
private static class ArrayOfArrays {
private final BagOfPrimitives[][] elements;
public ArrayOfArrays() {