Add a test for circular references via a CustomFieldSerializer.
Update deRPC code to pass this test.
Patch by: bobv
Review by: jgw
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6604 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java b/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java
index 5756565..83c69ee 100644
--- a/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/rpc/client/impl/CommandClientSerializationStreamWriter.java
@@ -51,13 +51,19 @@
anObject.hashCode();
}
- private final Map<Object, IdentityValueCommand> identityMap = new IdentityHashMap<Object, IdentityValueCommand>();
+ private final Map<Object, IdentityValueCommand> identityMap;
private final TypeOverrides serializer;
public CommandClientSerializationStreamWriter(TypeOverrides serializer,
CommandSink sink) {
+ this(serializer, sink, new IdentityHashMap<Object, IdentityValueCommand>());
+ }
+
+ private CommandClientSerializationStreamWriter(TypeOverrides serializer,
+ CommandSink sink, Map<Object, IdentityValueCommand> identityMap) {
super(sink);
this.serializer = serializer;
+ this.identityMap = identityMap;
}
/**
@@ -141,8 +147,13 @@
InvokeCustomFieldSerializerCommand command = new InvokeCustomFieldSerializerCommand(
type, null, null);
identityMap.put(value, command);
+
+ /*
+ * Pass the current identityMap into the new writer to allow circular
+ * references through the graph emitted by the CFS.
+ */
CommandClientSerializationStreamWriter subWriter = new CommandClientSerializationStreamWriter(
- serializer, new HasValuesCommandSink(command));
+ serializer, new HasValuesCommandSink(command), identityMap);
serializeFunction.serialize(subWriter, value);
if (serializer.hasExtraFields(type.getName())) {
diff --git a/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamReader.java b/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamReader.java
index 8f89687..bf20c17 100644
--- a/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamReader.java
@@ -177,7 +177,8 @@
public boolean visit(InvokeCustomFieldSerializerCommand x, Context ctx) {
if (maybePushBackRef(x)) {
- CommandServerSerializationStreamReader subReader = new CommandServerSerializationStreamReader();
+ CommandServerSerializationStreamReader subReader = new CommandServerSerializationStreamReader(
+ backRefs);
subReader.prepareToRead(x.getValues());
Class<?> serializerClass = x.getSerializerClass();
@@ -267,9 +268,18 @@
}
}
- Map<IdentityValueCommand, Object> backRefs = new HashMap<IdentityValueCommand, Object>();
+ final Map<IdentityValueCommand, Object> backRefs;
Iterator<ValueCommand> values;
+ public CommandServerSerializationStreamReader() {
+ this(new HashMap<IdentityValueCommand, Object>());
+ }
+
+ private CommandServerSerializationStreamReader(
+ Map<IdentityValueCommand, Object> backRefs) {
+ this.backRefs = backRefs;
+ }
+
public void prepareToRead(List<ValueCommand> commands) {
values = commands.iterator();
assert values.hasNext() : "No commands";
diff --git a/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamWriter.java b/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamWriter.java
index d3b4b0f..bfae11e 100644
--- a/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/rpc/server/CommandServerSerializationStreamWriter.java
@@ -48,7 +48,7 @@
CommandSerializationStreamWriterBase {
private final ClientOracle clientOracle;
- private final Map<Object, IdentityValueCommand> identityMap = new IdentityHashMap<Object, IdentityValueCommand>();
+ private final Map<Object, IdentityValueCommand> identityMap;
public CommandServerSerializationStreamWriter(CommandSink sink) {
this(new HostedModeClientOracle(), sink);
@@ -56,8 +56,14 @@
public CommandServerSerializationStreamWriter(ClientOracle oracle,
CommandSink sink) {
+ this(oracle, sink, new IdentityHashMap<Object, IdentityValueCommand>());
+ }
+
+ private CommandServerSerializationStreamWriter(ClientOracle oracle,
+ CommandSink sink, Map<Object, IdentityValueCommand> identityMap) {
super(sink);
this.clientOracle = oracle;
+ this.identityMap = identityMap;
}
/**
@@ -70,14 +76,17 @@
return NullValueCommand.INSTANCE;
}
+ /*
+ * Check accessor map before the identity map because we don't want to
+ * recurse on wrapped primitive values.
+ */
Accessor accessor;
-
- if (identityMap.containsKey(value)) {
- return identityMap.get(value);
-
- } else if ((accessor = CommandSerializationUtil.getAccessor(type)).canMakeValueCommand()) {
+ if ((accessor = CommandSerializationUtil.getAccessor(type)).canMakeValueCommand()) {
return accessor.makeValueCommand(value);
+ } else if (identityMap.containsKey(value)) {
+ return identityMap.get(value);
+
} else if (type.isArray()) {
return makeArray(type, value);
@@ -92,6 +101,7 @@
private ArrayValueCommand makeArray(Class<?> type, Object value)
throws SerializationException {
ArrayValueCommand toReturn = new ArrayValueCommand(type.getComponentType());
+ identityMap.put(value, toReturn);
for (int i = 0, j = Array.getLength(value); i < j; i++) {
Object arrayValue = Array.get(value, i);
if (arrayValue == null) {
@@ -102,7 +112,6 @@
toReturn.add(makeValue(valueType, arrayValue));
}
}
- identityMap.put(value, toReturn);
return toReturn;
}
@@ -202,8 +211,12 @@
instanceClass, customSerializer, manuallySerializedType);
identityMap.put(instance, toReturn);
+ /*
+ * Pass the current identityMap into the new writer to allow circular
+ * references through the graph emitted by the CFS.
+ */
CommandServerSerializationStreamWriter subWriter = new CommandServerSerializationStreamWriter(
- clientOracle, new HasValuesCommandSink(toReturn));
+ clientOracle, new HasValuesCommandSink(toReturn), identityMap);
method.invoke(null, subWriter, instance);
return toReturn;
diff --git a/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java b/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java
index b7718f1..a93e308 100644
--- a/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java
+++ b/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java
@@ -295,102 +295,102 @@
@Override
public boolean visit(InvokeCustomFieldSerializerCommand x, Context ctx) {
- byte[] currentBackRef = null;
- // TODO Extract the commands as an inline function
- if (!isStarted(x)) {
- InstantiateCommand makeReader = new InstantiateCommand(
- CommandClientSerializationStreamReader.class);
- /*
- * Ensure that the reader will stick around for both instantiate and
- * deserialize calls.
- */
- makeBackRef(makeReader);
-
- ArrayValueCommand payload = new ArrayValueCommand(Object.class);
- for (ValueCommand value : x.getValues()) {
- payload.add(value);
- }
- makeReader.set(CommandClientSerializationStreamReader.class, "payload",
- payload);
-
- currentBackRef = begin(x);
-
- String instantiateIdent = clientOracle.getMethodId(
- x.getSerializerClass(), "instantiate",
- SerializationStreamReader.class);
-
- // x = $Foo(new Foo);
- // x = instantiate(reader);
- push(currentBackRef);
- eq();
- if (instantiateIdent == null) {
- // No instantiate method, we'll have to invoke the constructor
-
- instantiateIdent = clientOracle.getSeedName(x.getTargetClass());
- assert instantiateIdent != null : "instantiateIdent";
-
- // $Foo()
- String constructorMethodName;
- if (x.getTargetClass().getEnclosingClass() == null) {
- constructorMethodName = "$" + x.getTargetClass().getSimpleName();
- } else {
- String name = x.getTargetClass().getName();
- constructorMethodName = "$"
- + name.substring(name.lastIndexOf('.') + 1);
- }
-
- String constructorIdent = clientOracle.getMethodId(
- x.getTargetClass(), constructorMethodName, x.getTargetClass());
- assert constructorIdent != null : "constructorIdent "
- + constructorMethodName;
-
- // constructor(new Seed);
- push(constructorIdent);
- lparen();
- _new();
- push(instantiateIdent);
- rparen();
- semi();
- } else {
- // instantiate(reader);
- push(instantiateIdent);
- lparen();
- accept(makeReader);
- rparen();
- semi();
- }
-
- // Call the deserialize method if it exists
- String deserializeIdent = clientOracle.getMethodId(
- x.getSerializerClass(), "deserialize",
- SerializationStreamReader.class, x.getManuallySerializedType());
- if (deserializeIdent != null) {
- // deserialize(reader, obj);
- push(deserializeIdent);
- lparen();
- accept(makeReader);
- comma();
- push(currentBackRef);
- rparen();
- semi();
- }
-
- // If there are extra fields, set them
- for (SetCommand setter : x.getSetters()) {
- accept(setter);
- semi();
- }
-
- commit(x);
- forget(makeReader);
- }
-
- if (currentBackRef == null) {
+ if (isStarted(x)) {
push(makeBackRef(x));
- } else {
- push(currentBackRef);
+ return false;
}
+ // ( backref = instantiate(), deserialize(), setter, ..., backref )
+ byte[] currentBackRef = begin(x);
+
+ lparen();
+
+ InstantiateCommand makeReader = new InstantiateCommand(
+ CommandClientSerializationStreamReader.class);
+ /*
+ * Ensure that the reader will stick around for both instantiate and
+ * deserialize calls.
+ */
+ makeBackRef(makeReader);
+
+ ArrayValueCommand payload = new ArrayValueCommand(Object.class);
+ for (ValueCommand value : x.getValues()) {
+ payload.add(value);
+ }
+ makeReader.set(CommandClientSerializationStreamReader.class, "payload",
+ payload);
+
+ String instantiateIdent = clientOracle.getMethodId(
+ x.getSerializerClass(), "instantiate",
+ SerializationStreamReader.class);
+
+ // x = $Foo(new Foo),
+ // x = instantiate(reader),
+ push(currentBackRef);
+ eq();
+ if (instantiateIdent == null) {
+ // No instantiate method, we'll have to invoke the constructor
+
+ instantiateIdent = clientOracle.getSeedName(x.getTargetClass());
+ assert instantiateIdent != null : "instantiateIdent";
+
+ // $Foo()
+ String constructorMethodName;
+ if (x.getTargetClass().getEnclosingClass() == null) {
+ constructorMethodName = "$" + x.getTargetClass().getSimpleName();
+ } else {
+ String name = x.getTargetClass().getName();
+ constructorMethodName = "$"
+ + name.substring(name.lastIndexOf('.') + 1);
+ }
+
+ String constructorIdent = clientOracle.getMethodId(x.getTargetClass(),
+ constructorMethodName, x.getTargetClass());
+ assert constructorIdent != null : "constructorIdent "
+ + constructorMethodName;
+
+ // constructor(new Seed),
+ push(constructorIdent);
+ lparen();
+ _new();
+ push(instantiateIdent);
+ rparen();
+ comma();
+ } else {
+ // instantiate(reader),
+ push(instantiateIdent);
+ lparen();
+ accept(makeReader);
+ rparen();
+ comma();
+ }
+
+ // Call the deserialize method if it exists
+ String deserializeIdent = clientOracle.getMethodId(
+ x.getSerializerClass(), "deserialize",
+ SerializationStreamReader.class, x.getManuallySerializedType());
+ if (deserializeIdent != null) {
+ // deserialize(reader, obj),
+ push(deserializeIdent);
+ lparen();
+ accept(makeReader);
+ comma();
+ push(currentBackRef);
+ rparen();
+ comma();
+ }
+
+ // If there are extra fields, set them
+ for (SetCommand setter : x.getSetters()) {
+ accept(setter);
+ comma();
+ }
+
+ push(currentBackRef);
+ rparen();
+ commit(x, false);
+ forget(makeReader);
+
return false;
}
@@ -696,7 +696,7 @@
} while (leafType.getComponentType() != null);
assert dims > 0;
// leafType cannot be null here
-
+
String s = getJavahSignatureName(leafType);
for (int i = 0; i < dims; ++i) {
s = "_3" + s;
diff --git a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
index ce6b964..291605b 100644
--- a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableGraphWithCFS;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream;
@@ -68,6 +69,25 @@
});
}
+ public void testComplexCyclicGraphWithCFS() {
+ delayTestFinish(TEST_DELAY);
+
+ ObjectGraphTestServiceAsync service = getServiceAsync();
+ service.echo_ComplexCyclicGraphWithCFS(
+ TestSetFactory.createComplexCyclicGraphWithCFS(),
+ new AsyncCallback<SerializableGraphWithCFS>() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(SerializableGraphWithCFS result) {
+ assertNotNull(result);
+ assertTrue(TestSetValidator.isValidComplexCyclicGraphWithCFS(result));
+ finishTest();
+ }
+ });
+ }
+
public void testComplexCyclicGraph2() {
delayTestFinish(TEST_DELAY);
diff --git a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestService.java b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestService.java
index f806677..aa9825a 100644
--- a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestService.java
@@ -16,6 +16,7 @@
package com.google.gwt.user.client.rpc;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableGraphWithCFS;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
@@ -33,9 +34,13 @@
SerializableDoublyLinkedNode echo_ComplexCyclicGraph(
SerializableDoublyLinkedNode node1, SerializableDoublyLinkedNode node2);
+ SerializableGraphWithCFS echo_ComplexCyclicGraphWithCFS(
+ SerializableGraphWithCFS createComplexCyclicGraphWithArrays);
+
SerializablePrivateNoArg echo_PrivateNoArg(SerializablePrivateNoArg node);
- SerializableWithTwoArrays echo_SerializableWithTwoArrays(SerializableWithTwoArrays node);
+ SerializableWithTwoArrays echo_SerializableWithTwoArrays(
+ SerializableWithTwoArrays node);
SerializableDoublyLinkedNode echo_TrivialCyclicGraph(
SerializableDoublyLinkedNode node);
diff --git a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestServiceAsync.java
index 4b4d42b..b277bba 100644
--- a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTestServiceAsync.java
@@ -16,6 +16,7 @@
package com.google.gwt.user.client.rpc;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableGraphWithCFS;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
@@ -34,8 +35,13 @@
void echo_PrivateNoArg(SerializablePrivateNoArg node, AsyncCallback callback);
- void echo_SerializableWithTwoArrays(SerializableWithTwoArrays node, AsyncCallback callback);
+ void echo_SerializableWithTwoArrays(SerializableWithTwoArrays node,
+ AsyncCallback callback);
void echo_TrivialCyclicGraph(SerializableDoublyLinkedNode node,
AsyncCallback callback);
+
+ void echo_ComplexCyclicGraphWithCFS(
+ SerializableGraphWithCFS createComplexCyclicGraphWithArrays,
+ AsyncCallback<SerializableGraphWithCFS> asyncCallback);
}
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
index 5166bf2..069a7fb 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
@@ -257,6 +257,32 @@
}
/**
+ * Test reference cycles where the reference graph passes through a
+ * CustomFieldSerializer.
+ */
+ public static final class SerializableGraphWithCFS implements Serializable {
+ private ArrayList<SerializableGraphWithCFS> array;
+ private SerializableGraphWithCFS parent;
+
+ public SerializableGraphWithCFS() {
+ array = new ArrayList<SerializableGraphWithCFS>();
+ array.add(new SerializableGraphWithCFS(this));
+ }
+
+ public SerializableGraphWithCFS(SerializableGraphWithCFS parent) {
+ this.parent = parent;
+ }
+
+ public ArrayList<SerializableGraphWithCFS> getArray() {
+ return array;
+ }
+
+ public SerializableGraphWithCFS getParent() {
+ return parent;
+ }
+ }
+
+ /**
* Tests that classes with a private no-arg constructor can be serialized.
*/
public static class SerializablePrivateNoArg implements IsSerializable {
@@ -352,6 +378,10 @@
new Character(Character.MAX_VALUE), new Character(Character.MIN_VALUE)};
}
+ public static SerializableGraphWithCFS createComplexCyclicGraphWithCFS() {
+ return new SerializableGraphWithCFS();
+ }
+
@SuppressWarnings("deprecation")
public static Date[] createDateArray() {
return new Date[] {
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
index adbf839..58390a0 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -15,17 +15,18 @@
*/
package com.google.gwt.user.client.rpc;
-import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
-import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
-import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
-import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
-import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertSame;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableGraphWithCFS;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -41,8 +42,8 @@
import java.util.Map.Entry;
/**
- * Misnamed set of static validation methods used by various
- * collection class tests.
+ * Misnamed set of static validation methods used by various collection class
+ * tests.
* <p>
* TODO: could add generics to require args to be of the same type
*/
@@ -247,7 +248,7 @@
return reference.equals(list);
}
- public static boolean isValid(HashMap<?,?> expected, HashMap<?,?> map) {
+ public static boolean isValid(HashMap<?, ?> expected, HashMap<?, ?> map) {
if (map == null) {
return false;
}
@@ -259,7 +260,7 @@
Set<?> entries = expected.entrySet();
Iterator<?> entryIter = entries.iterator();
while (entryIter.hasNext()) {
- Entry<?,?> entry = (Entry<?,?>) entryIter.next();
+ Entry<?, ?> entry = (Entry<?, ?>) entryIter.next();
Object value = map.get(entry.getKey());
@@ -298,8 +299,9 @@
return true;
}
- public static boolean isValid(LinkedHashMap<?,?> expected, LinkedHashMap<?,?> map) {
- if (isValid((HashMap<?,?>) expected, (HashMap<?,?>) map)) {
+ public static boolean isValid(LinkedHashMap<?, ?> expected,
+ LinkedHashMap<?, ?> map) {
+ if (isValid((HashMap<?, ?>) expected, (HashMap<?, ?>) map)) {
Iterator<?> expectedEntries = expected.entrySet().iterator();
Iterator<?> actualEntries = map.entrySet().iterator();
return equals(expectedEntries, actualEntries);
@@ -494,6 +496,19 @@
return true;
}
+ public static boolean isValidComplexCyclicGraphWithCFS(
+ SerializableGraphWithCFS result) {
+ assertNotNull(result);
+ List<SerializableGraphWithCFS> array = result.getArray();
+ assertNotNull(array);
+ assertEquals(1, array.size());
+
+ SerializableGraphWithCFS child = array.get(0);
+ assertFalse(result == child);
+ assertSame(result, child.getParent());
+ return true;
+ }
+
public static boolean isValidTrivialCyclicGraph(
SerializableDoublyLinkedNode actual) {
if (actual == null) {
@@ -538,5 +553,4 @@
private static boolean equalsWithNullCheck(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
-
}
diff --git a/user/test/com/google/gwt/user/server/rpc/ObjectGraphTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/ObjectGraphTestServiceImpl.java
index c86f7b2..4d1262a 100644
--- a/user/test/com/google/gwt/user/server/rpc/ObjectGraphTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/ObjectGraphTestServiceImpl.java
@@ -18,6 +18,7 @@
import com.google.gwt.user.client.rpc.ObjectGraphTestService;
import com.google.gwt.user.client.rpc.TestSetValidator;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
+import com.google.gwt.user.client.rpc.TestSetFactory.SerializableGraphWithCFS;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
@@ -45,6 +46,15 @@
return root;
}
+ public SerializableGraphWithCFS echo_ComplexCyclicGraphWithCFS(
+ SerializableGraphWithCFS root) {
+ if (!TestSetValidator.isValidComplexCyclicGraphWithCFS(root)) {
+ throw new RuntimeException();
+ }
+
+ return root;
+ }
+
public SerializableDoublyLinkedNode echo_TrivialCyclicGraph(
SerializableDoublyLinkedNode root) {
if (!TestSetValidator.isValidTrivialCyclicGraph(root)) {