This patch expands Gwt's ability to successfully create classMappings, necessary when running Emma. The following types of classes 
can now be handled correctly:
(a) A top level class that is not the main type in a java file (e.g., Test$1Foo produced from Bar.java)
(b) A class that is nested multiple-levels deep (e.g., Test$Foo$1Bar)
(c) An anonmyous class that extends a named local class (e.g., Test$1Foo)

Also adds a test target to enable running all hosted mode tests in Emma mode. All tests pass except CoverageTest.java that fails
because of a bug in javac in both OpenJDK and Sun's Java6.

Patch by: amitmanjhi
Review by: jat (desk review)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4627 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index e2653a1..e3c2f19 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -15,11 +15,13 @@
  */
 package com.google.gwt.dev.javac;
 
+import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.asm.ClassReader;
 import com.google.gwt.dev.asm.Opcodes;
 import com.google.gwt.dev.asm.commons.EmptyVisitor;
 import com.google.gwt.dev.jdt.TypeRefVisitor;
 import com.google.gwt.dev.shell.CompilingClassLoader;
+import com.google.gwt.dev.util.Util;
 
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
@@ -30,6 +32,9 @@
 import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -46,25 +51,129 @@
  */
 public abstract class CompilationUnit {
 
-  static class AnonymousClassVisitor extends EmptyVisitor {
-    /*
-     * array of classNames of inner clases that aren't synthetic classes.
-     */
-    List<String> classNames = new ArrayList<String>();
+  /**
+   * Encapsulates the functionality to find all nested classes of this class
+   * that have compiler-generated names. All class bytes are loaded from the
+   * disk and then analyzed using ASM.
+   */
+  static class GeneratedClassnameFinder {
+    private static class AnonymousClassVisitor extends EmptyVisitor {
+      /*
+       * array of classNames of inner clases that aren't synthetic classes.
+       */
+      List<String> classNames = new ArrayList<String>();
 
-    public List<String> getInnerClassNames() {
-      return classNames;
-    }
+      public List<String> getInnerClassNames() {
+        return classNames;
+      }
 
-    @Override
-    public void visitInnerClass(String name, String outerName,
-        String innerName, int access) {
-      if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
-        classNames.add(name);
+      @Override
+      public void visitInnerClass(String name, String outerName,
+          String innerName, int access) {
+        if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
+          classNames.add(name);
+        }
       }
     }
-  }
 
+    private final List<String> classesToScan;
+    private final TreeLogger logger;
+    private final String mainClass;
+    private String mainUrlBase = null;
+
+    GeneratedClassnameFinder(TreeLogger logger, String mainClass) {
+      assert mainClass != null;
+      this.mainClass = mainClass;
+      classesToScan = new ArrayList<String>();
+      classesToScan.add(mainClass);
+      this.logger = logger;
+    }
+
+    List<String> getClassNames() {
+      // using a list because presumably there will not be many generated
+      // classes
+      List<String> allGeneratedClasses = new ArrayList<String>();
+      for (int i = 0; i < classesToScan.size(); i++) {
+        String lookupName = classesToScan.get(i);
+        byte classBytes[] = getClassBytes(lookupName);
+        if (classBytes == null) {
+          /*
+           * Weird case: javac might generate a name and reference the class in
+           * the bytecode but decide later that the class is unnecessary. In the
+           * bytecode, a null is passed for the class.
+           */
+          continue;
+        }
+
+        /*
+         * Add the class to the list only if it can be loaded to get around the
+         * javac weirdness issue where javac refers a class but does not
+         * generate it.
+         */
+        if (CompilingClassLoader.isClassnameGenerated(lookupName)
+            && !allGeneratedClasses.contains(lookupName)) {
+          allGeneratedClasses.add(lookupName);
+        }
+        AnonymousClassVisitor cv = new AnonymousClassVisitor();
+        new ClassReader(classBytes).accept(cv, 0);
+        List<String> innerClasses = cv.getInnerClassNames();
+        for (String innerClass : innerClasses) {
+          // The innerClass has to be an inner class of the lookupName
+          if (!innerClass.startsWith(mainClass + "$")) {
+            continue;
+          }
+          /*
+           * TODO (amitmanjhi): consider making this a Set if necessary for
+           * performance
+           */
+          // add the class to classes
+          if (!classesToScan.contains(innerClass)) {
+            classesToScan.add(innerClass);
+          }
+        }
+      }
+      Collections.sort(allGeneratedClasses, new GeneratedClassnameComparator());
+      return allGeneratedClasses;
+    }
+
+    /*
+     * Load classBytes from disk. Check if the classBytes are loaded from the
+     * same location as the location of the mainClass.
+     */
+    private byte[] getClassBytes(String slashedName) {
+      URL url = Thread.currentThread().getContextClassLoader().getResource(
+          slashedName + ".class");
+      if (url == null) {
+        logger.log(TreeLogger.DEBUG, "Unable to find " + slashedName
+            + " on the classPath");
+        return null;
+      }
+      String urlStr = url.toExternalForm();
+      if (slashedName.equals(mainClass)) {
+        // initialize the mainUrlBase for later use.
+        mainUrlBase = urlStr.substring(0, urlStr.lastIndexOf('/'));
+      } else {
+        assert mainUrlBase != null;
+        if (!mainUrlBase.equals(urlStr.substring(0, urlStr.lastIndexOf('/')))) {
+          logger.log(TreeLogger.DEBUG, "Found " + slashedName + " at " + urlStr
+              + " The base location is different from  that of " + mainUrlBase
+              + " Not loading");
+          return null;
+        }
+      }
+
+      // url != null, we found it on the class path.
+      try {
+        URLConnection conn = url.openConnection();
+        return Util.readURLConnectionAsBytes(conn);
+      } catch (IOException ignored) {
+        logger.log(TreeLogger.DEBUG, "Unable to load " + urlStr
+            + ", in trying to load " + slashedName);
+        // Fall through.
+      }
+      return null;
+    }
+  }
   /**
    * Tracks the state of a compilation unit through the compile and recompile
    * process.
@@ -167,26 +276,31 @@
   private State state = State.FRESH;
 
   /*
-   * Check if the unit has one or more anonymous classes. 'javac' below refers
-   * to the compiler that was used to compile the java files on disk. Returns
-   * true if our heuristic for constructing the anonymous class mappings worked.
+   * Check if the unit has one or more classes with generated names. 'javac'
+   * below refers to the compiler that was used to compile the java files on
+   * disk. Returns true if our heuristic for constructing the anonymous class
+   * mappings worked.
    */
-  public boolean constructAnonymousClassMappings(byte classBytes[]) {
+  public boolean constructAnonymousClassMappings(TreeLogger logger) {
     // map from the name in javac to the name in jdt
     anonymousClassMap = new HashMap<String, String>();
-    List<String> javacClasses = getJavacClassNames(classBytes);
-    List<String> jdtClasses = getJdtClassNames();
-    if (javacClasses.size() == jdtClasses.size()) {
+    for (String topLevelClass : getTopLevelClasses()) {
+      // Generate a mapping for each top-level class separately
+      List<String> javacClasses = new GeneratedClassnameFinder(logger,
+          topLevelClass).getClassNames();
+      List<String> jdtClasses = getJdtClassNames(topLevelClass);
+      if (javacClasses.size() != jdtClasses.size()) {
+        anonymousClassMap = Collections.emptyMap();
+        return false;
+      }
       int size = javacClasses.size();
       for (int i = 0; i < size; i++) {
         if (!javacClasses.get(i).equals(jdtClasses.get(i))) {
           anonymousClassMap.put(javacClasses.get(i), jdtClasses.get(i));
         }
       }
-      return true;
     }
-    anonymousClassMap = Collections.emptyMap();
-    return false;
+    return true;
   }
 
   public boolean createdClassMapping() {
@@ -383,25 +497,11 @@
     }
   }
 
-  private List<String> getJavacClassNames(byte classBytes[]) {
-    AnonymousClassVisitor cv = new AnonymousClassVisitor();
-    new ClassReader(classBytes).accept(cv, 0);
-    List<String> classNames = cv.getInnerClassNames();
-    List<String> namesToRemove = new ArrayList<String>();
-    for (String className : classNames) {
-      if (!CompilingClassLoader.isClassnameGenerated(className)) {
-        namesToRemove.add(className);
-      }
-    }
-    classNames.removeAll(namesToRemove);
-    Collections.sort(classNames, new GeneratedClassnameComparator());
-    return classNames;
-  }
-
-  private List<String> getJdtClassNames() {
+  private List<String> getJdtClassNames(String topLevelClass) {
     List<String> classNames = new ArrayList<String>();
     for (CompiledClass cc : getCompiledClasses()) {
-      if (isAnonymousClass(cc)) {
+      if (isAnonymousClass(cc)
+          && cc.getBinaryName().startsWith(topLevelClass + "$")) {
         classNames.add(cc.getBinaryName());
       }
     }
@@ -409,6 +509,16 @@
     return classNames;
   }
 
+  private List<String> getTopLevelClasses() {
+    List<String> topLevelClasses = new ArrayList<String>();
+    for (CompiledClass cc : getCompiledClasses()) {
+      if (cc.getEnclosingClass() == null) {
+        topLevelClasses.add(cc.binaryName);
+      }
+    }
+    return topLevelClasses;
+  }
+
   /**
    * Removes all accumulated state associated with compilation.
    */
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 143a3c3..d773404 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -361,7 +361,7 @@
    */
   private static byte[] javaScriptHostBytes;
 
-  private static final Pattern GENERATED_CLASSNAME_PATTERN = Pattern.compile(".+\\$\\d+(\\$.*)?");
+  private static final Pattern GENERATED_CLASSNAME_PATTERN = Pattern.compile(".+\\$\\d.*");
 
   static {
     for (Class<?> c : BRIDGE_CLASSES) {
@@ -386,9 +386,10 @@
 
   /**
    * Checks if the class names is generated. Accepts any classes whose names
-   * match .+$\d+($.*)? (handling named classes within anonymous classes).
-   * Checks if the class or any of its enclosing classes are anonymous or
-   * synthetic.
+   * match .+$\d.* (handling named classes within anonymous classes and
+   * multiple named classes of the same name in a class, but in different
+   * methods). Checks if the class or any of its enclosing classes are anonymous
+   * or synthetic.
    * <p>
    * If new compilers have different conventions for anonymous and synthetic
    * classes, this code needs to be updated.
@@ -670,7 +671,7 @@
         lookupClassName);
 
     CompilationUnit unit = (compiledClass == null)
-        ? getUnitForClassName(className) : compiledClass.getUnit();
+        ? getUnitForClassName(lookupClassName) : compiledClass.getUnit();
     if (emmaAvailable) {
       /*
        * build the map for anonymous classes. Do so only if unit has anonymous
@@ -682,20 +683,12 @@
       if (unit != null && !unit.isSuperSource() && unit.hasAnonymousClasses()
           && jsniMethods != null && jsniMethods.size() > 0
           && !unit.createdClassMapping()) {
-        String mainLookupClassName = unit.getTypeName().replace('.', '/');
-        byte mainClassBytes[] = emmaStrategy.getEmmaClassBytes(null,
-            mainLookupClassName, 0);
-        if (mainClassBytes != null) {
-          if (!unit.constructAnonymousClassMappings(mainClassBytes)) {
-            logger.log(TreeLogger.ERROR,
-                "Our heuristic for mapping anonymous classes between compilers "
-                    + "failed. Unsafe to continue because the wrong jsni code "
-                    + "could end up running. className = " + className);
-            return null;
-          }
-        } else {
-          logger.log(TreeLogger.ERROR, "main class bytes is null for unit = "
-              + unit + ", mainLookupClassName = " + mainLookupClassName);
+        if (!unit.constructAnonymousClassMappings(logger)) {
+          logger.log(TreeLogger.ERROR,
+              "Our heuristic for mapping anonymous classes between compilers "
+                  + "failed. Unsafe to continue because the wrong jsni code "
+                  + "could end up running. className = " + className);
+          return null;
         }
       }
     }
@@ -717,7 +710,8 @@
        * find it on disk. Typically this is a synthetic class added by the
        * compiler.
        */
-      if (typeHasCompilationUnit(className) && isClassnameGenerated(className)) {
+      if (typeHasCompilationUnit(lookupClassName)
+          && isClassnameGenerated(className)) {
         /*
          * modification time = 0 ensures that whatever is on the disk is always
          * loaded.
@@ -758,17 +752,19 @@
   /**
    * Returns the compilationUnit corresponding to the className. For nested
    * classes, the unit corresponding to the top level type is returned.
+   * 
+   * Since a file might have several top-level types, search using classFileMap.
    */
   private CompilationUnit getUnitForClassName(String className) {
     String mainTypeName = className;
     int index = mainTypeName.length();
-    CompilationUnit unit = null;
-    while (unit == null && index != -1) {
+    CompiledClass cc = null;
+    while (cc == null && index != -1) {
       mainTypeName = mainTypeName.substring(0, index);
-      unit = compilationState.getCompilationUnitMap().get(mainTypeName);
+      cc = compilationState.getClassFileMap().get(mainTypeName);
       index = mainTypeName.lastIndexOf('$');
     }
-    return unit;
+    return cc == null ? null : cc.getUnit();
   }
 
   private void injectJsniMethods(CompilationUnit unit) {
diff --git a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
index 630d438..3af1a6a 100644
--- a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
@@ -1,3 +1,18 @@
+/*
+ * 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.dev.javac;
 
 import junit.framework.TestCase;
@@ -38,8 +53,21 @@
   }
 
   public void testMixedNames() {
-    String original[] = {"Foo", "Foo$1", "Foo$1xyz", "Foo$2", "Foo$xyz"};
-    String expected[] = {"Foo", "Foo$1", "Foo$2", "Foo$1xyz", "Foo$xyz"};
+    String original[] = {
+        "Foo", "Foo$1", "Foo$1Bar", "Foo$2Bar", "Foo$2", "Foo$xyz"};
+    String expected[] = {
+        "Foo", "Foo$1", "Foo$2", "Foo$1Bar", "Foo$2Bar", "Foo$xyz"};
+    Arrays.sort(original, new GeneratedClassnameComparator());
+    for (int i = 0; i < original.length; i++) {
+      assertEquals("index = " + i, expected[i], original[i]);
+    }
+  }
+
+  public void testMultipleToplevelClasses() {
+    String original[] = {
+        "Foo$1", "Foo$2", "Bar$1", "Bar$3", "Foo$2$1", "Bar$2$1"};
+    String expected[] = {
+        "Bar$1", "Bar$3", "Foo$1", "Foo$2", "Bar$2$1", "Foo$2$1"};
     Arrays.sort(original, new GeneratedClassnameComparator());
     for (int i = 0; i < original.length; i++) {
       assertEquals("index = " + i, expected[i], original[i]);
diff --git a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java
new file mode 100644
index 0000000..0f428e2
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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.dev.javac;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.javac.CompilationUnit.GeneratedClassnameFinder;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Test-cases to check that we indeed obtain the correct list of nested types
+ * with generated classNames by examining bytecodes using ASM.
+ * 
+ */
+public class GeneratedClassnameFinderTest extends TestCase {
+  enum EnumClass {
+    A, B, C,
+  }
+
+  static class MainClass {
+    static class NestedClass {
+      void foo() {
+        TestInterface c = new TestInterface() {
+          public void foo() {
+          }
+        };
+        EnumClass et = EnumClass.A;
+        switch (et) {
+          case A:
+            break;
+        }
+        TestInterface d = new TestInterface() {
+          public void foo() {
+          }
+        };
+      }
+    }
+
+    void foo() {
+      TestInterface a = new TestInterface() {
+        public void foo() {
+        }
+      };
+      EnumClass et = EnumClass.A;
+      switch (et) {
+        case A:
+          break;
+      }
+      TestInterface b = new TestInterface() {
+        public void foo() {
+        }
+      };
+    }
+  }
+  interface TestInterface {
+    void foo();
+  }
+
+  static final TreeLogger logger = new PrintWriterTreeLogger();
+
+  public void test() {
+    String mainClassName = this.getClass().getName().replace('.', '/');
+    assertEquals(
+        4,
+        new GeneratedClassnameFinder(logger, mainClassName).getClassNames().size());
+    assertEquals(0, new GeneratedClassnameFinder(logger, mainClassName
+        + "$EnumClass").getClassNames().size());
+    assertEquals(0, new GeneratedClassnameFinder(logger, mainClassName
+        + "$TestInterface").getClassNames().size());
+    assertEquals(4, new GeneratedClassnameFinder(logger, mainClassName
+        + "$MainClass").getClassNames().size());
+    assertEquals(2, new GeneratedClassnameFinder(logger, mainClassName
+        + "$MainClass$NestedClass").getClassNames().size());
+  }
+
+  public void testAnonymous() {
+    assertEquals(1, new AnonymousTester().getGeneratedClasses().size());
+  }
+
+  public void testEnum() {
+    assertEquals(0, new EnumTester().getGeneratedClasses().size());
+  }
+
+  public void testJavacWeirdness() {
+    List<String> classNames = new JavacWeirdnessTester().getGeneratedClasses();
+    assertEquals(3, classNames.size());
+    assertTrue(classNames.get(0) + " should not contain Foo",
+        classNames.get(0).indexOf("Foo") == -1);
+    assertTrue(classNames.get(1) + " should contain Foo",
+        classNames.get(1).indexOf("Foo") != -1);
+    assertTrue(classNames.get(2) + " should contain Foo",
+        classNames.get(2).indexOf("Foo") != -1);
+  }
+
+  public void testNamedLocal() {
+    assertEquals(2, new NamedLocalTester().getGeneratedClasses().size());
+  }
+
+  public void testNested() {
+    assertEquals(2, new NestedTester().getGeneratedClasses().size());
+  }
+
+  public void testStatic() {
+    assertEquals(0, new StaticTester().getGeneratedClasses().size());
+  }
+
+  public void testTopLevel() {
+    assertEquals(1, new TopLevelTester().getGeneratedClasses().size());
+  }
+
+}
+
+/**
+ * For testing a class containing an anonymous inner class.
+ */
+class AnonymousTester {
+  interface TestInterface {
+    void foo();
+  }
+
+  void foo() {
+    TestInterface a = new TestInterface() {
+      public void foo() {
+      }
+    };
+    a.foo();
+  }
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+}
+
+/**
+ * For testing a class with an Enum (for which javac generates a synthetic
+ * class).
+ */
+class EnumTester {
+  enum EnumClass {
+    A, B, C,
+  }
+
+  void foo() {
+    EnumClass et = EnumClass.A;
+    switch (et) {
+      case A:
+        break;
+    }
+  }
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+}
+
+/**
+ * Javac generates weird code for the following class. It passes a synthetic
+ * class ...Tester$1 as a first parameter to constructors of Fuji and Granny.
+ * Normally, it generates the synthetic class, but in this case, it decides not
+ * to generate the class. However, the bytecode still has reference to the
+ * synthetic class -- it just passes null for the synthetic class.
+ * 
+ * This code also tests for an anonymous class extending a named local class.
+ */
+class JavacWeirdnessTester {
+  private abstract static class Apple implements Fruit {
+  }
+
+  private static interface Fruit {
+  }
+
+  private static class Fuji extends Apple {
+  }
+
+  private static class Granny extends Apple {
+  }
+
+  private static volatile boolean TRUE = true;
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+
+  private void assertEquals(Object a, Object b) {
+  }
+
+  private void testArrayStore() {
+    Apple[] apple = TRUE ? new Granny[3] : new Apple[3];
+    Apple g = TRUE ? (Apple) new Granny() : (Apple) new Fuji();
+    Apple a = apple[0] = g;
+    assertEquals(g, a);
+  }
+
+  private void testDeadTypes() {
+    if (false) {
+      new Object() {
+      }.toString();
+
+      class Foo {
+        void a() {
+        }
+      }
+      new Foo().a();
+    }
+  }
+
+  private void testLocalClasses() {
+    class Foo {
+      public Foo(int j) {
+        assertEquals(1, j);
+      };
+    }
+    final int i;
+    new Foo(i = 1) {
+      {
+        assertEquals(1, i);
+      }
+    };
+    assertEquals(1, i);
+  }
+
+  private void testReturnStatementInCtor() {
+    class Foo {
+      int i;
+
+      Foo(int i) {
+        this.i = i;
+        if (i == 0) {
+          return;
+        } else if (i == 1) {
+          return;
+        }
+        return;
+      }
+    }
+    assertEquals(new Foo(0).i, 0);
+  }
+}
+
+/**
+ * For testing a class with a generated name like $1Foo.
+ */
+class NamedLocalTester {
+  void foo1() {
+    if (false) {
+      class Foo {
+        void foo() {
+        }
+      }
+      new Foo().foo();
+    }
+  }
+
+  void foo2() {
+    class Foo {
+      void foo() {
+      }
+    }
+    new Foo().foo();
+  }
+
+  void foo3() {
+    class Foo {
+      void foo() {
+      }
+    }
+    new Foo().foo();
+  }
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+}
+
+/**
+ * For testing that nested classes are examined recursively for classes with
+ * generated names.
+ */
+class NestedTester {
+  class MainClass {
+    class NestedClass {
+      void foo() {
+        class Foo {
+          void bar() {
+          }
+        }
+        new Foo().bar();
+      }
+    }
+
+    void foo() {
+      class Foo {
+        void bar() {
+        }
+      }
+      new Foo().bar();
+    }
+  }
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+}
+
+/**
+ * For testing classes with private static members (javac generates a synthetic
+ * class here but the jdt does not).
+ */
+class StaticTester {
+  private abstract static class Apple implements Fruit {
+  }
+
+  private static interface Fruit {
+    void bar();
+  }
+
+  private static class Fuji extends Apple {
+    public void bar() {
+    }
+  }
+
+  private static class Granny extends Apple {
+    public void bar() {
+    }
+  }
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+
+}
+
+/**
+ * For testing that a class with a generated name inside another top-level class
+ * is found.
+ */
+class TopLevelTester {
+  public void foo() {
+    GeneratedClassnameFinderTest.TestInterface a = new GeneratedClassnameFinderTest.TestInterface() {
+      public void foo() {
+      }
+    };
+  }
+
+  List<String> getGeneratedClasses() {
+    return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger,
+        this.getClass().getName().replace('.', '/'))).getClassNames();
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java b/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
index b5040c1..ecf7087 100644
--- a/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
@@ -11,8 +11,8 @@
   public void testGeneratedClassnames() {
     String namesToAccept[] = {
         "Test$1", "Test$10", "Test$Foo$1", "Test$1$Foo", "Test$10$Foo",
-        "$$345", "Test$1$Foo$"};
-    String namesToReject[] = {"Test1", "$345", "Test$2Foo", "Test$Foo$1Bar"};
+        "$$345", "Test$1$Foo$", "Test$1Foo", "Test$2Foo", "Test$Foo$1Bar"};
+    String namesToReject[] = {"Test1", "TestFoo", "Test$Foo$Bar", "$345"};
 
     for (String name : namesToAccept) {
       assertTrue("className = " + name + " should have been accepted",
diff --git a/user/build.xml b/user/build.xml
index dcd2c99..67c8337 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -12,6 +12,11 @@
 
   <fileset id="default.emma.tests" dir="${javac.junit.out}" 
        includes="**/EmmaClassLoadingTest.class" />
+  
+  <fileset id="default.hosted.emma.tests" dir="${javac.junit.out}" 
+      excludes="**/*jjs.test.CoverageTest.class" includes="**/*Test.class" />
+  <!-- everything succeeds except CoverageTest.java. It fails due to a javac bug in sun/OpenJDK's Java. See the file contents for details -->
+  
   <!--
     Default web mode test cases
   -->
@@ -96,6 +101,15 @@
     </gwt.junit>
   </target>
 
+  <target name="test.hosted.emma" depends="compile, compile.tests" description="Run all hosted-mode tests in emma mode.">
+    <gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.emma.tests" >
+      <extraclasspaths>
+        <pathelement location="${gwt.build}/out/dev/core/bin-test" />
+        <pathelement location="${gwt.tools.redist}/emma/emma.jar" />
+      </extraclasspaths>
+    </gwt.junit>
+  </target>
+
   <target name="test.hosted" depends="compile, compile.tests" description="Run only hosted-mode tests for this project.">
     <gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.tests" >
       <extraclasspaths>
@@ -107,7 +121,7 @@
         <pathelement location="${gwt.build}/out/dev/core/bin-test" />
         <pathelement location="${gwt.tools.redist}/emma/emma.jar" />
       </extraclasspaths>
-    </gwt.junit>
+    </gwt.junit> 
   </target>
 
   <target name="test.web" depends="compile, compile.tests" description="Run only web-mode tests for this project.">
diff --git a/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java b/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
index a975fc4..9d161ce 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java
@@ -95,6 +95,12 @@
 
       new InnerSub().new InnerSubSub().fda();
       new SecondMain().new FunkyInner();
+      /*
+       * The statement below causes a javac bug in openJdk and sun's java 6. It
+       * produces incorrect bytecode that fails with a java.lang.VerifyError --
+       * see Google's internal issue 1628473. This is likely to be an hindrance
+       * if and when GWT attempts to read bytecode directly.
+       */
       new NamedLocal().new NamedLocalSub().foo();
     }
 
diff --git a/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java b/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
index 35250a3..8d6eb5b 100644
--- a/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
+++ b/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
@@ -16,6 +16,9 @@
 package com.google.gwt.dev.shell.rewrite.client;
 
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Timer;
+
+import junit.framework.TestCase;
 
 /**
  * Test-case to check if the jsni blocks are mapped correctly between the
@@ -31,7 +34,7 @@
  */
 public class EmmaClassLoadingTest extends GWTTestCase {
 
-  enum EnumTest {
+  enum EnumClass {
     A, B, C,
   }
 
@@ -40,7 +43,7 @@
   }
 
   private static String messages[] = {
-      "a foo", "b foo", "enum A", "d foo", "e foo"};
+      "1a foo", "1b foo", "1enum A", "1d foo", "1e foo"};
 
   private static int logCount = 0;
 
@@ -56,14 +59,14 @@
   public void test1() {
     TestInterface a = new TestInterface() {
       public void foo() {
-        log("a foo");
+        log("1a foo");
       }
     };
     a.foo();
 
     TestInterface b = new TestInterface() {
       public native void foo() /*-{
-        @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("b foo");
+        @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1b foo");
       }-*/;
     };
     b.foo();
@@ -75,10 +78,10 @@
         }-*/;
       };
     }
-    EnumTest et = EnumTest.A;
+    EnumClass et = EnumClass.A;
     switch (et) {
       case A:
-        log("enum A");
+        log("1enum A");
         break;
       case B:
         log("ANY_FOO_2");
@@ -90,7 +93,7 @@
 
     TestInterface d = new TestInterface() {
       public native void foo() /*-{
-        @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("d foo");
+        @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1d foo");
       }-*/;
     };
     d.foo();
@@ -103,8 +106,193 @@
      */
     TestInterface e = new TestInterface() {
       public native void foo() /*-{
-        @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("e foo");
+        @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1e foo");
       }-*/;
     };
-  } 
+  }
+
+  public void test2() {
+    SecondTopLevelClass second = new SecondTopLevelClass();
+    SecondTopLevelClass.InnerClass.test();
+  }
+
+  public void test3() {
+    ThirdTopLevelClass third = new ThirdTopLevelClass();
+    third.test();
+  }
+
+  public void test4() {
+    FourthTopLevelClass fourth = new FourthTopLevelClass();
+    fourth.test();
+  }
+
+}
+
+/**
+ * Check that the algorithm correctly maps named inner classes. In this example,
+ * jdt generates $1Foo and $2Foo whereas javac generates $2Foo and $3Foo.
+ * 
+ */
+class FourthTopLevelClass extends TestCase {
+  private static String messages[] = {"4a foo"};
+
+  private static int logCount = 0;
+
+  private static void log(String msg) {
+    assertEquals(messages[logCount++], msg);
+  }
+
+  void test() {
+    test1();
+    test2();
+  };
+
+  private void test1() {
+    if (false) {
+      class Foo {
+        public native void foo() /*-{
+          @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("ANY_FOO");
+        }-*/;
+      }
+      new Foo().foo();
+    }
+  }
+
+  private void test2() {
+    class Foo {
+      public native void foo() /*-{
+        @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("4a foo");
+      }-*/;
+    }
+    new Foo().foo();
+  }
+
+  /*
+   * Added a test3() method so that when the test fails, it fails quickly.
+   * Instead of timing out, the AssertEquals fails
+   */
+  private void test3() {
+    class Foo {
+      public native void foo() /*-{
+        @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("4b foo");
+      }-*/;
+    }
+    new Foo().foo();
+  }
+}
+
+/**
+ * Check if GWT is able to correctly compile cases when there are multiple
+ * top-level classes and when there is a need to traverse inner classes. This
+ * class's test method simply mirrors the test methods of EmmaClassLoadingTest.
+ * 
+ */
+class SecondTopLevelClass extends TestCase {
+
+  static class InnerClass {
+    /**
+     * Test that mapping is constructed for something which is not in the main
+     * unit as well.
+     */
+    static void test() {
+      EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() {
+        public void foo() {
+          log("2a foo");
+        }
+      };
+      a.foo();
+
+      EmmaClassLoadingTest.TestInterface b = new EmmaClassLoadingTest.TestInterface() {
+        public native void foo() /*-{
+          @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2b foo");
+        }-*/;
+      };
+      b.foo();
+
+      if (false) {
+        EmmaClassLoadingTest.TestInterface c = new EmmaClassLoadingTest.TestInterface() {
+          public native void foo() /*-{
+            @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("ANY_FOO_1");
+          }-*/;
+        };
+      }
+      EmmaClassLoadingTest.EnumClass et = EmmaClassLoadingTest.EnumClass.A;
+      switch (et) {
+        case A:
+          log("2enum A");
+          break;
+        case B:
+          log("ANY_FOO_2");
+          break;
+        case C:
+          log("ANY_FOO_3");
+          break;
+      }
+
+      EmmaClassLoadingTest.TestInterface d = new EmmaClassLoadingTest.TestInterface() {
+        public native void foo() /*-{
+          @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2d foo");
+        }-*/;
+      };
+      d.foo();
+
+      /*
+       * jdt generates $1 (a), $2 (b), $3 (d), $4 (e). javac generates $1 (a),
+       * $2 (b), $3 (c), $4 (d), $5 (e), $6 (synthetic). Added e so that the
+       * test fails quickly. Otherwise, it had to wait for a time-out (in
+       * looking through jdt generated code for non-existent jsni methods of $4)
+       * to fail.
+       */
+      EmmaClassLoadingTest.TestInterface e = new EmmaClassLoadingTest.TestInterface() {
+        public native void foo() /*-{
+          @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2e foo");
+        }-*/;
+      };
+    }
+  }
+
+  private static String messages[] = {
+      "2a foo", "2b foo", "2enum A", "2d foo", "2e foo"};
+
+  private static int logCount = 0;
+
+  private static void log(String msg) {
+    assertEquals(messages[logCount++], msg);
+  }
+}
+
+/**
+ * Check that the mapping algorithm is not confused by the presence of other
+ * inner classes.
+ */
+class ThirdTopLevelClass extends TestCase {
+
+  private static String messages[] = {"3a foo"};
+
+  private static int logCount = 0;
+
+  private static void log(String msg) {
+    assertEquals(messages[logCount++], msg);
+  }
+
+  void test() {
+    Timer t1 = new Timer() {
+      @Override
+      public void run() {
+      }
+    };
+
+    EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() {
+      public native void foo() /*-{
+        @com.google.gwt.dev.shell.rewrite.client.ThirdTopLevelClass::log(Ljava/lang/String;)("3a foo");
+      }-*/;
+    };
+    a.foo();
+
+    Timer t2 = new Timer() {
+      @Override
+      public void run() {
+      }
+    };
+  }
 }