Ensure that enum types reachable through AutoBean method parameterizations are included in the EnumMap.
Patch by: bobv
Review by: jbrosenberg

Review at http://gwt-code-reviews.appspot.com/1240801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9482 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
index c73ef79..9268c26 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
@@ -249,7 +249,7 @@
       AutoBeanMethod toAdd = builder.build();
 
       // Collect referenced enums
-      if (toAdd.isEnum()) {
+      if (toAdd.hasEnumMap()) {
         allEnumConstants.putAll(toAdd.getEnumMap());
       }
 
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
index f9b2d0d..0362aea 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.typeinfo.JEnumType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.editor.rebind.model.ModelUtils;
 
@@ -158,40 +159,29 @@
       toReturn.method = method;
       TypeOracle oracle = method.getEnclosingType().getOracle();
 
-      toReturn.isValueType = ModelUtils.isValueType(oracle,
-          method.getReturnType());
+      JType returnType = method.getReturnType();
+      toReturn.isValueType = ModelUtils.isValueType(oracle, returnType);
 
       if (!toReturn.isValueType) {
         // See if it's a collection or a map
-        JClassType returnClass = method.getReturnType().isClassOrInterface();
+        JClassType returnClass = returnType.isClassOrInterface();
         JClassType collectionInterface = oracle.findType(Collection.class.getCanonicalName());
         JClassType mapInterface = oracle.findType(Map.class.getCanonicalName());
         if (collectionInterface.isAssignableFrom(returnClass)) {
           JClassType[] parameterizations = ModelUtils.findParameterizationOf(
               collectionInterface, returnClass);
           toReturn.elementType = parameterizations[0];
+          maybeProcessEnumType(toReturn.elementType);
         } else if (mapInterface.isAssignableFrom(returnClass)) {
           JClassType[] parameterizations = ModelUtils.findParameterizationOf(
               mapInterface, returnClass);
           toReturn.keyType = parameterizations[0];
           toReturn.valueType = parameterizations[1];
+          maybeProcessEnumType(toReturn.keyType);
+          maybeProcessEnumType(toReturn.valueType);
         }
-      }
-
-      JEnumType enumType = method.getReturnType().isEnum();
-      if (enumType != null) {
-        Map<JEnumConstant, String> map = new LinkedHashMap<JEnumConstant, String>();
-        for (JEnumConstant e : enumType.getEnumConstants()) {
-          String name;
-          PropertyName annotation = e.getAnnotation(PropertyName.class);
-          if (annotation == null) {
-            name = e.getName();
-          } else {
-            name = annotation.value();
-          }
-          map.put(e, name);
-        }
-        toReturn.enumMap = map;
+      } else {
+        maybeProcessEnumType(returnType);
       }
     }
 
@@ -202,6 +192,39 @@
     public void setStaticImp(JMethod staticImpl) {
       toReturn.staticImpl = staticImpl;
     }
+
+    /**
+     * Call {@link #processEnumType(JEnumType)} if {@code type} is a
+     * {@link JEnumType}.
+     */
+    private void maybeProcessEnumType(JType type) {
+      assert type != null : "type == null";
+      JEnumType enumType = type.isEnum();
+      if (enumType != null) {
+        processEnumType(enumType);
+      }
+    }
+
+    /**
+     * Adds a JEnumType to the AutoBeanMethod's enumMap so that the
+     * AutoBeanFactoryGenerator can embed extra metadata about the enum values.
+     */
+    private void processEnumType(JEnumType enumType) {
+      Map<JEnumConstant, String> map = toReturn.enumMap;
+      if (map == null) {
+        map = toReturn.enumMap = new LinkedHashMap<JEnumConstant, String>();
+      }
+      for (JEnumConstant e : enumType.getEnumConstants()) {
+        String name;
+        PropertyName annotation = e.getAnnotation(PropertyName.class);
+        if (annotation == null) {
+          name = e.getName();
+        } else {
+          name = annotation.value();
+        }
+        map.put(e, name);
+      }
+    }
   }
 
   private Action action;
