Implemented Class.getEnumConstants() in the compiler and JRE.

Note: this patch does not implement Enum.valueOf(Class<T> enumType, String name) or Enum.getDeclaringClass().

Review by: mmastrac

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1679 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java
index eb97f67..b76cede 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassLiteral.java
@@ -15,6 +15,9 @@
  */
 package com.google.gwt.dev.jjs.ast;
 
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
+
 /**
  * Java class literal expression.
  */
@@ -77,6 +80,26 @@
       }
 
       call.getArgs().add(superclassLiteral);
+
+      if (classType instanceof JEnumType) {
+        JEnumType enumType = (JEnumType) classType;
+        JMethod valuesMethod = null;
+        for (JMethod methodIt : enumType.methods) {
+          if ("values".equals(methodIt.getName())) {
+            if (methodIt.params.size() != 0) {
+              continue;
+            }
+            valuesMethod = methodIt;
+          }
+        }
+        if (valuesMethod == null) {
+          throw new InternalCompilerException(
+              "Could not find enum values() method");
+        }
+        JsniMethodRef jsniMethodRef = new JsniMethodRef(program.program, null,
+            valuesMethod);
+        call.getArgs().add(jsniMethodRef);
+      }
     } else {
       assert (type instanceof JArrayType || type instanceof JInterfaceType || type instanceof JPrimitiveType);
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
index b023c9a..0272c16 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
@@ -41,6 +41,13 @@
     return isAbstract;
   }
 
