Do not visit the fields of enumerated types and do not consider nested types that are not visible to a class in the parent package.

Review by: spoon (desk check)

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2872 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 6478697..3a9bf67 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -497,6 +497,22 @@
     return isSpeculative ? TreeLogger.WARN : TreeLogger.ERROR;
   }
 
+  /**
+   * Returns <code>true</code> if the query type is accessible to classes in
+   * the same package.
+   */
+  private static boolean isAccessibleToClassesInSamePackage(JClassType type) {
+    if (type.isPrivate()) {
+      return false;
+    }
+
+    if (type.isMemberType()) {
+      return isAccessibleToClassesInSamePackage(type.getEnclosingType());
+    }
+
+    return true;
+  }
+
   private static void logSerializableTypes(TreeLogger logger,
       Set<JClassType> fieldSerializableTypes) {
     TreeLogger localLogger = logger.branch(TreeLogger.DEBUG, "Identified "
@@ -943,7 +959,8 @@
   final boolean checkTypeInstantiableNoSubtypes(TreeLogger logger,
       JClassType type, boolean isSpeculative, Path path) {
     if (qualifiesForSerialization(logger, type, isSpeculative, path)) {
-      return checkFields(logger, type, isSpeculative, path);
+      return type.isEnum() != null
+          || checkFields(logger, type, isSpeculative, path);
     }
 
     return false;
@@ -955,6 +972,8 @@
 
   /**
    * Returns <code>true</code> if the type qualifies for serialization.
+   * 
+   * Default access to allow for testing.
    */
   boolean qualifiesForSerialization(TreeLogger logger, JClassType type,
       boolean isSpeculative, Path parent) {
@@ -985,30 +1004,6 @@
     } else {
       assert (typeInfo.isAutoSerializable());
 
-      if (type.isEnum() != null) {
-        if (type.isLocalType()) {
-          /*
-           * Quietly ignore local enum types.
-           */
-          return false;
-        } else {
-          /*
-           * Enumerated types are serializable by default, but they do not have
-           * their state automatically or manually serialized. So, consider it
-           * serializable but do not check its fields.
-           */
-          return !type.isPrivate();
-        }
-      }
-
-      if (type.isPrivate()) {
-        /*
-         * Quietly ignore private types since these cannot be instantiated from
-         * the generated field serializers.
-         */
-        return false;
-      }
-
       /*
        * Speculative paths log at DEBUG level, non-speculative ones log at WARN
        * level.
@@ -1016,7 +1011,18 @@
       TreeLogger.Type logLevel = isSpeculative ? TreeLogger.DEBUG
           : TreeLogger.WARN;
 
+      if (!isAccessibleToClassesInSamePackage(type)) {
+        // Class is not visible to a serializer class in the same package
+        logger.branch(
+            logLevel,
+            type.getParameterizedQualifiedSourceName()
+                + " is not accessible from a class in its same package; it will be excluded from the set of serializable types",
+            null);
+        return false;
+      }
+
       if (type.isLocalType()) {
+        // Local types cannot be serialized
         logger.branch(
             logLevel,
             type.getParameterizedQualifiedSourceName()
@@ -1026,6 +1032,7 @@
       }
 
       if (type.isMemberType() && !type.isStatic()) {
+        // Non-static member types cannot be serialized
         logger.branch(
             logLevel,
             type.getParameterizedQualifiedSourceName()
@@ -1034,18 +1041,26 @@
         return false;
       }
 
-      if (type.isAbstract()) {
-        // Abstract types will be picked up if there is an instantiable subtype.
-        return false;
-      }
+      if (type.isEnum() == null) {
+        if (type.isAbstract()) {
+          // Abstract types will be picked up if there is an instantiable
+          // subtype.
+          return false;
+        }
 
-      if (!type.isDefaultInstantiable()) {
-        // Warn and return false.
-        logger.log(
-            logLevel,
-            "Was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
-            null);
-        return false;
+        if (!type.isDefaultInstantiable()) {
+          // Warn and return false.
+          logger.log(
+              logLevel,
+              "Was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
+              null);
+          return false;
+        }
+      } else {
+        /*
+         * Enums are always instantiable regardless of abstract or default
+         * instantiability.
+         */
       }
     }
 
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 bec1bbd..120907c 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -431,6 +431,179 @@
   }
 
   /**
+   * Tests the rules that govern whether a type qualifies for serialization.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testClassQualifiesForSerialization()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public class NotSerializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("NotSerializable", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class AutoSerializable {\n");
+      code.append("  interface IFoo extends Serializable {};\n");
+      code.append("  IFoo createFoo() { return new IFoo(){};}\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("AutoSerializable", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class OuterClass {\n");
+      code.append("  static class StaticNested implements Serializable {};\n");
+      code.append("  class NonStaticNested implements Serializable {};\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("OuterClass", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public abstract class AbstractSerializableClass implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("AbstractSerializableClass", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class NonDefaultInstantiableSerializable implements Serializable {\n");
+      code.append("  NonDefaultInstantiableSerializable(int i) {}\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("NonDefaultInstantiableSerializable",
+          code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class PublicOuterClass {\n");
+      code.append("  private static class PrivateStaticInner {\n");
+      code.append("    public static class PublicStaticInnerInner implements Serializable {\n");
+      code.append("    }\n");
+      code.append("  }\n");
+      code.append("  static class DefaultStaticInner {\n");
+      code.append("    static class DefaultStaticInnerInner implements Serializable {\n");
+      code.append("    }\n");
+      code.append("  }\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("PublicOuterClass", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public enum EnumWithSubclasses {\n");
+      code.append("  A {\n");
+      code.append("    @Override\n");
+      code.append("    public String value() {\n");
+      code.append("      return \"X\";\n");
+      code.append("    }\n");
+      code.append("  },\n");
+      code.append("  B {\n");
+      code.append("    @Override\n");
+      code.append("    public String value() {\n");
+      code.append("      return \"Y\";\n");
+      code.append("    };\n");
+      code.append("  };\n");
+      code.append("  public abstract String value();\n");
+      code.append("};\n");
+      units.add(createMockCompilationUnit("EnumWithSubclasses", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public enum EnumWithNonDefaultCtors {\n");
+      code.append("  A(\"X\"), B(\"Y\");\n");
+      code.append("  String value;");
+      code.append("  private EnumWithNonDefaultCtors(String value) {\n");
+      code.append("    this.value = value;\n");
+      code.append("  }\n");
+      code.append("};\n");
+      units.add(createMockCompilationUnit("EnumWithNonDefaultCtors", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("package java.lang;\n");
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Enum<E extends Enum<E>> implements Serializable {\n");
+      code.append("  protected Enum(String name, int ordinal) {\n");
+      code.append("  }\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("java.lang.Enum", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+
+    // Does not qualify because it is not declared to be auto or manually
+    // serializable
+    JClassType notSerializable = to.getType("NotSerializable");
+    assertFalse(sob.qualifiesForSerialization(logger, notSerializable, false,
+        null));
+
+    // Local types should not qualify for serialization
+    JClassType iFoo = to.getType("AutoSerializable.IFoo");
+    assertFalse(sob.qualifiesForSerialization(logger, iFoo.getSubtypes()[0],
+        false, null));
+
+    // Static nested types qualify for serialization
+    JClassType staticNested = to.getType("OuterClass.StaticNested");
+    assertTrue(sob.qualifiesForSerialization(logger, staticNested, false, null));
+
+    // Non-static nested types do not qualify for serialization
+    JClassType nonStaticNested = to.getType("OuterClass.NonStaticNested");
+    assertFalse(sob.qualifiesForSerialization(logger, nonStaticNested, false,
+        null));
+
+    // Abstract classes that implement Serializable should not qualify
+    JClassType abstractSerializableClass = to.getType("AbstractSerializableClass");
+    assertFalse(sob.qualifiesForSerialization(logger,
+        abstractSerializableClass, false, null));
+
+    // Non-default instantiable types should not qualify
+    JClassType nonDefaultInstantiableSerializable = to.getType("NonDefaultInstantiableSerializable");
+    assertFalse(sob.qualifiesForSerialization(logger,
+        nonDefaultInstantiableSerializable, false, null));
+
+    // SPublicStaticInnerInner is not accessible to classes in its package
+    JClassType publicStaticInnerInner = to.getType("PublicOuterClass.PrivateStaticInner.PublicStaticInnerInner");
+    assertFalse(sob.qualifiesForSerialization(logger, publicStaticInnerInner,
+        false, null));
+
+    // DefaultStaticInnerInner is visible to classes in its package
+    JClassType defaultStaticInnerInner = to.getType("PublicOuterClass.DefaultStaticInner.DefaultStaticInnerInner");
+    assertTrue(sob.qualifiesForSerialization(logger, defaultStaticInnerInner,
+        false, null));
+
+    // Enum with subclasses should qualify, but their subtypes should not
+    JClassType enumWithSubclasses = to.getType("EnumWithSubclasses");
+    assertTrue(sob.qualifiesForSerialization(logger, enumWithSubclasses, false,
+        null));
+    assertFalse(sob.qualifiesForSerialization(logger,
+        enumWithSubclasses.getSubtypes()[0], false, null));
+
+    // Enum that are not default instantiable should qualify
+    JClassType enumWithNonDefaultCtors = to.getType("EnumWithNonDefaultCtors");
+    assertTrue(sob.qualifiesForSerialization(logger, enumWithNonDefaultCtors,
+        false, null));
+  }
+
+  /**
    * Tests that both the generic and raw forms of type that has a type parameter
    * that erases to object are not serializable.
    *