Author: [email protected]
Date: Wed Jan 14 07:59:47 2009
New Revision: 4446

Added:
     
releases/1.6/dev/core/src/com/google/gwt/dev/javac/GeneratedClassnameComparator.java
     
releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
     
releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
    releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/
     
releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/EmmaClassLoadingTest.gwt.xml
    releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/
     
releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
Modified:
    releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
    releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
    releases/1.6/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
     
releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
     
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
     
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
    releases/1.6/user/build.xml

Log:
This patch generates (and uses) the mappings from compiler-generated  
classNames of javac
(or any other compiler that generated the classes on disk) to the  
compiler-generated
classNames of jdt. It updates jsniMethods collection -- they are now  
associated with
each compilationUnit instead of each compiledClass.

It also adds test cases for all this stuff.

Patch by: amitmanjhi
Review by: jat



Modified:  
releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
==============================================================================
--- releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java     
 
(original)
+++ releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java     
 
Wed Jan 14 07:59:47 2009
@@ -15,7 +15,11 @@
   */
  package com.google.gwt.dev.javac;

+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 org.eclipse.jdt.core.compiler.CategorizedProblem;
  import org.eclipse.jdt.internal.compiler.ASTVisitor;
@@ -26,9 +30,12 @@
  import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
  import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;

+import java.util.ArrayList;
  import java.util.Collections;
+import java.util.HashMap;
  import java.util.HashSet;
  import java.util.IdentityHashMap;
+import java.util.List;
  import java.util.Map;
  import java.util.Set;

@@ -39,6 +46,25 @@
   */
  public abstract class CompilationUnit {

+  static class AnonymousClassVisitor extends EmptyVisitor {
+    /*
+     * array of classNames of inner clases that aren't synthetic.
+     */
+    List<String> classNames = new ArrayList<String>();
+
+    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);
+      }
+    }
+  }
+
    /**
     * Tracks the state of a compilation unit through the compile and  
recompile
     * process.
@@ -128,12 +154,45 @@
      return result;
    }

+  /**
+   * Map from the className in javac to the className in jdt. String  
represents
+   * the part of className after the compilation unit name. Emma-specific.
+   */
+  private Map<String, String> anonymousClassMap = null;
    private CompilationUnitDeclaration cud;
    private CategorizedProblem[] errors;
    private Set<CompiledClass> exposedCompiledClasses;
    private Set<String> fileNameRefs;
+  private List<JsniMethod> jsniMethods = null;
    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.
+   */
+  public boolean constructAnonymousClassMappings(byte classBytes[]) {
+    // 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()) {
+      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;
+  }
+
+  public boolean createdClassMapping() {
+    return anonymousClassMap != null;
+  }
+
    /**
     * Overridden to finalize; always returns object identity.
     */
@@ -142,12 +201,33 @@
      return super.equals(obj);
    }

+  public Map<String, String> getAnonymousClassMap() {
+    /*
+     * Return an empty map so that class-rewriter does not need to check  
for
+     * null. A null value indicates that anonymousClassMap was never  
created
+     * which is the case for many units. An example is a class containing  
jsni
+     * units but no inner classes.
+     */
+    if (anonymousClassMap == null) {
+      return Collections.emptyMap();
+    }
+    return anonymousClassMap;
+  }
+
    /**
     * Returns the user-relevant location of the source file. No programmatic
     * assumptions should be made about the return value.
     */
    public abstract String getDisplayLocation();

+  public boolean getJsniInjected() {
+    return jsniMethods != null;
+  }
+
+  public List<JsniMethod> getJsniMethods() {
+    return jsniMethods;
+  }
+
    /**
     * Returns the last modified time of the compilation unit.
     */
@@ -163,6 +243,15 @@
     */
    public abstract String getTypeName();

+  public boolean hasAnonymousClasses() {
+    for (CompiledClass cc : getCompiledClasses()) {
+      if (isAnonymousClass(cc)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
    /**
     * Overridden to finalize; always returns identity hash code.
     */
@@ -189,7 +278,7 @@
    public abstract boolean isGenerated();

    /**
-   *
+   *
     * @return true if the Compilation Unit is from a super-source.
     */
    public abstract boolean isSuperSource();
@@ -257,6 +346,10 @@
      state = State.COMPILED;
    }

+  void setJsniMethods(List<JsniMethod> jsniMethods) {
+    this.jsniMethods = Collections.unmodifiableList(jsniMethods);
+  }
+
    /**
     * Changes the compilation unit's internal state.
     */
@@ -290,12 +383,39 @@
      }
    }