+  public JEnumType isEnumOrSubclass() {
+    if (extnds != null) {
+      return extnds.isEnumOrSubclass();
+    }
+    return null;
+  }
+
   public boolean isFinal() {
     return isFinal;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JEnumType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JEnumType.java
index ee929e4..bcd5be9 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JEnumType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JEnumType.java
@@ -24,6 +24,9 @@
  * Java enum type reference expression.
  */
 public class JEnumType extends JClassType {
+  /*
+   * TODO: implement traverse?
+   */
 
   public final List<JEnumField> enumList = new ArrayList<JEnumField>();
 
@@ -32,10 +35,13 @@
     this.extnds = program.getTypeJavaLangEnum();
   }
 
-  // TODO: implement traverse?
-
   @Override
   public String getClassLiteralFactoryMethod() {
     return "Class.createForEnum";
   }
+
+  @Override
+  public JEnumType isEnumOrSubclass() {
+    return this;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniMethodRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniMethodRef.java
index 6891900..a0fd09b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniMethodRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniMethodRef.java
@@ -17,9 +17,11 @@
 
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 
 /**
@@ -33,6 +35,13 @@
         method);
   }
 
+  @Override
+  public JType getType() {
+    // If JavaScriptObject type is not available, just return the Object type
+    JClassType jsoType = program.getJavaScriptObject();
+    return (jsoType != null) ? jsoType : program.getTypeJavaLangObject();
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 744338e..699dac2 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -186,7 +186,7 @@
         mapThrownExceptions(newMethod, b);
 
         // Enums have hidden arguments for name and value
-        if (enclosingType instanceof JEnumType) {
+        if (enclosingType.isEnumOrSubclass() != null) {
           program.createParameter(info, "enum$name".toCharArray(),
               program.getTypeJavaLangString(), true, false, newMethod);
           program.createParameter(info, "enum$ordinal".toCharArray(),
@@ -481,7 +481,7 @@
           type.implments.add(superInterface);
         }
 
-        if (binding.isEnum()) {
+        if (type instanceof JEnumType) {
           processEnumType(binding, (JEnumType) type);
         }
 
@@ -731,7 +731,12 @@
         } else if (binding.isInterface()) {
           newType = program.createInterface(info, name);
         } else if (binding.isEnum()) {
-          newType = program.createEnum(info, name);
+          if (binding.isAnonymousType()) {
+            // Don't model an enum subclass as a JEnumType.
+            newType = program.createClass(info, name, false, true);
+          } else {
+            newType = program.createEnum(info, name);
+          }
         } else if (binding.isAnnotationType()) {
           // TODO
           return false;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index d913c6e..1343814 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -534,7 +534,7 @@
               new JReturnStatement(program, null, classLit));
         }
 
-        if (x.binding.isEnum()) {
+        if (currentClass instanceof JEnumType) {
           processEnumType((JEnumType) currentClass);
         }
 
@@ -777,7 +777,7 @@
         }
 
         // Enums: wire up synthetic name/ordinal params to the super method.
-        if (enclosingType instanceof JEnumType) {
+        if (enclosingType.isEnumOrSubclass() != null) {
           assert (superOrThisCall != null);
           JVariableRef enumNameRef = createVariableRef(
               superOrThisCall.getSourceInfo(), ctor.params.get(0));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index fc42d01..1e43e8c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -960,51 +960,10 @@
     }
 
     @Override
-    public void endVisit(JsniMethodBody x, Context ctx) {
-      JsFunction jsFunc = x.getFunc();
-
-      // replace all JSNI idents with a real JsName now that we know it
-      new JsModVisitor() {
-
-        @Override
-        public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
-          String ident = x.getIdent();
-          if (ident.charAt(0) == '@') {
-            HasEnclosingType node = program.jsniMap.get(ident);
-            assert (node != null);
-            if (node instanceof JField) {
-              JField field = (JField) node;
-              JsName jsName = names.get(field);
-              assert (jsName != null);
-              x.resolve(jsName);
-
-              // See if we need to add a clinit call to a static field ref
-              JsInvocation clinitCall = maybeCreateClinitCall(field);
-              if (clinitCall != null) {
-                JsExpression commaExpr = createCommaExpression(clinitCall, x);
-                ctx.replaceMe(commaExpr);
-              }
-            } else {
-              JMethod method = (JMethod) node;
-              if (x.getQualifier() == null) {
-                JsName jsName = names.get(method);
-                assert (jsName != null);
-                x.resolve(jsName);
-              } else {
-                JsName jsName = polymorphicNames.get(method);
-                if (jsName == null) {
-                  // this can occur when JSNI references an instance method on a
-                  // type that was never actually instantiated.
-                  jsName = nullMethodName;
-                }
-                x.resolve(jsName);
-              }
-            }
-          }
-        }
-      }.accept(jsFunc);
-
-      push(jsFunc);
+    public void endVisit(JsniMethodRef x, Context ctx) {
+      JMethod method = x.getTarget();
+      JsNameRef nameRef = names.get(method).makeRef();
+      push(nameRef);
     }
 
     @Override
@@ -1101,6 +1060,57 @@
     }
 
     @Override
+    public boolean visit(JsniMethodBody x, Context ctx) {
+      JsFunction jsFunc = x.getFunc();
+
+      // replace all JSNI idents with a real JsName now that we know it
+      new JsModVisitor() {
+
+        @Override
+        public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+          String ident = x.getIdent();
+          if (ident.charAt(0) == '@') {
+            HasEnclosingType node = program.jsniMap.get(ident);
+            assert (node != null);
+            if (node instanceof JField) {
+              JField field = (JField) node;
+              JsName jsName = names.get(field);
+              assert (jsName != null);
+              x.resolve(jsName);
+
+              // See if we need to add a clinit call to a static field ref
+              JsInvocation clinitCall = maybeCreateClinitCall(field);
+              if (clinitCall != null) {
+                JsExpression commaExpr = createCommaExpression(clinitCall, x);
+                ctx.replaceMe(commaExpr);
+              }
+            } else {
+              JMethod method = (JMethod) node;
+              if (x.getQualifier() == null) {
+                JsName jsName = names.get(method);
+                assert (jsName != null);
+                x.resolve(jsName);
+              } else {
+                JsName jsName = polymorphicNames.get(method);
+                if (jsName == null) {
+                  // this can occur when JSNI references an instance method on a
+                  // type that was never actually instantiated.
+                  jsName = nullMethodName;
+                }
+                x.resolve(jsName);
+              }
+            }
+          }
+        }
+      }.accept(jsFunc);
+
+      push(jsFunc);
+
+      // Do NOT visit JsniMethodRefs/JsniFieldRefs.
+      return false;
+    }
+
+    @Override
     public boolean visit(JSwitchStatement x, Context ctx) {
       /*
        * What a pain.. JSwitchStatement and JsSwitch are modeled completely
diff --git a/user/super/com/google/gwt/emul/java/lang/Class.java b/user/super/com/google/gwt/emul/java/lang/Class.java
index faad204..97b2468 100644
--- a/user/super/com/google/gwt/emul/java/lang/Class.java
+++ b/user/super/com/google/gwt/emul/java/lang/Class.java
@@ -15,6 +15,8 @@
  */
 package java.lang;
 
+import com.google.gwt.core.client.JavaScriptObject;
+
 /**
  * Generally unsupported. This class is provided so that the GWT compiler can
  * choke down class literal references.
@@ -62,12 +64,13 @@
    * @skip
    */
   static <T> Class<T> createForEnum(String packageName, String className,
-      Class<? super T> superclass) {
+      Class<? super T> superclass, JavaScriptObject enumConstantsFunc) {
     // Initialize here to avoid method inliner
     Class<T> clazz = new Class<T>();
     clazz.typeName = packageName + className;
     clazz.modifiers = ENUM;
     clazz.superclass = superclass;
+    clazz.enumConstantsFunc = enumConstantsFunc;
     return clazz;
   }
 
@@ -97,6 +100,9 @@
     return clazz;
   }
 
+  @SuppressWarnings("unused")
+  private JavaScriptObject enumConstantsFunc;
+
   private int modifiers;
 
   private String typeName;
@@ -111,10 +117,10 @@
   private Class() {
   }
 
-  public T[] getEnumConstants() {
-    // TODO
-    return null;
-  }
+  public native T[] getEnumConstants() /*-{
+    return this.@java.lang.Class::enumConstantsFunc
+        && (this.@java.lang.Class::enumConstantsFunc)();
+  }-*/;
 
   public String getName() {
     return typeName;
diff --git a/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java b/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
index 7cd0685..1391ea2 100644
--- a/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/ClassObjectTest.java
@@ -23,7 +23,12 @@
 public class ClassObjectTest extends GWTTestCase {
 
   private static enum Bar {
-    BAR;
+    BAR, BAZ {
+      @Override
+      public String toString() {
+        return "BAZ!";
+      }
+    };
   }
 
   private static class Foo implements IFoo {
@@ -32,6 +37,13 @@
   private static interface IFoo {
   }
 
+  private static void assertArrayEquals(Object[] expected, Object[] actual) {
+    assertEquals(expected.length, actual.length);
+    for (int i = 0; i < expected.length; ++i) {
+      assertEquals(expected[i], actual[i]);
+    }
+  }
+
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
   }
@@ -48,6 +60,7 @@
     assertFalse(o.getClass().isEnum());
     assertFalse(o.getClass().isInterface());
     assertFalse(o.getClass().isPrimitive());
+    assertNull(o.getClass().getEnumConstants());
 
     Foo[][] f = new Foo[3][3];
     assertEquals(Foo[][].class, f.getClass());
@@ -66,6 +79,7 @@
     assertFalse(Foo.class.isEnum());
     assertFalse(Foo.class.isInterface());
     assertFalse(Foo.class.isPrimitive());
+    assertNull(o.getClass().getEnumConstants());
   }
 
   public void testEnum() {
@@ -80,6 +94,24 @@
     assertTrue(o.getClass().isEnum());
     assertFalse(o.getClass().isInterface());
     assertFalse(o.getClass().isPrimitive());
+    assertArrayEquals(Bar.values(), o.getClass().getEnumConstants());
+  }
+
+  public void testEnumSubclass() {
+    Object o = Bar.BAZ;
+    assertNotSame(Bar.class, o.getClass());
+    assertEquals(Bar.class, o.getClass().getSuperclass());
+    /*
+     * TODO: implement
+     */
+    // assertEquals(Bar.class, o.getClass().getDeclaringClass());
+    assertTrue(o.getClass().getName().endsWith("$1"));
+    assertTrue(o.getClass().toString().endsWith("$1"));
+    assertFalse(o.getClass().isArray());
+    assertFalse(o.getClass().isEnum());
+    assertFalse(o.getClass().isInterface());
+    assertFalse(o.getClass().isPrimitive());
+    assertNull(o.getClass().getEnumConstants());
   }
 
   public void testInterface() {
@@ -92,6 +124,7 @@
     assertFalse(IFoo.class.isEnum());
     assertTrue(IFoo.class.isInterface());
     assertFalse(IFoo.class.isPrimitive());
+    assertNull(IFoo.class.getEnumConstants());
   }
 
   public void testPrimitive() {
@@ -102,6 +135,7 @@
     assertFalse(int.class.isEnum());
     assertFalse(int.class.isInterface());
     assertTrue(int.class.isPrimitive());
+    assertNull(int.class.getEnumConstants());
   }
 
 }