Fixed covariant array computation to use the serializable types found by checkTypeInstantiable and to exclude covariant arrays that whose leaf type is in the java packages. Unit test for this case was added.
Factored type hierarchy utility methods into their own class TypeHierarchyUtils.
Added comment to STOI so we don't forget why we do not generate field serializers into the standard java packages.
Patch by: mmendez, spoon (pair prog)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2879 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 a347f8a..d99d873 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -73,7 +73,7 @@
*
* <p>
* To improve the accuracy of the traversal there is a computations of the
- * exposure of type parameters. When the traversal reaches a paramaterized type,
+ * exposure of type parameters. When the traversal reaches a parameterized type,
* these exposure values are used to determine how to treat the arguments.
* </p>
*
@@ -178,9 +178,10 @@
autoSerializable = type.isAssignableTo(isSerializableClass)
|| type.isAssignableTo(serializableClass);
manualSerializer = findCustomFieldSerializer(typeOracle, type);
- directlyImplementsMarker = directlyImplementsInterface(type,
- isSerializableClass)
- || directlyImplementsInterface(type, serializableClass);
+ directlyImplementsMarker = TypeHierarchyUtils.directlyImplementsInterface(
+ type, isSerializableClass)
+ || TypeHierarchyUtils.directlyImplementsInterface(type,
+ serializableClass);
}
public JClassType getManualSerializer() {
@@ -320,6 +321,26 @@
}
/**
+ * Returns <code>true</code> if this type is part of the standard java
+ * packages.
+ *
+ * NOTE: This code is copied from
+ * {@link com.google.gwt.dev.shell.CompilingClassLoader CompilingClassLoader};
+ * don't change one without changing the other.
+ */
+ static boolean isInStandardJavaPackage(String className) {
+ if (className.startsWith("java.")) {
+ return true;
+ }
+
+ if (className.startsWith("javax.")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Returns <code>true</code> if the field qualifies for serialization
* without considering its type.
*/
@@ -442,43 +463,6 @@
};
}
- /**
- * Returns <code>true</code> if the type directly implements the specified
- * interface.
- *
- * @param type type to check
- * @param intf interface to look for
- * @return <code>true</code> if the type directly implements the specified
- * interface
- */
- private static boolean directlyImplementsInterface(JClassType type,
- JClassType intf) {
- return directlyImplementsInterfaceRecursive(new HashSet<JClassType>(),
- type, intf);
- }
-
- private static boolean directlyImplementsInterfaceRecursive(
- Set<JClassType> seen, JClassType clazz, JClassType intf) {
-
- if (clazz == intf) {
- return true;
- }
-
- JClassType[] intfImpls = clazz.getImplementedInterfaces();
-
- for (JClassType intfImpl : intfImpls) {
- if (!seen.contains(intfImpl)) {
- seen.add(intfImpl);
-
- if (directlyImplementsInterfaceRecursive(seen, intfImpl, intf)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
private static JArrayType getArrayType(TypeOracle typeOracle, int rank,
JType component) {
assert (rank > 0);
@@ -498,16 +482,21 @@
}
/**
- * Returns <code>true</code> if the query type is accessible to classes in
- * the same package.
+ * Returns <code>true</code> if a serializer class could access this type.
*/
- private static boolean isAccessibleToClassesInSamePackage(JClassType type) {
+ private static boolean isAccessibleToSerializer(JClassType type) {
if (type.isPrivate() || type.isLocalType()) {
return false;
}
+ if (isInStandardJavaPackage(type.getQualifiedSourceName())) {
+ if (!type.isPublic()) {
+ return false;
+ }
+ }
+
if (type.isMemberType()) {
- return isAccessibleToClassesInSamePackage(type.getEnclosingType());
+ return isAccessibleToSerializer(type.getEnclosingType());
}
return true;
@@ -692,6 +681,16 @@
*/
final boolean checkTypeInstantiable(TreeLogger logger, JType type,
boolean isSpeculative, final Path path) {
+ return checkTypeInstantiable(logger, type, isSpeculative, path, null);
+ }
+
+ /**
+ * Same as
+ * {@link #checkTypeInstantiable(TreeLogger, JType, boolean, com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder.Path)},
+ * except that returns the set of instantiable subtypes.
+ */
+ boolean checkTypeInstantiable(TreeLogger logger, JType type,
+ boolean isSpeculative, final Path path, Set<JClassType> instSubtypes) {
assert (type != null);
if (type.isPrimitive() != null) {
return true;
@@ -744,47 +743,7 @@
JArrayType isArray = classType.isArray();
if (isArray != null) {
- JType leafType = isArray.getLeafType();
- JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
- if (isLeafTypeParameter != null
- && !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
- // Don't deal with non root type parameters
- tic.setInstantiable(false);
- tic.setInstantiableSubytpes(true);
- return true;
- }
-
- boolean succeeded = checkArrayInstantiable(localLogger, isArray,
- isSpeculative, path);
- if (succeeded) {
- JClassType leafClass = leafType.isClassOrInterface();
- if (leafClass != null) {
- JClassType[] leafSubtypes = leafClass.getErasedType().getSubtypes();
- for (JClassType leafSubtype : leafSubtypes) {
- if (leafSubtype.isRawType() != null) {
- /*
- * Convert from a generic type into a parameterization of the said
- * generic type where each argument is the type argument. The goal
- * is have checkTypeInstantiable not check the type parameters.
- */
- JGenericType leafGenericSub = leafSubtype.isRawType().getBaseType();
- leafSubtype = typeOracle.getParameterizedType(leafGenericSub,
- leafGenericSub.getTypeParameters());
- }
-
- if (!isAccessibleToClassesInSamePackage(leafSubtype)) {
- continue;
- }
-
- JArrayType covariantArray = getArrayType(typeOracle,
- isArray.getRank(), leafSubtype);
- checkTypeInstantiable(localLogger, covariantArray, true, path);
- }
- }
- }
-
- tic.setInstantiable(succeeded);
- return succeeded;
+ return checkArrayInstantiable(localLogger, tic, isSpeculative, path);
}
if (classType == typeOracle.getJavaLangObject()) {
@@ -952,6 +911,10 @@
}
if (subInstantiable) {
+ if (instSubtypes != null) {
+ instSubtypes.add(subtype);
+ }
+
// TODO: This is suspect.
getTypeInfoComputed(subtype, path).setInstantiable(true);
anySubtypes = true;
@@ -1031,7 +994,7 @@
TreeLogger.Type logLevel = isSpeculative ? TreeLogger.DEBUG
: TreeLogger.WARN;
- if (!isAccessibleToClassesInSamePackage(type)) {
+ if (!isAccessibleToSerializer(type)) {
// Class is not visible to a serializer class in the same package
logger.branch(
logLevel,
@@ -1114,13 +1077,54 @@
}
}
- private boolean checkArrayInstantiable(TreeLogger logger,
- final JArrayType arrayType, boolean isSpeculative, final Path parent) {
- TreeLogger branch = logger.branch(TreeLogger.DEBUG,
- "Analyzing component type:", null);
+ private boolean checkArrayInstantiable(TreeLogger localLogger,
+ TypeInfoComputed tic, boolean isSpeculative, final Path path) {
+ JArrayType isArray = tic.getType().isArray();
+ assert (isArray != null);
- return checkTypeInstantiable(branch, arrayType.getComponentType(),
- isSpeculative, createArrayComponentPath(arrayType, parent));
+ JType leafType = isArray.getLeafType();
+ JClassType leafClass = leafType.isClassOrInterface();
+ JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
+ if (isLeafTypeParameter != null
+ && !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
+ // Don't deal with non root type parameters
+ tic.setInstantiable(false);
+ tic.setInstantiableSubytpes(true);
+ return true;
+ }
+
+ TreeLogger branch = localLogger.branch(TreeLogger.DEBUG,
+ "Analyzing component type:", null);
+ Set<JClassType> instantiableTypes = new HashSet<JClassType>();
+
+ boolean succeeded = checkTypeInstantiable(branch,
+ isArray.getComponentType(), isSpeculative, createArrayComponentPath(
+ isArray, path), instantiableTypes);
+ if (succeeded && leafClass != null) {
+ /*
+ * Compute covariant arrays for arrays of reference types.
+ */
+ for (JClassType instantiableType : TypeHierarchyUtils.getAllTypesBetweenRootTypeAndLeaves(
+ leafClass, instantiableTypes)) {
+ if (!isAccessibleToSerializer(instantiableType)) {
+ // Skip types that are not accessible from a serializer
+ continue;
+ }
+
+ for (int rank = 1; rank <= isArray.getRank(); ++rank) {
+ JArrayType covariantArray = getArrayType(typeOracle, rank,
+ instantiableType);
+
+ // TODO: Check the choice of path
+ TypeInfoComputed covariantArrayTic = getTypeInfoComputed(
+ covariantArray, path);
+ covariantArrayTic.setInstantiable(true);
+ }
+ }
+ }
+
+ tic.setInstantiable(succeeded);
+ return succeeded;
}
private boolean checkFields(TreeLogger logger, JClassType classOrInterface,
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
index 41f1653..993987d 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
@@ -65,7 +65,8 @@
private final Set<JClassType> possiblyInstantiatedTypes;
public SerializableTypeOracleImpl(TypeOracle typeOracle,
- Set<JClassType> serializableTypes, Set<JClassType> possiblyInstantiatedTypes) {
+ Set<JClassType> serializableTypes,
+ Set<JClassType> possiblyInstantiatedTypes) {
serializableTypesSet = serializableTypes;
this.typeOracle = typeOracle;
@@ -89,7 +90,12 @@
GENERATED_FIELD_SERIALIZER_SUFFIX);
if (name[0].length() > 0) {
String serializerName = name[0] + "." + name[1];
- if (name[0].startsWith("java.")) {
+ if (SerializableTypeOracleBuilder.isInStandardJavaPackage(type.getQualifiedSourceName())) {
+ /*
+ * Don't generate code into java packages. If you do hosted mode
+ * CompilingClassLoader will fail to resolve references to the generated
+ * code.
+ */
serializerName = "com.google.gwt.user.client.rpc.core."
+ serializerName;
}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java b/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java
new file mode 100644
index 0000000..bcdc1fd
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 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.gwt.user.rebind.rpc;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Collection of utility methods for dealing with type hierachies.
+ */
+class TypeHierarchyUtils {
+
+ /**
+ * Returns <code>true</code> if the type directly implements the specified
+ * interface.
+ *
+ * @param type type to check
+ * @param intf interface to look for
+ * @return <code>true</code> if the type directly implements the specified
+ * interface
+ */
+ public static boolean directlyImplementsInterface(JClassType type,
+ JClassType intf) {
+ return directlyImplementsInterfaceRecursive(new HashSet<JClassType>(),
+ type, intf);
+ }
+
+ /**
+ * Returns all types on the path from the root type to the serializable
+ * leaves.
+ *
+ * @param root the root type
+ * @param leaves the set of serializable leaf types
+ * @return all types on the path from the root type to the serializable leaves
+ */
+ public static List<JClassType> getAllTypesBetweenRootTypeAndLeaves(
+ JClassType root, Collection<JClassType> leaves) {
+ Map<JClassType, List<JClassType>> adjList = getInvertedTypeHierarchy(root.getErasedType());
+ Set<JClassType> types = new HashSet<JClassType>();
+
+ for (JClassType type : leaves) {
+ TypeHierarchyUtils.depthFirstSearch(types, adjList, type.getErasedType());
+ }
+
+ return Arrays.asList(types.toArray(new JClassType[0]));
+ }
+
+ private static void addEdge(Map<JClassType, List<JClassType>> adjList,
+ JClassType subclass, JClassType clazz) {
+ List<JClassType> edges = adjList.get(subclass);
+ if (edges == null) {
+ edges = new ArrayList<JClassType>();
+ adjList.put(subclass, edges);
+ }
+
+ edges.add(clazz);
+ }
+
+ private static void depthFirstSearch(Set<JClassType> seen,
+ Map<JClassType, List<JClassType>> adjList, JClassType type) {
+ if (seen.contains(type)) {
+ return;
+ }
+ seen.add(type);
+
+ List<JClassType> children = adjList.get(type);
+ if (children != null) {
+ for (JClassType child : children) {
+ if (!seen.contains(child)) {
+ depthFirstSearch(seen, adjList, child);
+ }
+ }
+ }
+ }
+
+ private static boolean directlyImplementsInterfaceRecursive(
+ Set<JClassType> seen, JClassType clazz, JClassType intf) {
+
+ if (clazz == intf) {
+ return true;
+ }
+
+ JClassType[] intfImpls = clazz.getImplementedInterfaces();
+
+ for (JClassType intfImpl : intfImpls) {
+ if (!seen.contains(intfImpl)) {
+ seen.add(intfImpl);
+
+ if (directlyImplementsInterfaceRecursive(seen, intfImpl, intf)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Given a root type return an adjacency list that is the inverted type
+ * hierarchy.
+ */
+ private static Map<JClassType, List<JClassType>> getInvertedTypeHierarchy(
+ JClassType root) {
+ Map<JClassType, List<JClassType>> adjList = new HashMap<JClassType, List<JClassType>>();
+ Set<JClassType> seen = new HashSet<JClassType>();
+ Stack<JClassType> queue = new Stack<JClassType>();
+ queue.push(root);
+ while (!queue.isEmpty()) {
+ JClassType clazz = queue.pop();
+ JClassType[] subclasses = clazz.getSubtypes();
+
+ if (seen.contains(clazz)) {
+ continue;
+ }
+ seen.add(clazz);
+
+ for (JClassType subclass : subclasses) {
+ if (clazz.isInterface() != null) {
+ if (TypeHierarchyUtils.directlyImplementsInterface(subclass, clazz)) {
+ TypeHierarchyUtils.addEdge(adjList, subclass, clazz);
+ queue.push(subclass);
+ }
+ } else {
+ if (subclass.getSuperclass() == clazz) {
+ TypeHierarchyUtils.addEdge(adjList, subclass, clazz);
+ queue.push(subclass);
+ }
+ }
+ }
+ }
+
+ return adjList;
+ }
+
+}
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 120907c..1182adf 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -346,6 +346,59 @@
assertSerializableTypes(so, list.getRawType(), emptyList.getRawType());
}
+ /**
+ * Tests that we do not violate java package restrictions when computing
+ * serializable types.
+ */
+ public void testAccessLevelsInJavaPackage() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("package java;\n");
+ code.append("import java.io.Serializable;\n");
+ code.append("public class A implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("java.A", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("package java;\n");
+ code.append("import java.io.Serializable;\n");
+ code.append("class B extends A {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("java.B", code));
+ }
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("package java;\n");
+ code.append("import java.io.Serializable;\n");
+ code.append("public class C extends A {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("java.C", code));
+ }
+
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ JClassType a = to.getType("java.A");
+ JArrayType arrayOfA = to.getArrayType(a);
+
+ JClassType c = to.getType("java.C");
+ JArrayType arrayOfC = to.getArrayType(c);
+
+ SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+ logger, to);
+ sob.addRootType(logger, arrayOfA);
+ SerializableTypeOracle so = sob.build(logger);
+
+ assertSerializableTypes(so, arrayOfA, arrayOfC, a, c);
+ }
+
/*
* Tests that arrays of type variables that do not cause infinite expansion.
*/