+  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() {
+    List<String> classNames = new ArrayList<String>();
+    for (CompiledClass cc : getCompiledClasses()) {
+      if (isAnonymousClass(cc)) {
+        classNames.add(cc.getBinaryName());
+      }
+    }
+    Collections.sort(classNames, new GeneratedClassnameComparator());
+    return classNames;
+  }
+
    /**
     * Removes all accumulated state associated with compilation.
     */
    private void invalidate() {
      cud = null;
      fileNameRefs = null;
+    jsniMethods = null;
      if (exposedCompiledClasses != null) {
        for (CompiledClass compiledClass : exposedCompiledClasses) {
          compiledClass.invalidate();
@@ -303,4 +423,12 @@
        exposedCompiledClasses = null;
      }
    }
+
+  private boolean isAnonymousClass(CompiledClass cc) {
+    if (!cc.getRealClassType().isLocalType()) {
+      return false;
+    }
+    return CompilingClassLoader.isClassnameGenerated(cc.getBinaryName());
+  }
+
  }

Modified:  
releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
==============================================================================
--- releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java       
 
(original)
+++ releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java       
 
Wed Jan 14 07:59:47 2009
@@ -27,9 +27,6 @@
  import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
  import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;

-import java.util.Collections;
-import java.util.List;
-
  /**
   * Encapsulates the state of a single compiled class file.
   */
@@ -61,7 +58,6 @@
    protected final CompilationUnit unit;

    // The state below is transient.
-  private List<JsniMethod> jsniMethods;
    private NameEnvironmentAnswer nameEnvironmentAnswer;
    private JRealClassType realClassType;
    // Can be killed after parent is CHECKED.
@@ -106,10 +102,6 @@
      return enclosingClass;
    }

-  public List<JsniMethod> getJsniMethods() {
-    return jsniMethods;
-  }
-
    /**
     * Returns the enclosing package, e.g. {...@code java.util}.
     */
@@ -163,15 +155,10 @@
    void invalidate() {
      nameEnvironmentAnswer = null;
      typeDeclaration = null;
-    jsniMethods = null;
      if (realClassType != null) {
        realClassType.invalidate();
        realClassType = null;
      }
-  }
-
-  void setJsniMethods(List<JsniMethod> jsniMethods) {
-    this.jsniMethods = Collections.unmodifiableList(jsniMethods);
    }

    void setRealClassType(JRealClassType realClassType) {

Added:  
releases/1.6/dev/core/src/com/google/gwt/dev/javac/GeneratedClassnameComparator.java
==============================================================================
--- (empty file)
+++  
releases/1.6/dev/core/src/com/google/gwt/dev/javac/GeneratedClassnameComparator.java
     
Wed Jan 14 07:59:47 2009
@@ -0,0 +1,82 @@
+/*
+ * 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 java.util.Comparator;
+
+class GeneratedClassnameComparator implements Comparator<String> {
+
+  public int compare(String arg0, String arg1) {
+    String pattern = "\\$";
+    String splits0[] = arg0.split(pattern);
+    String splits1[] = arg1.split(pattern);
+    if (splits0.length != splits1.length) {
+      return splits0.length - splits1.length;
+    }
+    for (int i = 0; i < splits0.length; i++) {
+      int answer = compareWithoutDollars(splits0[i], splits1[i]);
+      if (answer != 0) {
+        return answer;
+      }
+    }
+    return 0;
+  }
+
+  /*
+   * 3 cases: (i) both can be converted to integer: compare the integral  
value.
+   * (ii) only one can be converted to integer: the one with integral  
value is
+   * lower. (iii) none can be converted to integer: compare the strings.
+   */
+  private int compareWithoutDollars(String arg0, String arg1) {
+    boolean arg0IsInt = false;
+    boolean arg1IsInt = false;
+    int int0 = 0, int1 = 0;
+    if ((arg0 == null) != (arg1 == null)) {
+      return (arg0 == null) ? -1 : 1;
+    }
+    if (arg0 == null) {
+      return 0;
+    }
+
+    if (arg0.charAt(0) != '-') {
+      try {
+        int0 = Integer.parseInt(arg0);
+        arg0IsInt = true;
+      } catch (NumberFormatException ex) {
+        // ignored
+      }
+    }
+
+    if (arg1.charAt(0) != '-') {
+      try {
+        int1 = Integer.parseInt(arg1);
+        arg1IsInt = true;
+      } catch (NumberFormatException ex) {
+        // ignored
+      }
+    }
+
+    if (arg0IsInt != arg1IsInt) {
+      return arg0IsInt ? -1 : 1;
+    }
+
+    // now either both are int or both are Strings
+    if (arg0IsInt) {
+      return int0 - int1;
+    }
+    return arg0.compareTo(arg1);
+  }
+}