@@ -255,12 +278,12 @@
     return valueType;
   }
 
-  public boolean isCollection() {
-    return elementType != null;
+  public boolean hasEnumMap() {
+    return enumMap != null;
   }
 
-  public boolean isEnum() {
-    return enumMap != null;
+  public boolean isCollection() {
+    return elementType != null;
   }
 
   public boolean isMap() {
diff --git a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
index 027d454..348dfe3 100644
--- a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
+++ b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
@@ -49,6 +49,22 @@
     AutoBean<Simple> simple();
   }
 
+  /*
+   * These enums are used to verify that a List<Enum> or Map<Enum, Enum> pulls
+   * in the necessary metadata.
+   */
+  enum EnumReachableThroughList {
+    FOO_LIST
+  }
+
+  enum EnumReachableThroughMapKey {
+    FOO_KEY
+  }
+
+  enum EnumReachableThroughMapValue {
+    FOO_VALUE
+  }
+
   interface HasAutoBean {
     Splittable getSimple();
 
@@ -79,6 +95,10 @@
 
     Map<MyEnum, Integer> getMap();
 
+    List<EnumReachableThroughList> getParameterizedList();
+
+    Map<EnumReachableThroughMapKey, EnumReachableThroughMapValue> getParameterizedMap();
+
     void setEnum(MyEnum value);
 
     void setEnums(List<MyEnum> value);
@@ -157,13 +177,6 @@
     assertTrue(decodedBean.as().getList().isEmpty());
   }
 
-  private <T> AutoBean<T> checkEncode(AutoBean<T> bean) {
-    Splittable split = AutoBeanCodex.encode(bean);
-    AutoBean<T> decoded = AutoBeanCodex.decode(f, bean.getType(), split);
-    assertTrue(AutoBeanUtils.deepEquals(bean, decoded));
-    return decoded;
-  }
-
   public void testEnum() {
     EnumMap map = (EnumMap) f;
     assertEquals("BAR", map.getToken(MyEnum.BAR));
@@ -192,6 +205,24 @@
     assertEquals(mapValue, decoded.as().getMap());
   }
 
+  /**
+   * Ensures that enum types that are reachable only through a method
+   * parameterization are included in the enum map.
+   */
+  public void testEnumReachableOnlyThroughParameterization() {
+    EnumMap map = (EnumMap) f;
+    assertEquals("FOO_LIST", map.getToken(EnumReachableThroughList.FOO_LIST));
+    assertEquals("FOO_KEY", map.getToken(EnumReachableThroughMapKey.FOO_KEY));
+    assertEquals("FOO_VALUE",
+        map.getToken(EnumReachableThroughMapValue.FOO_VALUE));
+    assertEquals(EnumReachableThroughList.FOO_LIST,
+        map.getEnum(EnumReachableThroughList.class, "FOO_LIST"));
+    assertEquals(EnumReachableThroughMapKey.FOO_KEY,
+        map.getEnum(EnumReachableThroughMapKey.class, "FOO_KEY"));
+    assertEquals(EnumReachableThroughMapValue.FOO_VALUE,
+        map.getEnum(EnumReachableThroughMapValue.class, "FOO_VALUE"));
+  }
+
   public void testMap() {
     AutoBean<HasMap> bean = f.hasMap();
     Map<String, Simple> map = new HashMap<String, Simple>();
@@ -291,4 +322,11 @@
   protected void gwtSetUp() throws Exception {
     f = GWT.create(Factory.class);
   }
+
+  private <T> AutoBean<T> checkEncode(AutoBean<T> bean) {
+    Splittable split = AutoBeanCodex.encode(bean);
+    AutoBean<T> decoded = AutoBeanCodex.decode(f, bean.getType(), split);
+    assertTrue(AutoBeanUtils.deepEquals(bean, decoded));
+    return decoded;
+  }
 }