Mainly, this fixes an overlooked case of type parameter exposure, namely a
field whose type is an *array* of parameterized types, for example "Foo<T>[]".
Additionally, there are some documentation improvements and minor cleanups.
Review by: mmendez
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2912 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index e096154..a1bf11b 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -62,39 +62,33 @@
* </p>
*
* <p>
- * It then traverses the type hierarchy of each of the types in these method
- * signatures in order to discover additional types which it might need to
- * include. For the purposes of this explanation we define a root type to be any
- * type which appears in the RemoteService method signatures or the type of any
- * non-final, instance field which is part of a type that qualifies for
- * serialization. The builder will fail if a root is not serializable and it has
- * no subtypes that are.
+ * To find the serializable types, it includes the root types, and then it
+ * iteratively traverses the type hierarchy and the fields of any type already
+ * deemed serializable. To improve the accuracy of the traversal there is a
+ * computations of the exposure of type parameters. When the traversal reaches a
+ * parameterized type, these exposure values are used to determine how to treat
+ * the arguments.
* </p>
*
* <p>
- * To improve the accuracy of the traversal there is a computations of the
- * exposure of type parameters. When the traversal reaches a parameterized type,
- * these exposure values are used to determine how to treat the arguments.
- * </p>
- *
- * <p>
- * A type qualifies for serialization if it is automatically or manually
- * serializable. Automatic serialization is selected if the type is assignable
- * to {@link IsSerializable} or {@link Serializable} or if the type is a
- * primitive type such as int, boolean, etc. Manual serialization is selected if
- * there exists another type with the same fully qualified name concatenated
- * with "_CustomFieldSerializer". If a type qualifies for both manual and
- * automatic serialization, manual serialization is preferred.
+ * A type qualifies for serialization if it or one of its subtypes is
+ * automatically or manually serializable. Automatic serialization is selected
+ * if the type is assignable to {@link IsSerializable} or {@link Serializable}
+ * or if the type is a primitive type such as int, boolean, etc. Manual
+ * serialization is selected if there exists another type with the same fully
+ * qualified name concatenated with "_CustomFieldSerializer". If a type
+ * qualifies for both manual and automatic serialization, manual serialization
+ * is preferred.
* </p>
*/
public class SerializableTypeOracleBuilder {
- interface Path {
+ private interface Path {
Path getParent();
String toString();
}
- enum TypeState {
+ private enum TypeState {
/**
* The instantiability of a type has been determined.
*/
@@ -249,22 +243,6 @@
}
/**
- * Type parameter is exposed.
- */
- static final int EXPOSURE_DIRECT = 0;
-
- /**
- * Type parameter is exposed as a bounded array. The value is the max bound of
- * the exposure.
- */
- static final int EXPOSURE_MIN_BOUNDED_ARRAY = EXPOSURE_DIRECT + 1;
-
- /**
- * Type parameter is not exposed.
- */
- static final int EXPOSURE_NONE = -1;
-
- /**
* Compares {@link JType}s according to their qualified source names.
*/
static final Comparator<JType> JTYPE_COMPARATOR = new Comparator<JType>() {
@@ -399,6 +377,11 @@
}
}
+ /**
+ * Return <code>true</code> if a class's fields should be considered for
+ * serialization. If it returns <code>false</code> then none of the fields
+ * of this class should be serialized.
+ */
static boolean shouldConsiderFieldsForSerialization(TreeLogger logger,
JClassType type, boolean isSpeculative, TypeFilter filter) {
if (!isAllowedByFilter(logger, filter, type, isSpeculative)) {
@@ -681,6 +664,12 @@
private final TypeParameterExposureComputer typeParameterExposureComputer = new TypeParameterExposureComputer(
typeFilter);
+ /**
+ * The set of type parameters that appear in one of the root types.
+ * TODO(spoon): It would be cleaner to delete this field, and instead to have
+ * {@link #addRootType(TreeLogger, JType)} replace parameters with wildcard
+ * types. Then the root types would not contain any parameters.
+ */
private Set<JTypeParameter> typeParametersInRootTypes = new HashSet<JTypeParameter>();
/**
@@ -1347,7 +1336,7 @@
Path path = createTypeArgumentPath(parent, baseType, paramIndex, typeArg);
int exposure = getTypeParameterExposure(baseType, paramIndex);
switch (exposure) {
- case EXPOSURE_DIRECT: {
+ case TypeParameterExposureComputer.EXPOSURE_DIRECT: {
TreeLogger branch = logger.branch(
TreeLogger.DEBUG,
"Checking type argument "
@@ -1358,7 +1347,7 @@
return checkTypeInstantiable(branch, typeArg, true, path)
|| mightNotBeExposed(baseType, paramIndex);
}
- case EXPOSURE_NONE:
+ case TypeParameterExposureComputer.EXPOSURE_NONE:
// Ignore this argument
logger.branch(TreeLogger.DEBUG, "Ignoring type argument " + paramIndex
+ " of type '" + baseType.getParameterizedQualifiedSourceName()
@@ -1366,7 +1355,7 @@
return true;
default: {
- assert (exposure >= EXPOSURE_MIN_BOUNDED_ARRAY);
+ assert (exposure >= TypeParameterExposureComputer.EXPOSURE_MIN_BOUNDED_ARRAY);
TreeLogger branch = logger.branch(
TreeLogger.DEBUG,
"Checking type argument "
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
index e107c92..b3ddb78 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
@@ -41,6 +41,9 @@
* Helper class for type parameter flow information.
*/
class TypeParameterFlowInfo {
+ /**
+ * The class that declares this type parameter.
+ */
private final JGenericType baseType;
/**
@@ -52,7 +55,7 @@
*/
private final Map<TypeParameterFlowInfo, Integer> causesExposure = new LinkedHashMap<TypeParameterFlowInfo, Integer>();
- private int exposure = SerializableTypeOracleBuilder.EXPOSURE_NONE;
+ private int exposure = EXPOSURE_NONE;
private final Map<TypeParameterFlowInfo, Boolean> isTransitivelyAffectedByCache = new HashMap<TypeParameterFlowInfo, Boolean>();
@@ -75,14 +78,6 @@
this.ordinal = ordinal;
}
- /**
- * TODO: We only need to go up the hierarchy until we find the first non RPC
- * qualifying type.
- *
- * TODO: We need to ignore fields that would be ignored by serialization.
- *
- * @return
- */
public boolean checkDirectExposure() {
boolean didChange = false;
JClassType type = baseType;
@@ -134,6 +129,13 @@
return listeners;
}
+ /**
+ * Return whether it is possible for the parameter not to be exposed. For
+ * example, if a class has one subclass that uses the parameter and another
+ * that does not, then the parameter is exposed (exposure >=
+ * <code>EXPOSURE_DIRECT</code>) but this method will return
+ * <code>false</code>.
+ */
public boolean getMightNotBeExposed() {
return mightNotBeExposed;
}
@@ -208,6 +210,8 @@
}
private void computeIndirectExposureCauses() {
+ // TODO(spoon): this only needs to consider immediate subtypes, not all
+ // subtypes
JClassType[] subtypes = baseType.getSubtypes();
for (JClassType subtype : subtypes) {
JGenericType isGeneric = subtype.isGenericType();
@@ -242,7 +246,7 @@
continue;
}
- JParameterizedType isParameterized = field.getType().isParameterized();
+ JParameterizedType isParameterized = field.getType().getLeafType().isParameterized();
if (isParameterized == null) {
continue;
}
@@ -334,6 +338,22 @@
}
}
+ /**
+ * Type parameter is exposed.
+ */
+ static final int EXPOSURE_DIRECT = 0;
+
+ /**
+ * Type parameter is exposed as a bounded array. The value is the max bound of
+ * the exposure.
+ */
+ static final int EXPOSURE_MIN_BOUNDED_ARRAY = EXPOSURE_DIRECT + 1;
+
+ /**
+ * Type parameter is not exposed.
+ */
+ static final int EXPOSURE_NONE = -1;
+
private TypeFilter typeFilter;
private final Map<JTypeParameter, TypeParameterFlowInfo> typeParameterToFlowInfo = new IdentityHashMap<JTypeParameter, TypeParameterFlowInfo>();
@@ -389,8 +409,7 @@
/**
* Return the parameter flow info for a type parameter specified by class and
* index. If the flow info did not previously exist, create it and add it to
- * the work list. TODO(spoon): if the flow info has already been computed,
- * then make a perfect/no-search flow info for this type parameter.
+ * the work list.
*/
private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) {
JTypeParameter typeParameter = type.getTypeParameters()[index];
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index d3e96ce..9b6580d 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -68,6 +68,7 @@
this.maybeInstantiated = maybeInstantiated;
}
+ @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
@@ -89,6 +90,10 @@
}
}
+ private static final int EXPOSURE_DIRECT = TypeParameterExposureComputer.EXPOSURE_DIRECT;
+
+ private static final int EXPOSURE_NONE = TypeParameterExposureComputer.EXPOSURE_NONE;
+
private static void addICRSE(Set<CompilationUnit> units) {
StringBuffer code = new StringBuffer();
code.append("package com.google.gwt.user.client.rpc;\n");
@@ -335,7 +340,7 @@
JClassType cantSerialize = to.getType("CantSerialize");
JParameterizedType listOfCantSerialize = to.getParameterizedType(list,
- new JClassType[] {cantSerialize});
+ makeArray(cantSerialize));
SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
logger, to);
@@ -400,7 +405,118 @@
}
/*
- * Tests that arrays of type variables that do not cause infinite expansion.
+ * Tests arrays of parameterized types.
+ */
+ public void testArrayOfParameterizedTypes() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ // A<T> exposes its param
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class A<T> implements Serializable {\n");
+ code.append(" T t;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("A", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class AList<T> implements Serializable {\n");
+ code.append(" A<T>[] as;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("AList", code));
+ }
+
+ {
+ // B<T> does not expose its param
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class B<T> implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("B", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class BList<T> implements Serializable {\n");
+ code.append(" B<T>[] bs;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("BList", code));
+ }
+
+ {
+ // A random serializable class
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class Ser1 implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Ser1", code));
+ }
+
+ {
+ // A random serializable class
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class Ser2 implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Ser2", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class Root implements Serializable {\n");
+ code.append(" AList<Ser1> alist;\n");
+ code.append(" BList<Ser2> blist;\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("Root", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JGenericType a = to.getType("A").isGenericType();
+ JGenericType b = to.getType("B").isGenericType();
+ JGenericType alist = to.getType("AList").isGenericType();
+ JGenericType blist = to.getType("BList").isGenericType();
+ JClassType ser1 = to.getType("Ser1");
+ JClassType ser2 = to.getType("Ser2");
+ JClassType root = to.getType("Root");
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, root);
+
+ assertEquals(EXPOSURE_DIRECT, sob.getTypeParameterExposure(a, 0));
+ assertEquals(EXPOSURE_NONE, sob.getTypeParameterExposure(b, 0));
+ assertEquals(EXPOSURE_DIRECT, sob.getTypeParameterExposure(alist, 0));
+ assertEquals(EXPOSURE_NONE, sob.getTypeParameterExposure(blist, 0));
+
+ SerializableTypeOracle so = sob.build(logger);
+
+ JArrayType aArray = to.getArrayType(a.getRawType());
+ JArrayType bArray = to.getArrayType(b.getRawType());
+
+ assertSerializableTypes(so, root, alist.getRawType(), blist.getRawType(),
+ aArray, bArray, a.getRawType(), b.getRawType(), ser1);
+
+ assertInstantiable(so, alist.getRawType());
+ assertInstantiable(so, blist.getRawType());
+ assertInstantiable(so, a.getRawType());
+ assertInstantiable(so, b.getRawType());
+ assertInstantiable(so, aArray);
+ assertInstantiable(so, bArray);
+ assertInstantiable(so, ser1);
+ assertNotInstantiableOrFieldSerializable(so, ser2);
+ }
+
+ /*
+ * Tests arrays of type variables that do not cause infinite expansion.
*/
public void testArrayOfTypeParameter() throws UnableToCompleteException,
NotFoundException {
@@ -452,7 +568,7 @@
JClassType javaLangString = to.getType(String.class.getName());
JParameterizedType cOfString = to.getParameterizedType(c,
- new JClassType[] {javaLangString});
+ makeArray(javaLangString));
SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
logger, to);
sob.addRootType(logger, cOfString);
@@ -757,7 +873,7 @@
JClassType serializableArgument = to.getType("SerializableArgument");
JParameterizedType aOfString = to.getParameterizedType(a,
- new JClassType[] {serializableArgument});
+ makeArray(serializableArgument));
SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
logger, to);
sob.addRootType(logger, aOfString);
@@ -812,7 +928,7 @@
JClassType unusedSerializableArgument = to.getType("UnusedSerializableArgument");
JParameterizedType aOfString = to.getParameterizedType(a,
- new JClassType[] {unusedSerializableArgument});
+ makeArray(unusedSerializableArgument));
SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
logger, to);
sob.addRootType(logger, aOfString);
@@ -860,7 +976,7 @@
JClassType javaLangString = to.getType(String.class.getName());
JParameterizedType aOfString = to.getParameterizedType(a,
- new JClassType[] {javaLangString});
+ makeArray(javaLangString));
SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
logger, to);
sob.addRootType(logger, aOfString);
@@ -908,12 +1024,12 @@
JClassType javaLangString = to.getType(String.class.getName());
JParameterizedType aOfString = to.getParameterizedType(a,
- new JClassType[] {javaLangString});
+ makeArray(javaLangString));
SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
logger, to);
- assertEquals(0, sob.getTypeParameterExposure(a, 0));
- assertEquals(0, sob.getTypeParameterExposure(b, 0));
+ assertEquals(EXPOSURE_DIRECT, sob.getTypeParameterExposure(a, 0));
+ assertEquals(EXPOSURE_DIRECT, sob.getTypeParameterExposure(b, 0));
sob.addRootType(logger, aOfString);
SerializableTypeOracle so = sob.build(logger);
@@ -1169,12 +1285,12 @@
JClassType javaLangString = to.getType(String.class.getName());
JParameterizedType aOfString = to.getParameterizedType(a,
- new JClassType[] {javaLangString});
+ makeArray(javaLangString));
SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
logger, to);
stob.addRootType(logger, aOfString);
- assertTrue(stob.getTypeParameterExposure(a, 0) < 0);
+ assertEquals(EXPOSURE_NONE, stob.getTypeParameterExposure(a, 0));
SerializableTypeOracle so = stob.build(logger);
@@ -1387,7 +1503,7 @@
JClassType b = to.getType("B");
JTypeParameter syntheticTypeParam = new JTypeParameter("U", 0);
- syntheticTypeParam.setBounds(new JClassType[] {b});
+ syntheticTypeParam.setBounds(makeArray(b));
JParameterizedType parameterizedType = to.getParameterizedType(a,
new JClassType[] {syntheticTypeParam});
@@ -1400,4 +1516,8 @@
assertInstantiable(so, b);
assertSerializableTypes(so, rawA, b);
}
+
+ private JClassType[] makeArray(JClassType... elements) {
+ return elements;
+ }
}