Fixes a bug in SerializableTypeOracleBuilder where multi-dimensional
array types might not also pull in array types with lower rank.
A fast path was not computing complete information.

Review by: fabbott


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5982 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 5522faa..85147ed 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -115,7 +115,12 @@
  */
 public class SerializableTypeOracleBuilder {
 
-  private class TypeInfoComputed {
+  class TypeInfoComputed {
+    /**
+     * All instantiable types found when this type was quaried, including the
+     * type itself.
+     */
+    private Set<JClassType> instantiableTypes = new HashSet<JClassType>();
 
     /**
      * <code>true</code> if the type is assignable to {@link IsSerializable} or
@@ -170,15 +175,35 @@
     /**
      * {@link JClassType} associated with this metadata.
      */
-    private final JClassType type;
+    private final JType type;
 
-    public TypeInfoComputed(JClassType type, TypePath path) {
+    public TypeInfoComputed(JType type, TypePath path) {
       this.type = type;
       this.path = path;
-      autoSerializable = SerializableTypeOracleBuilder.isAutoSerializable(type);
-      manualSerializer = findCustomFieldSerializer(typeOracle, type);
-      directlyImplementsMarker = directlyImplementsMarkerInterface(type);
-      maybeEnhanced = hasJdoAnnotation(type) || hasJpaAnnotation(type);
+      if (type instanceof JClassType) {
+        JClassType typeClass = (JClassType) type;
+        autoSerializable = SerializableTypeOracleBuilder.isAutoSerializable(typeClass);
+        manualSerializer = findCustomFieldSerializer(typeOracle, typeClass);
+        directlyImplementsMarker = directlyImplementsMarkerInterface(typeClass);
+        maybeEnhanced = hasJdoAnnotation(typeClass)
+            || hasJpaAnnotation(typeClass);
+      } else {
+        autoSerializable = false;
+        manualSerializer = null;
+        directlyImplementsMarker = false;
+        maybeEnhanced = false;
+      }
+    }
+
+    /**
+     * Returns the internal set of instantiable types for this TIC.
+     * Modifications to this set are immediately recorded into the TIC as well.
+     * TODO(spoon) maybe pass the TIC around instead of the set?
+     *  then there could be addInstantiableType(JClassType) instead of
+     *  speccing this to be mutable.
+     */
+    public Set<JClassType> getInstantiableTypes() {
+      return instantiableTypes;
     }
 
     public JClassType getManualSerializer() {
@@ -189,12 +214,13 @@
       return path;
     }
 
-    public JClassType getType() {
+    public JType getType() {
       return type;
     }
 
     public boolean hasInstantiableSubtypes() {
-      return isInstantiable() || instantiableSubtypes;
+      return isInstantiable() || instantiableSubtypes
+          || isPendingInstantiable();
     }
 
     public boolean isAutoSerializable() {
@@ -794,8 +820,8 @@
     for (Entry<JClassType, TreeLogger> entry : rootTypes.entrySet()) {
       ProblemReport problems = new ProblemReport();
       problems.setContextType(entry.getKey());
-      boolean entrySucceeded = checkTypeInstantiable(entry.getValue(),
-          entry.getKey(), TypePaths.createRootPath(entry.getKey()), problems);
+      boolean entrySucceeded = computeTypeInstantiability(entry.getValue(),
+          entry.getKey(), TypePaths.createRootPath(entry.getKey()), problems).hasInstantiableSubtypes();
       if (!entrySucceeded) {
         problems.report(logger, TreeLogger.ERROR, TreeLogger.INFO);
       } else {
@@ -836,7 +862,10 @@
         JTYPE_COMPARATOR);
 
     for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
-      JClassType type = tic.getType();
+      if (!(tic.getType() instanceof JClassType)) {
+        continue;
+      }
+      JClassType type = (JClassType) tic.getType();
 
       type = type.getErasedType();
 
@@ -878,36 +907,27 @@
   }
 
   /**
-   * This method determines whether a type can be serialized by GWT. To do so,
-   * it must traverse all subtypes as well as all field types of those types,
-   * transitively.
-   *
-   * It returns a boolean indicating whether this type or any of its subtypes
-   * are instantiable.
-   *
+   * This method determines information about serializing a type with GWT. To do
+   * so, it must traverse all subtypes as well as all field types of those
+   * types, transitively.
+   * 
+   * It returns a {@link TypeInfoComputed} with the information found.
+   * 
    * As a side effect, all types needed--plus some--to serialize this type are
-   * accumulated in {@link #typeToTypeInfoComputed}.  In particular, there
-   * will be an entry for any type that has been validated by this method, as
-   * a shortcircuit to avoid recomputation.
-   *
+   * accumulated in {@link #typeToTypeInfoComputed}. In particular, there will
+   * be an entry for any type that has been validated by this method, as a
+   * shortcircuit to avoid recomputation.
+   * 
    * The method is exposed using default access to enable testing.
    */
-  final boolean checkTypeInstantiable(TreeLogger logger, JType type,
+  TypeInfoComputed computeTypeInstantiability(TreeLogger logger, JType type,
       TypePath path, ProblemReport problems) {
-    return checkTypeInstantiable(logger, type, path, new HashSet<JClassType>(),
-        problems);
-  }
-
-  /**
-   * Same as
-   * {@link #checkTypeInstantiable(TreeLogger, JType, boolean, com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder.TypePath)}
-   * , except that returns the set of instantiable subtypes.
-   */
-  boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      TypePath path, Set<JClassType> instSubtypes, ProblemReport problems) {
     assert (type != null);
     if (type.isPrimitive() != null) {
-      return true;
+      TypeInfoComputed tic = getTypeInfoComputed(type, path, true);
+      tic.setInstantiableSubtypes(true);
+      tic.setInstantiable(false);
+      return tic;
     }
 
     assert (type instanceof JClassType);
@@ -917,7 +937,7 @@
     TypeInfoComputed tic = getTypeInfoComputed(classType, path, false);
     if (tic != null && tic.isDone()) {
       // we have an answer already; use it.
-      return tic.hasInstantiableSubtypes();
+      return tic;
     }
 
     TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
@@ -926,10 +946,10 @@
     JTypeParameter isTypeParameter = classType.isTypeParameter();
     if (isTypeParameter != null) {
       if (typeParametersInRootTypes.contains(isTypeParameter)) {
-        return checkTypeInstantiable(localLogger,
+        return computeTypeInstantiability(localLogger,
             isTypeParameter.getFirstBound(),
             TypePaths.createTypeParameterInRootPath(path, isTypeParameter),
-            instSubtypes, problems);
+            problems);
       }
 
       /*
@@ -940,27 +960,27 @@
       tic = getTypeInfoComputed(classType, path, true);
       tic.setInstantiableSubtypes(true);
       tic.setInstantiable(false);
-      return true;
+      return tic;
     }
 
     JWildcardType isWildcard = classType.isWildcard();
     if (isWildcard != null) {
       boolean success = true;
       for (JClassType bound : isWildcard.getUpperBounds()) {
-        success &= checkTypeInstantiable(localLogger, bound, path, problems);
+        success &= computeTypeInstantiability(localLogger, bound, path, problems).hasInstantiableSubtypes();
       }
       tic = getTypeInfoComputed(classType, path, true);
       tic.setInstantiableSubtypes(success);
       tic.setInstantiable(false);
-      return success;
+      return tic;
     }
 
     JArrayType isArray = classType.isArray();
     if (isArray != null) {
-      boolean success = checkArrayInstantiable(localLogger, isArray, path,
+      TypeInfoComputed arrayTic = checkArrayInstantiable(localLogger, isArray, path,
           problems);
       assert getTypeInfoComputed(classType, path, false) != null;
-      return success;
+      return arrayTic;
     }
 
     if (classType == typeOracle.getJavaLangObject()) {
@@ -974,7 +994,7 @@
           "allowed; please use a more specific type", Priority.DEFAULT);
       tic = getTypeInfoComputed(classType, path, true);
       tic.setInstantiable(false);
-      return false;
+      return tic;
     }
 
     if (classType.isRawType() != null) {
@@ -990,14 +1010,14 @@
 
     // TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
     // "Analyzing subclasses:", null);
-    boolean anySubtypes = checkSubtypes(localLogger, originalType,
-        instSubtypes, path, problems);
     tic = getTypeInfoComputed(classType, path, true);
+    boolean anySubtypes = checkSubtypes(localLogger, originalType,
+        tic.getInstantiableTypes(), path, problems);
     if (!tic.isDone()) {
       tic.setInstantiableSubtypes(anySubtypes);
       tic.setInstantiable(false);
     }
-    return anySubtypes;
+    return tic;
   }
 
   int getTypeParameterExposure(JGenericType type, int index) {
@@ -1039,14 +1059,14 @@
     JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
     for (JClassType cls : allTypes) {
       if (isDeclaredSerializable(cls)) {
-        checkTypeInstantiable(localLogger, cls,
+        computeTypeInstantiability(localLogger, cls,
             TypePaths.createSubtypePath(parent, cls,
                 typeOracle.getJavaLangObject()), problems);
       }
     }
   }
 
-  private boolean checkArrayInstantiable(TreeLogger logger, JArrayType array,
+  private TypeInfoComputed checkArrayInstantiable(TreeLogger logger, JArrayType array,
       TypePath path, ProblemReport problems) {
 
     JType leafType = array.getLeafType();
@@ -1066,7 +1086,7 @@
       TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
       tic.setInstantiableSubtypes(true);
       tic.setInstantiable(false);
-      return true;
+      return tic;
     }
 
     if (!isAllowedByFilter(array, problems)) {
@@ -1074,24 +1094,23 @@
       // save time if it recurs.  We assume they're not instantiable.
       TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
       tic.setInstantiable(false);
-      return false;
+      return tic;
     }
 
     TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
     if (tic.isDone()) {
-      return tic.hasInstantiableSubtypes();
+      return tic;
     } else if (tic.isPendingInstantiable()) {
-      return true;
+      return tic;
     }
     tic.setPendingInstantiable();
 
     TreeLogger branch = logger.branch(TreeLogger.DEBUG,
         "Analyzing component type:", null);
-    Set<JClassType> instantiableTypes = new HashSet<JClassType>();
 
-    boolean succeeded = checkTypeInstantiable(branch, leafType,
-        TypePaths.createArrayComponentPath(array, path), instantiableTypes,
-        problems);
+    TypeInfoComputed leafTic = computeTypeInstantiability(branch, leafType,
+        TypePaths.createArrayComponentPath(array, path), problems);
+    boolean succeeded = leafTic.hasInstantiableSubtypes();
     if (succeeded) {
       if (leafClass == null) {
         assert leafType.isPrimitive() != null;
@@ -1104,7 +1123,7 @@
          * Compute covariant arrays for arrays of reference types.
          */
         for (JClassType instantiableType : TypeHierarchyUtils.getAllTypesBetweenRootTypeAndLeaves(
-            leafClass, instantiableTypes)) {
+            leafClass, leafTic.getInstantiableTypes())) {
           if (!isAccessibleToSerializer(instantiableType)) {
             // Skip types that are not accessible from a serializer
             continue;
@@ -1120,7 +1139,7 @@
     }
 
     tic.setInstantiable(succeeded);
-    return succeeded;
+    return tic;
   }
 
   /**
@@ -1131,7 +1150,7 @@
   private boolean checkDeclaredFields(TreeLogger logger,
       TypeInfoComputed typeInfo, TypePath parent, ProblemReport problems) {
 
-    JClassType classOrInterface = typeInfo.getType();
+    JClassType classOrInterface = (JClassType) typeInfo.getType();
     if (classOrInterface.isEnum() != null) {
       // The fields of an enum are never serialized; they are always okay.
       return true;
@@ -1166,8 +1185,8 @@
               "Object was reached from a manually serializable type", null),
               path, problems);
         } else {
-          allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType, path,
-              problems);
+          allSucceeded &= computeTypeInstantiability(fieldLogger, fieldType, path,
+              problems).hasInstantiableSubtypes();
         }
       }
     }
@@ -1376,7 +1395,7 @@
                 + " of type '"
                 + baseType.getParameterizedQualifiedSourceName()
                 + "' because it is directly exposed in this type or in one of its subtypes");
-        return checkTypeInstantiable(branch, typeArg, path, problems)
+        return computeTypeInstantiability(branch, typeArg, path, problems).hasInstantiableSubtypes()
             || mightNotBeExposed(baseType, paramIndex);
       }
       case TypeParameterExposureComputer.EXPOSURE_NONE:
@@ -1396,8 +1415,8 @@
                 + "' because it is exposed as an array with a maximum dimension of "
                 + exposure + " in this type or one of its subtypes",
             Priority.AUXILIARY);
-        return checkTypeInstantiable(logger, getArrayType(typeOracle, exposure,
-            typeArg), path, problems)
+        return computeTypeInstantiability(logger,
+            getArrayType(typeOracle, exposure, typeArg), path, problems).hasInstantiableSubtypes()
             || mightNotBeExposed(baseType, paramIndex);
       }
     }
@@ -1475,7 +1494,7 @@
     return possiblyInstantiableTypes;
   }
 
-  private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path,
+  private TypeInfoComputed getTypeInfoComputed(JType type, TypePath path,
       boolean createIfNeeded) {
     TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
     if (tic == null && createIfNeeded) {
@@ -1606,8 +1625,8 @@
      */
     Set<JType> supersOfInstantiableTypes = new LinkedHashSet<JType>();
     for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
-      if (tic.isInstantiable()) {
-        JClassType type = tic.getType().getErasedType();
+      if (tic.isInstantiable() && tic.getType() instanceof JClassType) {
+        JClassType type = (JClassType) tic.getType().getErasedType();
         JClassType sup = type;
         while (sup != null) {
           supersOfInstantiableTypes.add(sup.getErasedType());
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 a7f7fe6..9a2003d 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -1753,8 +1753,9 @@
     stob.addRootType(logger, topInterface);
 
     ProblemReport problems = new ProblemReport();
-    assertTrue("TopInterface should be (partially) serializable",
-        stob.checkTypeInstantiable(logger, topInterface, null, problems));
+    assertTrue(
+        "TopInterface should be (partially) serializable",
+        stob.computeTypeInstantiability(logger, topInterface, null, problems).hasInstantiableSubtypes());
     assertTrue("TopInterface should be a serializable type",
         problems.getProblemsForType(topInterface).isEmpty());
     assertTrue(
@@ -1779,8 +1780,8 @@
     problems = new ProblemReport();
     assertFalse(
         "PureAbstractClass should have no possible concrete implementation",
-        stob.checkTypeInstantiable(logger, to.getType("PureAbstractClass"),
-            null, problems));
+        stob.computeTypeInstantiability(logger, to.getType("PureAbstractClass"),
+            null, problems).hasInstantiableSubtypes());
     assertTrue(
         "PureAbstractClass should have a problem entry as the tested class",
         null != problems.getProblemsForType(to.getType("PureAbstractClass")));
@@ -1788,8 +1789,8 @@
     problems = new ProblemReport();
     assertFalse(
         "PureAbstractSerializable should have no possible concrete implementation",
-        stob.checkTypeInstantiable(logger,
-            to.getType("PureAbstractSerializable"), null, problems));
+        stob.computeTypeInstantiability(logger,
+            to.getType("PureAbstractSerializable"), null, problems).hasInstantiableSubtypes());
     assertFalse(
         "PureAbstractSerializable should have a problem entry",
         problems.getProblemsForType(to.getType("PureAbstractSerializable")).isEmpty());
@@ -1946,6 +1947,44 @@
     assertSerializableTypes(so, rawA);
   }
 
+  /**
+   * Tests that type String[][] also pulls in String[].
+   */
+  public void testStringArrayArray() throws NotFoundException,
+      UnableToCompleteException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Data implements Serializable {\n");
+      code.append("  String justOneString;");
+      code.append("  String[][] stringsGalore;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Data", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JClassType data = to.getType("Data");
+    JClassType string = to.getType(String.class.getCanonicalName());
+    JArrayType stringArray = to.getArrayType(string);
+    JArrayType stringArrayArray = to.getArrayType(stringArray);
+
+    SerializableTypeOracleBuilder sob = createSerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, data);
+
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertSerializableTypes(so, data, string, stringArray, stringArrayArray);
+    assertInstantiable(so, data);
+    assertInstantiable(so, stringArrayArray);
+    assertInstantiable(so, stringArray);
+  }
+
   /*
    * Tests the isAssignable test for deciding whether a subclass should be
    * pulled in.