Modified:  
releases/1.6/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
==============================================================================
--- releases/1.6/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java       
 
(original)
+++ releases/1.6/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java       
 
Wed Jan 14 07:59:47 2009
@@ -65,7 +65,7 @@
      private final String[] paramNames;
      private final String source;
      private final JsProgram program;
-
+
      private JsniMethodImpl(String name, String source, String[] paramNames,
          int line, String location, JsProgram program) {
        this.name = name;
@@ -133,10 +133,13 @@
        if (unit.getState() == State.COMPILED) {
          String loc = unit.getDisplayLocation();
          String source = unit.getSource();
+        assert unit.getJsniMethods() == null;
+        List<JsniMethod> jsniMethods = new ArrayList<JsniMethod>();
          for (CompiledClass compiledClass : unit.getCompiledClasses()) {
-          assert compiledClass.getJsniMethods() == null;
-          collectJsniMethods(logger, loc, source, compiledClass, program);
+          jsniMethods.addAll(collectJsniMethods(logger, loc, source,
+              compiledClass, program));
          }
+        unit.setJsniMethods(jsniMethods);
        }
      }
    }
@@ -144,8 +147,8 @@
    /**
     * TODO: log real errors, replacing GenerateJavaScriptAST?
     */
-  private static void collectJsniMethods(TreeLogger logger, String loc,
-      String source, CompiledClass compiledClass, JsProgram program) {
+  private static List<JsniMethod> collectJsniMethods(TreeLogger logger,
+      String loc, String source, CompiledClass compiledClass, JsProgram  
program) {
      TypeDeclaration typeDecl = compiledClass.getTypeDeclaration();
      int[] lineEnds =  
typeDecl.compilationResult.getLineSeparatorPositions();
      List<JsniMethod> jsniMethods = new ArrayList<JsniMethod>();
@@ -174,7 +177,7 @@
              startLine, loc, program));
        }
      }
-    compiledClass.setJsniMethods(jsniMethods);
+    return jsniMethods;
    }

    private static Interval findJsniSource(String source,

Modified:  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
==============================================================================
---  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java    
 
(original)
+++  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java    
 
Wed Jan 14 07:59:47 2009
@@ -23,6 +23,7 @@
  import com.google.gwt.core.ext.typeinfo.JParameter;
  import com.google.gwt.core.ext.typeinfo.TypeOracle;
  import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
  import com.google.gwt.dev.javac.CompiledClass;
  import com.google.gwt.dev.javac.JsniMethod;
  import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
@@ -47,8 +48,10 @@
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
+import java.util.List;
  import java.util.Map;
  import java.util.Set;
+import java.util.regex.Pattern;

  /**
   * An isolated {...@link ClassLoader} for running all user code. All user  
files are
@@ -349,7 +352,7 @@

    private static final String CLASS_DUMP_PATH = "rewritten-classes";

-  private static boolean emmaIsAvailable = false;
+  private static boolean emmaAvailable = false;

    private static EmmaStrategy emmaStrategy;

@@ -358,6 +361,8 @@
     */
    private static byte[] javaScriptHostBytes;

+  private static final Pattern GENERATED_CLASSNAME_PATTERN =  
Pattern.compile(".+\\$\\d+(\\$.*)?");
+
    static {
      for (Class<?> c : BRIDGE_CLASSES) {
        BRIDGE_CLASS_NAMES.put(c.getName(), c);
@@ -373,10 +378,27 @@
        Class<?> emmaBridge = Class.forName(EmmaStrategy.EMMA_RT_CLASSNAME,
            false, Thread.currentThread().getContextClassLoader());
        BRIDGE_CLASS_NAMES.put(EmmaStrategy.EMMA_RT_CLASSNAME, emmaBridge);
-      emmaIsAvailable = true;
+      emmaAvailable = true;
      } catch (ClassNotFoundException ignored) {
      }
-    emmaStrategy = EmmaStrategy.get(emmaIsAvailable);
+    emmaStrategy = EmmaStrategy.get(emmaAvailable);
+  }
+  /**
+   * 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.
+   * <p>
+   * If new compilers have different conventions for anonymous and  
synthetic
+   * classes, this code needs to be updated.
+   * </p>
+   *
+   * @param className name of the class to be checked.
+   * @return true iff class or any of its enclosing classes are anonymous  
or
+   *         synthetic.
+   */
+  public static boolean isClassnameGenerated(String className) {
+    return GENERATED_CLASSNAME_PATTERN.matcher(className).matches();
    }

    private static void classDump(String name, byte[] bytes) {
@@ -646,43 +668,76 @@
      CompiledClass compiledClass = compilationState.getClassFileMap().get(
          lookupClassName);

+    CompilationUnit unit = (compiledClass == null)
+        ? getUnitForClassName(className) : compiledClass.getUnit();
+    if (emmaAvailable) {
+      /*
+       * build the map for anonymous classes. Do so only if unit has  
anonymous
+       * classes, jsni methods, is not super-source and the map has not  
been
+       * built before.
+       */
+      List<JsniMethod> jsniMethods = (unit == null) ? null
+          : unit.getJsniMethods();
+      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);
+        }
+      }
+    }
+
      byte classBytes[] = null;
      if (compiledClass != null) {
-
-      injectJsniFor(compiledClass);
        classBytes = compiledClass.getBytes();
        if (!compiledClass.getUnit().isSuperSource()) {
-        /*
-         * It is okay if Emma throws away the old classBytes since the  
actual
-         * jsni injection happens in the rewriter. The injectJsniFor method
-         * above simply defines the native methods in the browser.
-         */
          classBytes = emmaStrategy.getEmmaClassBytes(classBytes,
              lookupClassName, compiledClass.getUnit().getLastModified());
        } else {
          logger.log(TreeLogger.SPAM, "no emma instrumentation for "
              + lookupClassName + " because it is from super-source");
        }
-    } else if (emmaIsAvailable) {
+    } else if (emmaAvailable) {
        /*
         * TypeOracle does not know about this class. Most probably, this  
class
         * was referenced in one of the classes loaded from disk. Check if  
we can
         * find it on disk. Typically this is a synthetic class added by the
-       * compiler. If the synthetic class contains native methods, it will  
fail
-       * later.
+       * compiler.
         */
-      if (isSynthetic(className)) {
+      if (shouldLoadClassFromDisk(className)) {
          /*
           * modification time = 0 ensures that whatever is on the disk is  
always
           * loaded.
           */
-        logger.log(TreeLogger.SPAM, "EmmaStrategy: loading " +  
lookupClassName
+        logger.log(TreeLogger.DEBUG, "EmmaStrategy: loading " +  
lookupClassName
              + " from disk even though TypeOracle does not know about it");
          classBytes = emmaStrategy.getEmmaClassBytes(null, lookupClassName,  
0);
        }
      }
      if (classBytes != null && classRewriter != null) {
-      byte[] newBytes = classRewriter.rewrite(className, classBytes);
+      /*
+       * The injectJsniFor method defines the native methods in the  
browser. The
+       * rewriter does the jsni injection.
+       */
+      injectJsniMethods(unit);
+      Map<String, String> anonymousClassMap = Collections.emptyMap();
+      if (unit != null) {
+        anonymousClassMap = unit.getAnonymousClassMap();
+      }
+      byte[] newBytes = classRewriter.rewrite(className, classBytes,
+          anonymousClassMap);
        if (CLASS_DUMP) {
          if (!Arrays.equals(classBytes, newBytes)) {
            classDump(className, newBytes);
@@ -699,8 +754,28 @@
      return name;
    }

-  private void injectJsniFor(CompiledClass compiledClass) {
-    for (JsniMethod jsniMethod : compiledClass.getJsniMethods()) {
+  /**
+   * Returns the compilationUnit corresponding to the className.
+   * <p>
+   * Not considering classnames where a $ sign appears.
+   */
+  private CompilationUnit getUnitForClassName(String className) {
+    String mainTypeName = className;
+    int index = mainTypeName.length();
+    CompilationUnit unit = null;
+    while (unit == null && index != -1) {
+      mainTypeName = mainTypeName.substring(0, index);
+      unit = compilationState.getCompilationUnitMap().get(mainTypeName);
+      index = mainTypeName.lastIndexOf('$');
+    }
+    return unit;
+  }
+
+  private void injectJsniMethods(CompilationUnit unit) {
+    if (unit == null || unit.getJsniMethods() == null) {
+      return;
+    }
+    for (JsniMethod jsniMethod : unit.getJsniMethods()) {
        String body = Jsni.getJavaScriptForHostedMode(logger, jsniMethod);
        if (body == null) {
          // The error has been logged; just ignore it for now.
@@ -711,28 +786,12 @@
      }
    }

-  /**
-   * For safety, we only allow synthetic classes to be loaded this way.  
Accepts
-   * any classes whose names match .+$\d+
-   * <p>
-   * If new compilers have different conventions for synthetic classes,  
this
-   * code needs to be updated.
-   * </p>
-   *
-   * @param className class to be loaded from disk.
-   * @return true iff class should be loaded from disk
-   */
-  private boolean isSynthetic(String className) {
-    int index = className.lastIndexOf("$");
-    if (index <= 0 || index == className.length() - 1) {
-      return false;
-    }
-    for (int i = index + 1; i < className.length(); i++) {
-      if (!Character.isDigit(className.charAt(i))) {
-        return false;
-      }
-    }
-    return true;
+  private boolean isBaseClassInGwt(String className) {
+    return getUnitForClassName(className) != null;
+  }
+
+  private boolean shouldLoadClassFromDisk(String className) {
+    return isBaseClassInGwt(className) && isClassnameGenerated(className);
    }

    /**

Modified:  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
==============================================================================
---  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
  
(original)
+++  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
  
Wed Jan 14 07:59:47 2009
@@ -33,15 +33,15 @@
   * <ol>
   * <li>Rewrites all native methods into non-native thunks to call JSNI via
   * {...@link com.google.gwt.dev.shell.JavaScriptHost}.</li>
- * <li>Rewrites all JSO types into an interface type (which retains the
- * original name) and an implementation type (which has a $ appended).</li>
- * <li>All JSO interface types are empty and mirror the original type
- * hierarchy.</li>
- * <li>All JSO impl types contain the guts of the original type, except  
that
- * all instance methods are reimplemented as statics.</li>
- * <li>Calls sites to JSO types rewritten to dispatch to impl types. Any
- * virtual calls are also made static. Static field references to JSO types
- * reference static fields in the the impl class.</li>
+ * <li>Rewrites all JSO types into an interface type (which retains the  
original
+ * name) and an implementation type (which has a $ appended).</li>
+ * <li>All JSO interface types are empty and mirror the original type  
hierarchy.
+ * </li>
+ * <li>All JSO impl types contain the guts of the original type, except  
that all
+ * instance methods are reimplemented as statics.</li>
+ * <li>Calls sites to JSO types rewritten to dispatch to impl types. Any  
virtual
+ * calls are also made static. Static field references to JSO types  
reference
+ * static fields in the the impl class.</li>
   * <li>JavaScriptObject$ implements all the interface types and is the only
   * instantiable type.</li>
   * </ol>
@@ -167,8 +167,11 @@
     *
     * @param className the name of the class
     * @param classBytes the bytes of the class
+   * @param anonymousClassMap a map between the anonymous class names of  
java
+   *          compiler used to compile code and jdt. Emma-specific.
     */
-  public byte[] rewrite(String className, byte[] classBytes) {
+  public byte[] rewrite(String className, byte[] classBytes,
+      Map<String, String> anonymousClassMap) {
      String desc = toDescriptor(className);
      assert (!jsoIntfDescs.contains(desc));

@@ -185,7 +188,7 @@
        v = new WriteJsoImpl(v, jsoIntfDescs, mapper);
      }

-    v = new RewriteJsniMethods(v);
+    v = new RewriteJsniMethods(v, anonymousClassMap);

      if (Double.parseDouble(System.getProperty("java.class.version")) <  
Opcodes.V1_6) {
        v = new ForceClassVersion15(v);

Modified:  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
==============================================================================
---  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
       
(original)
+++  
releases/1.6/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
       
Wed Jan 14 07:59:47 2009
@@ -25,6 +25,7 @@
  import com.google.gwt.dev.shell.JavaScriptHost;

  import java.lang.reflect.Modifier;
+import java.util.Map;

  /**
   * Turns native method declarations into normal Java functions which  
perform the
@@ -224,9 +225,8 @@

      /**
       * Does all of the work necessary to do the dispatch to the appropriate
-     * variant of
-     * {...@link JavaScriptHost#invokeNativeVoid  
JavaScriptHost.invokeNative*}.
-     * And example output:
+     * variant of {...@link JavaScriptHost#invokeNativeVoid
+     * JavaScriptHost.invokeNative*}. And example output:
       *
       * <pre>
       * return JavaScriptHost.invokeNativeInt(
@@ -238,8 +238,8 @@
        super.visitCode();

        /*
-       * If you modify the generated code, you must recompute the stack  
size
-       * in visitEnd().
+       * If you modify the generated code, you must recompute the stack  
size in
+       * visitEnd().
         */

        // First argument - JSNI signature
@@ -322,9 +322,12 @@
     * The name of the class we're operating on.
     */
    private String classDesc;
+  private Map<String, String> anonymousClassMap;

-  public RewriteJsniMethods(ClassVisitor v) {
+  public RewriteJsniMethods(ClassVisitor v,
+      Map<String, String> anonymousClassMap) {
      super(v);
+    this.anonymousClassMap = anonymousClassMap;
    }

    @Override
@@ -356,8 +359,8 @@
     *
     * @param name the name of the method; for example {...@code "echo"}
     * @param descriptor the descriptor for the method; for example  
{...@code "(I)I"}
-   * @return the JSNI signature for the method; for example,
-   *         {...@code "@com.google.gwt.sample.hello.client.Hello::echo(I)"}
+   * @return the JSNI signature for the method; for example, {...@code
+   *         "@com.google.gwt.sample.hello.client.Hello::echo(I)"}
     */
    private String getJsniSignature(String name, String descriptor) {
      int argsIndexBegin = descriptor.indexOf('(');
@@ -367,6 +370,11 @@
          + descriptor;
      String argsDescriptor = descriptor.substring(argsIndexBegin,
          argsIndexEnd + 1);
-    return "@" + classDesc.replace('/', '.') + "::" + name +  
argsDescriptor;
+    String classDescriptor = classDesc.replace('/', '.');
+    String newDescriptor = anonymousClassMap.get(classDesc);
+    if (newDescriptor != null) {
+      classDescriptor = newDescriptor.replace('/', '.');
+    }
+    return "@" + classDescriptor + "::" + name + argsDescriptor;
    }
  }

Added:  
releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
==============================================================================
--- (empty file)
+++  
releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java
        
Wed Jan 14 07:59:47 2009
@@ -0,0 +1,49 @@
+package com.google.gwt.dev.javac;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ *
+ * This class tests to see if the anonymous class names are sorted in the  
right
+ * order. For example, Foo$10 should be after Foo$2.
+ */
+public class GeneratedClassnameComparatorTest extends TestCase {
+
+  public void testBasicOrder() {
+    int max = 15;
+    String original[] = new String[max];
+    String expected[] = new String[max];
+    for (int i = 0; i < max; i++) {
+      String name = "Foo$" + (i + 1);
+      original[i] = name;
+      expected[i] = name;
+    }
+    Arrays.sort(original, new GeneratedClassnameComparator());
+    for (int i = 0; i < max; i++) {
+      assertEquals("index = " + i, expected[i], original[i]);
+    }
+  }
+
+  public void testHierarchicalOrder() {
+    String original[] = {
+        "Foo$1", "Foo$1$1$1", "Foo$1$2", "Foo$2", "Foo$2$1", "Foo$3",};
+    String expected[] = {
+        "Foo$1", "Foo$2", "Foo$3", "Foo$1$2", "Foo$2$1", "Foo$1$1$1"};
+    Arrays.sort(original, new GeneratedClassnameComparator());
+    for (int i = 0; i < original.length; i++) {
+      assertEquals("index = " + i, expected[i], original[i]);
+    }
+  }
+
+  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"};
+    Arrays.sort(original, new GeneratedClassnameComparator());
+    for (int i = 0; i < original.length; i++) {
+      assertEquals("index = " + i, expected[i], original[i]);
+    }
+  }
+
+}

Added:  
releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java
==============================================================================
--- (empty file)
+++  
releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java 
 
Wed Jan 14 07:59:47 2009
@@ -0,0 +1,27 @@
+package com.google.gwt.dev.shell;
+
+import junit.framework.TestCase;
+
+public class GeneratedClassnameTest extends TestCase {
+
+  /**
+   * Test if {...@link CompilingClassLoader#isClassnameGenerated(String)}  
works
+   * correctly.
+   */
+  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"};
+
+    for (String name : namesToAccept) {
+      assertTrue("className = " + name + " should have been accepted",
+          CompilingClassLoader.isClassnameGenerated(name));
+    }
+
+    for (String name : namesToReject) {
+      assertFalse("className = " + name + " should not have been accepted",
+          CompilingClassLoader.isClassnameGenerated(name));
+    }
+  }
+}

Modified: releases/1.6/user/build.xml
==============================================================================
--- releases/1.6/user/build.xml (original)
+++ releases/1.6/user/build.xml Wed Jan 14 07:59:47 2009
@@ -10,6 +10,8 @@
    <fileset id="default.hosted.tests" dir="${javac.junit.out}"
         includes="${gwt.junit.testcase.includes}" />

+  <fileset id="default.emma.tests" dir="${javac.junit.out}"
+       includes="**/EmmaClassLoadingTest.class" />
    <!--
      Default web mode test cases
    -->
@@ -98,6 +100,12 @@
      <gwt.junit test.args="${test.args}"  
test.out="${junit.out}/${build.host.platform}-hosted-mode"  
test.cases="default.hosted.tests" >
        <extraclasspaths>
          <pathelement location="${gwt.build}/out/dev/core/bin-test" />
+      </extraclasspaths>
+    </gwt.junit>
+    <gwt.junit test.args="${test.args}"  
test.out="${junit.out}/${build.host.platform}-hosted-mode-emma"  
test.cases="default.emma.tests" >
+      <extraclasspaths>
+        <pathelement location="${gwt.build}/out/dev/core/bin-test" />
+        <pathelement location="${gwt.tools.redist}/emma/emma.jar" />
        </extraclasspaths>
      </gwt.junit>
    </target>

Added:  
releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/EmmaClassLoadingTest.gwt.xml
==============================================================================
--- (empty file)
+++  
releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/EmmaClassLoadingTest.gwt.xml
     
Wed Jan 14 07:59:47 2009
@@ -0,0 +1,3 @@
+<module>
+  <inherits name="com.google.gwt.user.User"/>
+</module>

Added:  
releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
==============================================================================
--- (empty file)
+++  
releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java
         
Wed Jan 14 07:59:47 2009
@@ -0,0 +1,110 @@
+/*
+ * 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.shell.rewrite.client;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Test-case to check if the jsni blocks are mapped correctly between the
+ * synthetic and anonymous classes generated by the javac and the jdt  
compiler.
+ * <p>
+ * This issue arises when emma.jar is in the classpath. Gwt then follows  
the
+ * emma strategy and loads (offline-instrumented) classes from the disk.  
These
+ * classes could have been generated by any java compiler -- the java  
compiler
+ * does not have to be jdt and frequently is javac. These class files may
+ * contain references to synthetic classes and anonymous classes that the  
jdt
+ * does not know about. This testcase checks that gwt handles these cases
+ * correctly.
+ */
+public class EmmaClassLoadingTest extends GWTTestCase {
+
+  enum EnumTest {
+    A, B, C,
+  }
+
+  interface TestInterface {
+    void foo();
+  }
+
+  private static String messages[] = {
+      "a foo", "b foo", "enum A", "d foo", "e foo"};
+
+  private static int logCount = 0;
+
+  private static void log(String msg) {
+    assertEquals(messages[logCount++], msg);
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.shell.rewrite.EmmaClassLoadingTest";
+  }
+
+  public void test1() {
+    TestInterface a = new TestInterface() {
+      public void foo() {
+        log("a 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");
+      }-*/;
+    };
+    b.foo();
+
+    if (false) {
+      TestInterface c = new TestInterface() {
+        public native void foo() /*-{
+           
@com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("ANY_FOO_1");
+        }-*/;
+      };
+    }
+    EnumTest et = EnumTest.A;
+    switch (et) {
+      case A:
+        log("enum A");
+        break;
+      case B:
+        log("ANY_FOO_2");
+        break;
+      case C:
+        log("ANY_FOO_3");
+        break;
+    }
+
+    TestInterface d = new TestInterface() {
+      public native void foo() /*-{
+         
@com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("d
  
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.
+     */
+    TestInterface e = new TestInterface() {
+      public native void foo() /*-{
+         
@com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("e
  
foo");
+      }-*/;
+    };
+  }
+}

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to