Allows JSNI references to non-overloaded methods to use * in place
of the list of parameter types.

Review by: bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7368 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
index dea6347..7064bc5 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniChecker.java
@@ -410,6 +410,9 @@
     }
 
     private boolean paramTypesMatch(MethodBinding method, JsniRef jsniRef) {
+      if (jsniRef.matchesAnyOverload()) {
+        return true;
+      }
       StringBuilder methodSig = new StringBuilder();
       if (method.parameters != null) {
         for (TypeBinding binding : method.parameters) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index d4fbd85..ce3ab28 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -768,6 +768,7 @@
         "init".toCharArray(), program.getIndexedType("EntryMethodHolder"),
         program.getTypeVoid(), false, true, true, false, false);
     bootStrapMethod.freezeParamTypes();
+    bootStrapMethod.setSynthetic();
 
     JMethodBody body = (JMethodBody) bootStrapMethod.getBody();
     JBlock block = body.getBlock();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index e2cb5de..fcaabee 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -52,6 +52,7 @@
   private boolean isFinal;
   private final boolean isPrivate;
   private final boolean isStatic;
+  private boolean isSynthetic = false;
   private final String name;
   private List<JType> originalParamTypes;
   private JType originalReturnType;
@@ -178,6 +179,10 @@
     return isStatic;
   }
 
+  public boolean isSynthetic() {
+    return isSynthetic;
+  }
+
   public boolean isTrace() {
     return trace;
   }
@@ -226,6 +231,10 @@
     }
   }
 
+  public void setSynthetic() {
+    isSynthetic = true;
+  }
+
   public void setTrace() {
     this.trace = true;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 3092e21..e2fab31 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -125,6 +125,10 @@
   }
 
   public static String getJsniSig(JMethod method) {
+    return getJsniSig(method, true);
+  }
+
+  public static String getJsniSig(JMethod method, boolean addReturnType) {
     StringBuffer sb = new StringBuffer();
     sb.append(method.getName());
     sb.append("(");
@@ -133,7 +137,9 @@
       sb.append(type.getJsniSignatureName());
     }
     sb.append(")");
-    sb.append(method.getOriginalReturnType().getJsniSignatureName());
+    if (addReturnType) {
+      sb.append(method.getOriginalReturnType().getJsniSignatureName());
+    }
     return sb.toString();
   }
 
@@ -957,6 +963,7 @@
       nullMethod = new JMethod(createSourceInfoSynthetic(JProgram.class,
           "Null method"), "nullMethod", null, JNullType.INSTANCE, false, false,
           true, true);
+      nullMethod.setSynthetic();
     }
     return nullMethod;
   }
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 55f54ea..58b0682 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
@@ -403,6 +403,7 @@
           BuildDeclMapVisitor.class, "Synthetic constructor"),
           "new".toCharArray(), type, program.getNonNullType(type), false, true,
           true, false, false);
+      synthetic.setSynthetic();
 
       // new Foo() : Create the instance
       JNewInstance newInstance = new JNewInstance(
@@ -567,6 +568,7 @@
               program.getTypeJavaLangClass(), false, false, false, false, false);
           assert (type.getMethods().get(2) == getClassMethod);
           getClassMethod.freezeParamTypes();
+          getClassMethod.setSynthetic();
         }
 
         if (binding.isNestedType() && !binding.isStatic()) {
@@ -653,6 +655,9 @@
       JMethod newMethod = program.createMethod(info, b.selector, enclosingType,
           returnType, b.isAbstract(), b.isStatic(), b.isFinal(), b.isPrivate(),
           b.isNative());
+      if (b.isSynthetic()) {
+        newMethod.setSynthetic();
+      }
 
       typeMap.put(b, newMethod);
       return newMethod;
@@ -810,6 +815,7 @@
             "$clinit".toCharArray(), newType, program.getTypeVoid(), false,
             true, true, true, false);
         clinit.freezeParamTypes();
+        clinit.setSynthetic();
 
         if (newType instanceof JClassType) {
           JMethod init = program.createMethod(info.makeChild(
@@ -817,6 +823,7 @@
               "$init".toCharArray(), newType, program.getTypeVoid(), false,
               false, true, true, false);
           init.freezeParamTypes();
+          init.setSynthetic();
         }
 
         typeMap.put(binding, newType);
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 5e2add6..14845b9 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
@@ -2037,6 +2037,7 @@
           jdtBridgeMethod.selector, clazz,
           (JType) typeMap.get(jdtBridgeMethod.returnType.erasure()), false,
           false, true, false, false);
+      bridgeMethod.setSynthetic();
       int paramIdx = 0;
       for (TypeBinding jdtParamType : jdtBridgeMethod.parameters) {
         String paramName = "p" + paramIdx++;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java
index a85c8ea..a212d59 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dev.jjs.ast.HasEnclosingType;
 import com.google.gwt.dev.jjs.ast.JArrayType;
 import com.google.gwt.dev.jjs.ast.JClassLiteral;
+import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JMethod;
@@ -26,15 +27,15 @@
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.util.JsniRef;
 
-import java.util.LinkedList;
-import java.util.Queue;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
 import java.util.TreeSet;
 
 /**
  * A utility class that can look up a {@link JsniRef} in a {@link JProgram}.
  */
 public class JsniRefLookup {
-
   /**
    * A callback used to indicate the reason for a failed JSNI lookup.
    */
@@ -74,7 +75,7 @@
           return program.getNullField();
         }
 
-      } else if (fieldName.equals("class")) {
+      } else if (fieldName.equals(JsniRef.CLASS)) {
         JClassLiteral lit = program.getLiteralClass(type);
         return lit.getField();
 
@@ -97,14 +98,12 @@
       errorReporter.reportError("Unresolvable native reference to field '"
           + fieldName + "' in type '" + className + "'");
       return null;
-
     } else if (type instanceof JPrimitiveType) {
       errorReporter.reportError("May not refer to methods on primitive types");
       return null;
-
     } else {
       // look for a method
-      TreeSet<String> almostMatches = new TreeSet<String>();
+      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig = new LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>>();
       String methodName = ref.memberName();
       String jsniSig = ref.memberSignature();
       if (type == null) {
@@ -112,37 +111,40 @@
           return program.getNullMethod();
         }
       } else {
-        Queue<JDeclaredType> workList = new LinkedList<JDeclaredType>();
-        workList.add((JDeclaredType) type);
-        while (!workList.isEmpty()) {
-          JDeclaredType cur = workList.poll();
-          for (JMethod method : cur.getMethods()) {
-            if (method.getName().equals(methodName)) {
-              String sig = JProgram.getJsniSig(method);
-              if (sig.equals(jsniSig)) {
-                return method;
-              } else if (sig.startsWith(jsniSig) && jsniSig.endsWith(")")) {
-                return method;
-              } else {
-                almostMatches.add(sig);
-              }
-            }
-          }
-          if (cur.getSuperClass() != null) {
-            workList.add(cur.getSuperClass());
-          }
-          workList.addAll(cur.getImplements());
+        findMostDerivedMembers(matchesBySig, (JDeclaredType) type,
+            ref.memberName(), true);
+        LinkedHashMap<String, HasEnclosingType> matches = matchesBySig.get(jsniSig);
+        if (matches != null && matches.size() == 1) {
+          /*
+           * Backward compatibility: allow accessing bridge methods with full
+           * qualification
+           */
+          return matches.values().iterator().next();
+        }
+
+        removeSyntheticMembers(matchesBySig);
+        matches = matchesBySig.get(jsniSig);
+        if (matches != null && matches.size() == 1) {
+          return matches.values().iterator().next();
         }
       }
 
-      if (almostMatches.isEmpty()) {
+      // Not found; signal an error
+      if (matchesBySig.isEmpty()) {
         errorReporter.reportError("Unresolvable native reference to method '"
             + methodName + "' in type '" + className + "'");
         return null;
       } else {
         StringBuilder suggestList = new StringBuilder();
         String comma = "";
-        for (String almost : almostMatches) {
+        // use a TreeSet to sort the near matches
+        TreeSet<String> almostMatchSigs = new TreeSet<String>();
+        for (String sig : matchesBySig.keySet()) {
+          if (matchesBySig.get(sig).size() == 1) {
+            almostMatchSigs.add(sig);
+          }
+        }
+        for (String almost : almostMatchSigs) {
           suggestList.append(comma + "'" + almost + "'");
           comma = ", ";
         }
@@ -154,4 +156,117 @@
     }
   }
 
+  private static void addMember(
+      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig,
+      HasEnclosingType member, String refSig) {
+    LinkedHashMap<String, HasEnclosingType> matchesByFullSig = matchesBySig.get(refSig);
+    if (matchesByFullSig == null) {
+      matchesByFullSig = new LinkedHashMap<String, HasEnclosingType>();
+      matchesBySig.put(refSig, matchesByFullSig);
+    }
+
+    String fullSig;
+    if (member instanceof JField) {
+      fullSig = ((JField) member).getName();
+    } else {
+      fullSig = JProgram.getJsniSig((JMethod) member);
+    }
+
+    matchesByFullSig.put(fullSig, member);
+  }
+
+  /**
+   * For each member with the given name, find the most derived members for each
+   * JSNI reference that match it. For wildcard JSNI references, there will in
+   * general be more than one match. This method does not ignore synthetic
+   * methods.
+   */
+  private static void findMostDerivedMembers(
+      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig,
+      JDeclaredType targetType, String memberName, boolean addConstructors) {
+    /*
+     * Analyze superclasses and interfaces first. More derived members will thus
+     * be seen later.
+     */
+    if (targetType instanceof JClassType) {
+      JClassType targetClass = (JClassType) targetType;
+      if (targetClass.getSuperClass() != null) {
+        findMostDerivedMembers(matchesBySig, targetClass.getSuperClass(),
+            memberName, false);
+      }
+    }
+    for (JDeclaredType intf : targetType.getImplements()) {
+      findMostDerivedMembers(matchesBySig, intf, memberName, false);
+    }
+
+    // Get the methods on this class/interface.
+    for (JMethod method : targetType.getMethods()) {
+      if (method.getName().equals(memberName)) {
+        if (addConstructors || !method.getName().equals(JsniRef.NEW)) {
+          addMember(matchesBySig, method, getJsniSignature(method, false));
+          addMember(matchesBySig, method, getJsniSignature(method, true));
+        }
+      }
+    }
+
+    // Get the fields on this class/interface.
+    for (JField field : targetType.getFields()) {
+      if (field.getName().equals(memberName)) {
+        addMember(matchesBySig, field, field.getName());
+      }
+    }
+  }
+
+  /**
+   * Return the JSNI signature for a member. Leave off the return type for a
+   * method signature, so as to match what a user would type in as a JsniRef.
+   */
+  private static String getJsniSignature(HasEnclosingType member,
+      boolean wildcardParams) {
+    if (member instanceof JField) {
+      return ((JField) member).getName();
+    }
+    JMethod method = (JMethod) member;
+
+    if (wildcardParams) {
+      return method.getName() + "(" + JsniRef.WILDCARD_PARAM_LIST + ")";
+    } else {
+      return JProgram.getJsniSig(method, false);
+    }
+  }
+
+  private static boolean isNewMethod(HasEnclosingType member) {
+    if (member instanceof JMethod) {
+      JMethod method = (JMethod) member;
+      return method.getName().equals(JsniRef.NEW);
+    }
+
+    assert member instanceof JField;
+    return false;
+  }
+
+  private static boolean isSynthetic(HasEnclosingType member) {
+    if (member instanceof JMethod) {
+      return ((JMethod) member).isSynthetic();
+    }
+
+    assert member instanceof JField;
+    return false;
+  }
+
+  private static void removeSyntheticMembers(
+      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig) {
+    for (LinkedHashMap<String, HasEnclosingType> matchesByFullSig : matchesBySig.values()) {
+      Set<String> toRemove = new LinkedHashSet<String>();
+      for (String fullSig : matchesByFullSig.keySet()) {
+        HasEnclosingType member = matchesByFullSig.get(fullSig);
+        if (isSynthetic(member) && !isNewMethod(member)) {
+          toRemove.add(fullSig);
+        }
+      }
+      for (String fullSig : toRemove) {
+        matchesByFullSig.remove(fullSig);
+      }
+    }
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
index 83929cd..7199696 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
@@ -133,6 +133,7 @@
     JMethod newMethod = program.createMethod(sourceInfo.makeChild(
         JsoDevirtualizer.class, "Devirtualized method"), name.toCharArray(),
         jsoType, objectMethod.getType(), false, true, true, false, false);
+    newMethod.setSynthetic();
 
     // Setup parameters.
     JParameter thisParam = program.createParameter(sourceInfo,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index d0bf13f..2b88423 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -155,6 +155,7 @@
       JMethod newMethod = new JMethod(sourceInfo.makeChild(
           CreateStaticImplsVisitor.class, "Devirtualized function"), newName,
           enclosingType, returnType, false, true, true, x.isPrivate());
+      newMethod.setSynthetic();
 
       // Setup parameters; map from the old params to the new params
       JParameter thisParam = JParameter.create(sourceInfo.makeChild(
diff --git a/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java b/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java
index 871fec7..934eabb 100644
--- a/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java
+++ b/dev/core/src/com/google/gwt/dev/js/rhino/TokenStream.java
@@ -1374,6 +1374,18 @@
       // Assume the opening '(' has already been read.
       // Read param type signatures until we see a closing ')'.
       //
+      // First check for the special case of * as the parameter list, indicating
+      // a wildcard
+      if (in.peek() == '*') {
+        addToString(in.read());
+        if (in.peek() != ')') {
+          reportSyntaxError("msg.jsni.expected.char", new String[] { ")" });
+        }
+        addToString(in.read());
+        return true;
+      }
+      
+      // Otherwise, loop through reading one param type at a time
       do {
         int c = in.read();
         if (c == ')') {
diff --git a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
index 4140d30..5f70df0 100644
--- a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
+++ b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
@@ -15,19 +15,24 @@
  */
 package com.google.gwt.dev.shell;
 
+import com.google.gwt.dev.util.JsniRef;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
 
 /**
  * Helper class for dispatching methods to Java objects. It takes methods on
  * various Java classes and assigns DISPID's to them.
  */
 public class DispatchClassInfo {
-
   private Class<?> cls;
 
   private final int clsId;
@@ -62,20 +67,97 @@
     return id.intValue();
   }
 
-  private void addMember(Member member, String sig) {
-    if (member.isSynthetic()) {
-      return;
+  private void addMember(
+      LinkedHashMap<String, LinkedHashMap<String, Member>> members,
+      Member member, String sig) {
+    String fullSig = getJsniSignature(member, false);
+    LinkedHashMap<String, Member> membersWithSig = members.get(sig);
+    if (membersWithSig == null) {
+      membersWithSig = new LinkedHashMap<String, Member>();
+      members.put(sig, membersWithSig);
     }
-    int index = memberById.size();
-    memberById.add(member);
-    memberIdByName.put(sig, index);
+    membersWithSig.put(fullSig, member);
   }
 
-  private String getJsniSignature(Member member) {
+  private void addMemberIfUnique(String name, List<Member> membersForName) {
+    if (membersForName.size() == 1) {
+      memberById.add(membersForName.get(0));
+      memberIdByName.put(name, memberById.size() - 1);
+    }
+  }
+
+  private List<Member> filterOutSyntheticMembers(Collection<Member> members) {
+    List<Member> nonSynth = new ArrayList<Member>();
+    for (Member member : members) {
+      if (!member.isSynthetic()) {
+        nonSynth.add(member);
+      }
+    }
+    return nonSynth;
+  }
+
+  private LinkedHashMap<String, LinkedHashMap<String, Member>> findMostDerivedMembers(
+      Class<?> targetClass, boolean addConstructors) {
+    LinkedHashMap<String, LinkedHashMap<String, Member>> members = new LinkedHashMap<String, LinkedHashMap<String, Member>>();
+    findMostDerivedMembers(members, targetClass, addConstructors);
+    return members;
+  }
+
+  /**
+   * For each available JSNI reference, find the most derived field or method
+   * that matches it. For wildcard references, there will be more than one of
+   * them, one for each signature matched.
+   */
+  private void findMostDerivedMembers(
+      LinkedHashMap<String, LinkedHashMap<String, Member>> members,
+      Class<?> targetClass, boolean addConstructors) {
+    /*
+     * Analyze superclasses and interfaces first. More derived members will thus
+     * be seen later.
+     */
+    Class<?> superclass = targetClass.getSuperclass();
+    if (superclass != null) {
+      findMostDerivedMembers(members, superclass, false);
+    }
+    for (Class<?> intf : targetClass.getInterfaces()) {
+      findMostDerivedMembers(members, intf, false);
+    }
+
+    if (addConstructors) {
+      for (Constructor<?> ctor : targetClass.getDeclaredConstructors()) {
+        ctor.setAccessible(true);
+        addMember(members, ctor, getJsniSignature(ctor, false));
+        addMember(members, ctor, getJsniSignature(ctor, true));
+      }
+    }
+
+    // Get the methods on this class/interface.
+    for (Method method : targetClass.getDeclaredMethods()) {
+      method.setAccessible(true);
+      addMember(members, method, getJsniSignature(method, false));
+      addMember(members, method, getJsniSignature(method, true));
+    }
+
+    // Get the fields on this class/interface.
+    Field[] fields = targetClass.getDeclaredFields();
+    for (Field field : fields) {
+      field.setAccessible(true);
+      addMember(members, field, field.getName());
+    }
+
+    // Add a magic field to access class literals from JSNI
+    addMember(members, new SyntheticClassMember(targetClass), "class");
+  }
+
+  private String getJsniSignature(Member member, boolean wildcardParamList) {
     String name;
     Class<?>[] paramTypes;
 
-    if (member instanceof Method) {
+    if (member instanceof Field) {
+      return member.getName();
+    } else if (member instanceof SyntheticClassMember) {
+      return member.getName();
+    } else if (member instanceof Method) {
       name = member.getName();
       paramTypes = ((Method) member).getParameterTypes();
     } else if (member instanceof Constructor) {
@@ -89,10 +171,14 @@
     StringBuffer sb = new StringBuffer();
     sb.append(name);
     sb.append("(");
-    for (int i = 0; i < paramTypes.length; ++i) {
-      Class<?> type = paramTypes[i];
-      String typeSig = getTypeSig(type);
-      sb.append(typeSig);
+    if (wildcardParamList) {
+      sb.append(JsniRef.WILDCARD_PARAM_LIST);
+    } else {
+      for (int i = 0; i < paramTypes.length; ++i) {
+        Class<?> type = paramTypes[i];
+        String typeSig = getTypeSig(type);
+        sb.append(typeSig);
+      }
     }
     sb.append(")");
 
@@ -143,57 +229,17 @@
       memberById = new ArrayList<Member>();
       memberById.add(null); // 0 is reserved; it's magic on Win32
       memberIdByName = new HashMap<String, Integer>();
-      lazyInitTargetMembersUsingReflectionHelper(cls, true);
-    }
-  }
 
-  private void lazyInitTargetMembersUsingReflectionHelper(Class<?> targetClass,
-      boolean addConstructors) {
-    // Start by analyzing the superclass recursively; the concrete class will
-    // clobber on overrides.
-    Class<?> superclass = targetClass.getSuperclass();
-    if (superclass != null) {
-      lazyInitTargetMembersUsingReflectionHelper(superclass, false);
-    }
-    for (Class<?> intf : targetClass.getInterfaces()) {
-      lazyInitTargetMembersUsingReflectionHelper(intf, false);
-    }
+      LinkedHashMap<String, LinkedHashMap<String, Member>> members = findMostDerivedMembers(
+          cls, true);
+      for (Entry<String, LinkedHashMap<String, Member>> entry : members.entrySet()) {
+        String name = entry.getKey();
 
-    if (addConstructors) {
-      for (Constructor<?> ctor : targetClass.getDeclaredConstructors()) {
-        ctor.setAccessible(true);
-        String sig = getJsniSignature(ctor);
-        addMember(ctor, sig);
+        List<Member> membersForName = new ArrayList<Member>(
+            entry.getValue().values());
+        addMemberIfUnique(name, membersForName); // backward compatibility
+        addMemberIfUnique(name, filterOutSyntheticMembers(membersForName));
       }
     }
-
-    /*
-     * TODO(mmendez): How should we handle the case where a user writes JSNI
-     * code to interact with an instance that is typed as a particular
-     * interface? Should a user write JSNI code as follows:
-     * 
-     * x.@com.google.gwt.HasFocus::equals(Ljava/lang/Object;)(y)
-     * 
-     * or
-     * 
-     * x.@java.lang.Object::equals(Ljava/lang/Object;)(y)
-     */
-
-    // Get the methods on this class/interface.
-    for (Method method : targetClass.getDeclaredMethods()) {
-      method.setAccessible(true);
-      String sig = getJsniSignature(method);
-      addMember(method, sig);
-    }
-
-    // Get the fields on this class/interface.
-    Field[] fields = targetClass.getDeclaredFields();
-    for (Field field : fields) {
-      field.setAccessible(true);
-      addMember(field, field.getName());
-    }
-
-    // Add a magic field to access class literals from JSNI
-    addMember(new SyntheticClassMember(targetClass), "class");
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/JsniRef.java b/dev/core/src/com/google/gwt/dev/util/JsniRef.java
index 64118bf..7eb7954 100644
--- a/dev/core/src/com/google/gwt/dev/util/JsniRef.java
+++ b/dev/core/src/com/google/gwt/dev/util/JsniRef.java
@@ -25,14 +25,28 @@
  * A parsed Java reference from within a JSNI method.
  */
 public class JsniRef {
+  /**
+   * Special field name for referring to a class literal.
+   */
+  public static final String CLASS = "class";
+
+  /**
+   * Special method name for a class constructor.
+   */
+  public static final String NEW = "new";
+
+  /**
+   * A parameter list indicating a match to any overload.
+   */
+  public static final String WILDCARD_PARAM_LIST = "*";
 
   /**
    * A regex pattern for a Java reference in JSNI code. Its groups are:
    * <ol>
-   * <li> the class name
-   * <li> the field or method name
-   * <li> the method parameter types, including the surrounding parentheses
-   * <li> the method parameter types, excluding the parentheses
+   * <li>the class name
+   * <li>the field or method name
+   * <li>the method parameter types, including the surrounding parentheses
+   * <li>the method parameter types, excluding the parentheses
    * </ol>
    */
   private static Pattern JsniRefPattern = Pattern.compile("@?([^:]+)::([^(]+)(\\((.*)\\))?");
@@ -54,9 +68,11 @@
     String[] paramTypes = null;
     if (matcher.group(3) != null) {
       paramTypesString = matcher.group(4);
-      paramTypes = computeParamTypes(paramTypesString);
-      if (paramTypes == null) {
-        return null;
+      if (!paramTypesString.equals(WILDCARD_PARAM_LIST)) {
+        paramTypes = computeParamTypes(paramTypesString);
+        if (paramTypes == null) {
+          return null;
+        }
       }
     }
     return new JsniRef(className, memberName, paramTypesString, paramTypes);
@@ -112,8 +128,8 @@
   private final String[] paramTypes;
   private final String paramTypesString;
 
-  protected JsniRef(String className, String memberName, String paramTypesString,
-      String[] paramTypes) {
+  protected JsniRef(String className, String memberName,
+      String paramTypesString, String[] paramTypes) {
     this.className = className;
     this.memberName = memberName;
     this.paramTypesString = paramTypesString;
@@ -142,6 +158,14 @@
     return paramTypesString != null;
   }
 
+  /**
+   * Whether this method reference matches all overloads of the specified class
+   * and method name. Only valid for method references.
+   */
+  public boolean matchesAnyOverload() {
+    return paramTypesString.equals(WILDCARD_PARAM_LIST);
+  }
+
   public String memberName() {
     return memberName;
   }
@@ -156,9 +180,11 @@
 
   /**
    * Return the list of parameter types for the method referred to by this
-   * reference.
+   * reference. Only valid for method references where
+   * {@link #matchesAnyOverload()} is false.
    */
   public String[] paramTypes() {
+    assert !matchesAnyOverload();
     return paramTypes;
   }
 
diff --git a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
index 0f19de7..51daf0f 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JsniCheckerTest.java
@@ -589,4 +589,15 @@
           "Referencing field 'Extra.Inner.x': type 'long' is not safe to access in JSNI code");
     }
   }
+
+  public void testWildcardRef() {
+    StringBuffer code = new StringBuffer();
+    code.append("class Buggy {\n");
+    code.append("  int m(String x) { return -1; }\n");
+    code.append("  native void jsniMeth() /*-{\n");
+    code.append("    $wnd.alert(this.@Buggy::m(*)(\"hello\")); }-*/;\n");
+    code.append("}\n");
+
+    shouldGenerateNoError(code);
+  }
 }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java
new file mode 100644
index 0000000..ad63932
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2009 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.jjs.impl;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.jjs.ast.HasEnclosingType;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.JsniRefLookup.ErrorReporter;
+import com.google.gwt.dev.util.JsniRef;
+
+/**
+ * Tests class {@link JsniRefLookup}.
+ */
+public class JsniRefLookupTest extends OptimizerTestBase {
+  private class MockErrorReporter implements ErrorReporter {
+    private String error = null;
+
+    public void assertHasError() {
+      assertTrue("Expected a lookup failure", error != null);
+    }
+
+    public void assertNoError() {
+      assertTrue("Unexpected error: " + error, error == null);
+    }
+
+    public void reportError(String error) {
+      this.error = error;
+    }
+  }
+
+  private JProgram program;
+
+  @Override
+  public void setUp() {
+    sourceOracle.addOrReplace(new MockJavaResource("test.Intf") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;\n");
+        code.append("public interface Intf {\n");
+        code.append("  public int addTwoOverloaded(int x);\n");
+        code.append("  public int addOne(int x);\n");
+        code.append("  public int foo(int x);\n");
+        code.append("  public double foo(double x);\n");
+        code.append("}\n");
+        return code;
+      }
+    });
+
+    sourceOracle.addOrReplace(new MockJavaResource("test.Foo") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;\n");
+        code.append("public class Foo implements Intf {\n");
+        code.append("  public Foo() { }\n");
+        code.append("  public Foo(int x) { }\n");
+        code.append("  public static int intStatic;\n");
+        code.append("  public int intInstance;\n");
+        code.append("  public int addOne(int x) { return x+1; }\n");
+        code.append("  public int addTwoOverloaded(int x) { return x+2; }\n");
+        code.append("  public double addTwoOverloaded(double x) { return x+2; }\n");
+        code.append("  public int foo(int x) { return x+1; }\n");
+        code.append("  public double foo(double x) { return x+1; }\n");
+        code.append("  public int bar(int x) { return x+1; }\n");
+        code.append("}\n");
+        return code;
+      }
+    });
+
+    sourceOracle.addOrReplace(new MockJavaResource("test.Bar") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;\n");
+        code.append("public class Bar extends Foo {\n");
+        code.append("  public Bar() { }\n");
+        code.append("  public int foo(int x) { return x+1; }\n");
+        code.append("  public int bar(int x) { return x+1; }\n");
+        code.append("  public double bar(double x) { return x+1; }\n");
+        code.append("}\n");
+        return code;
+      }
+    });
+
+    sourceOracle.addOrReplace(new MockJavaResource("test.GenericClass") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;\n");
+        code.append("public abstract class GenericClass<T> {\n");
+        code.append("  abstract void set(T x);\n");
+        code.append("}\n");
+        return code;
+      }
+    });
+
+    sourceOracle.addOrReplace(new MockJavaResource("test.ClassWithBridge") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;\n");
+        code.append("class ClassWithBridge extends GenericClass<String> {\n");
+        code.append("  void set(String x) { }\n");
+        code.append("}\n");
+        return code;
+      }
+    });
+
+    try {
+      // The snippet must reference the classes so they will be compiled in
+      program = compileSnippet("void",
+          "new test.Foo(); new test.Bar(); new ClassWithBridge();");
+    } catch (UnableToCompleteException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public void testBasicLookups() {
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JField res = (JField) lookup("test.Foo::intStatic", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("intStatic", res.getName());
+      assertTrue(res.isStatic());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JField res = (JField) lookup("test.Foo::intInstance", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("intInstance", res.getName());
+      assertFalse(res.isStatic());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::addOne(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addOne", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::addTwoOverloaded(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addTwoOverloaded", res.getName());
+      assertEquals(JPrimitiveType.INT, res.getParams().get(0).getType());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::addTwoOverloaded(D)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addTwoOverloaded", res.getName());
+      assertEquals(JPrimitiveType.DOUBLE, res.getParams().get(0).getType());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::new()", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("new", res.getName());
+    }
+
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Foo::bogoField", errors);
+      errors.assertHasError();
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Foo::bogoMethod()", errors);
+      errors.assertHasError();
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Foo::new(J)", errors);
+      errors.assertHasError();
+    }
+  }
+
+  public void testBridgeMethods() {
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup(
+          "test.ClassWithBridge::set(Ljava/lang/String;)", errors);
+      errors.assertNoError();
+      assertEquals("test.ClassWithBridge", res.getEnclosingType().getName());
+      assertEquals("set", res.getName());
+      assertEquals("java.lang.String",
+          res.getParams().get(0).getType().getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.ClassWithBridge::set(*)", errors);
+      errors.assertNoError();
+      assertEquals("test.ClassWithBridge", res.getEnclosingType().getName());
+      assertEquals("set", res.getName());
+      assertEquals("java.lang.String",
+          res.getParams().get(0).getType().getName());
+    }
+    {
+      // For backward compatibility, allow calling a bridge method directly
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup(
+          "test.ClassWithBridge::set(Ljava/lang/Object;)", errors);
+      errors.assertNoError();
+      assertEquals("test.ClassWithBridge", res.getEnclosingType().getName());
+      assertEquals("set", res.getName());
+      assertEquals("java.lang.Object",
+          res.getParams().get(0).getType().getName());
+    }
+  }
+
+  public void testConstructors() {
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::new()", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("new", res.getName());
+      assertEquals(0, res.getParams().size());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::new(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("new", res.getName());
+      assertEquals(1, res.getParams().size());
+      assertSame(JPrimitiveType.INT, res.getParams().get(0).getType());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      lookup("test.Foo::new(*)", errors);
+      errors.assertHasError();
+    }
+
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::new()", errors);
+      errors.assertNoError();
+      assertEquals("test.Bar", res.getEnclosingType().getName());
+      assertEquals("new", res.getName());
+      assertEquals(0, res.getParams().size());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::new(*)", errors);
+      errors.assertNoError();
+      assertEquals("test.Bar", res.getEnclosingType().getName());
+      assertEquals("new", res.getName());
+      assertEquals(0, res.getParams().size());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      lookup("test.Bar::new(I)", errors);
+      errors.assertHasError();
+    }
+  }
+
+  public void testInheritance() {
+    // test lookups of methods where the subtype is specified
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::addOne(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addOne", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::addTwoOverloaded(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addTwoOverloaded", res.getName());
+      assertEquals(JPrimitiveType.INT, res.getParams().get(0).getType());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::addTwoOverloaded(D)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addTwoOverloaded", res.getName());
+      assertEquals(JPrimitiveType.DOUBLE, res.getParams().get(0).getType());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::addOne(*)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addOne", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Bar::addTwoOverloaded(*)", errors);
+      errors.assertHasError();
+    }
+
+    /*
+     * test wildcard lookups when the subtype overloads but the supertype does
+     * not, and vice versa
+     */
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::foo(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Bar", res.getEnclosingType().getName());
+      assertEquals("foo", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::bar(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Bar", res.getEnclosingType().getName());
+      assertEquals("bar", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Bar::bar(D)", errors);
+      errors.assertNoError();
+      assertEquals("test.Bar", res.getEnclosingType().getName());
+      assertEquals("bar", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Bar::foo(*)", errors);
+      errors.assertHasError();
+    }
+  }
+
+  public void testInterfaces() {
+    // Test lookups in the interface that specify the types
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::addTwoOverloaded(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Intf", res.getEnclosingType().getName());
+      assertEquals("addTwoOverloaded", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::addOne(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Intf", res.getEnclosingType().getName());
+      assertEquals("addOne", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::foo(I)", errors);
+      errors.assertNoError();
+      assertEquals("test.Intf", res.getEnclosingType().getName());
+      assertEquals("foo", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::foo(D)", errors);
+      errors.assertNoError();
+      assertEquals("test.Intf", res.getEnclosingType().getName());
+      assertEquals("foo", res.getName());
+    }
+
+    // Test lookups that use wildcards
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::addTwoOverloaded(*)", errors);
+      errors.assertNoError();
+      assertEquals("test.Intf", res.getEnclosingType().getName());
+      assertEquals("addTwoOverloaded", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::addOne(*)", errors);
+      errors.assertNoError();
+      assertEquals("test.Intf", res.getEnclosingType().getName());
+      assertEquals("addOne", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Intf::foo(*)", errors);
+      errors.assertHasError();
+    }
+  }
+
+  public void testWildcardLookups() {
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      JMethod res = (JMethod) lookup("test.Foo::addOne(*)", errors);
+      errors.assertNoError();
+      assertEquals("test.Foo", res.getEnclosingType().getName());
+      assertEquals("addOne", res.getName());
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Foo::addTwoOverloaded(*)", errors);
+      errors.assertHasError();
+    }
+    {
+      MockErrorReporter errors = new MockErrorReporter();
+      HasEnclosingType res = lookup("test.Foo::bogoMethod(*)", errors);
+      errors.assertHasError();
+    }
+  }
+
+  private HasEnclosingType lookup(String refString, MockErrorReporter errors) {
+    return JsniRefLookup.findJsniRefTarget(JsniRef.parse(refString), program,
+        errors);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/js/TokenStreamTest.java b/dev/core/test/com/google/gwt/dev/js/TokenStreamTest.java
new file mode 100644
index 0000000..9f8db11
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/js/TokenStreamTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2009 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.js;
+
+import com.google.gwt.dev.js.rhino.EvaluatorException;
+import com.google.gwt.dev.js.rhino.TokenStream;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+/**
+ * Tests class {@link TokenStream}.
+ */
+public class TokenStreamTest extends TestCase {
+  private static class Token {
+    final String text;
+    final int type;
+
+    public Token(int type, String text) {
+      this.type = type;
+      this.text = text;
+    }
+  }
+
+  private static Token scanToken(String tokenString) throws EvaluatorException,
+      IOException {
+    TokenStream tokStream = new TokenStream(new StringReader(tokenString),
+        "test input", 1);
+    int type = tokStream.getToken();
+    String text = tokStream.getString();
+    return new Token(type, text);
+  }
+
+  public void testJsniRefs() throws IOException {
+    // Field refs
+    assertGoodJsni("@org.group.Foo::bar");
+
+    // Method refs
+    assertGoodJsni("@org.group.Foo::bar()");
+    assertGoodJsni("@org.group.Foo::bar(I)");
+    assertGoodJsni("@org.group.Foo::bar(IJ)");
+    assertGoodJsni("@org.group.Foo::bar(Lorg/group/Foo;)");
+    assertGoodJsni("@org.group.Foo::bar([I)");
+    // The following is currently tolerated
+    // assertBadJsni("@org.group.Foo::bar(Lorg/group/Foo)");
+    assertBadJsni("@org.group.Foo::bar(A)");
+    assertBadJsni("@org.group.Foo::bar(L)");
+
+    // Method refs with * as the parameter list
+    assertGoodJsni("@org.group.Foo::bar(*)");
+    assertBadJsni("@org.group.Foo::bar(*");
+
+    // bad references
+    assertBadJsni("@");
+    assertBadJsni("@org.group.Foo.bar");
+    assertBadJsni("@org.group.Foo:");
+    assertBadJsni("@org.group.Foo::");
+    assertBadJsni("@org.group.Foo::(");
+  }
+
+  private void assertBadJsni(String token) throws IOException {
+    try {
+      scanToken(token);
+      fail("Expected a token scanning error for " + token);
+    } catch (EvaluatorException e) {
+      // expected
+    }
+  }
+
+  private void assertGoodJsni(String jsniRef) throws IOException {
+    Token token = scanToken(jsniRef);
+    assertEquals(TokenStream.NAME, token.type);
+    assertEquals(jsniRef, token.text);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/DispatchClassInfoTest.java b/dev/core/test/com/google/gwt/dev/shell/DispatchClassInfoTest.java
new file mode 100644
index 0000000..4ea1a9c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/shell/DispatchClassInfoTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2009 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;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+
+/**
+ * Tests DispatchClassInfoTest.
+ */
+public class DispatchClassInfoTest extends TestCase {
+  /**
+   * Used in {@link #testInterface()}.
+   */
+  private interface Intf {
+    void bar(double x);
+
+    void bar(int x);
+
+    void foo(int x);
+  }
+
+  public void testBasics() {
+    @SuppressWarnings("unused")
+    class Foo {
+      int field;
+
+      void nonOverloaded(int x) {
+      }
+
+      void overloaded(double x) {
+      }
+
+      void overloaded(int x) {
+      }
+    }
+
+    DispatchClassInfo dci = new DispatchClassInfo(Foo.class, 42);
+    assertField(dci, "field", "field");
+    assertNonExistent(dci, "bogofield");
+    assertMethod(dci, "nonOverloaded(I)", Foo.class, "nonOverloaded",
+        Integer.TYPE);
+    assertMethod(dci, "nonOverloaded(*)", Foo.class, "nonOverloaded",
+        Integer.TYPE);
+    assertMethod(dci, "overloaded(I)", Foo.class, "overloaded", Integer.TYPE);
+    assertMethod(dci, "overloaded(D)", Foo.class, "overloaded", Double.TYPE);
+    assertNonExistent(dci, "overloaded(*)");
+    assertNonExistent(dci, "bogometh(I)");
+    assertNonExistent(dci, "bogometh(*)");
+  }
+
+  public void testInheritance() {
+    /*
+     * In these two classes, foo is overloaded in the superclass but not the
+     * subclass, and bar vice versa.
+     */
+    @SuppressWarnings("unused")
+    class Super {
+      int field;
+
+      void bar(int x) {
+      }
+
+      void foo(double x) {
+      }
+
+      void foo(int x) {
+      }
+
+      void nonover(int x) {
+      }
+
+      void over(double x) {
+      }
+
+      void over(int x) {
+      }
+    }
+
+    @SuppressWarnings("unused")
+    class Sub extends Super {
+      void bar(double x) {
+      }
+
+      @Override
+      void bar(int x) {
+      }
+
+      @Override
+      void foo(int x) {
+      }
+    }
+
+    DispatchClassInfo dci = new DispatchClassInfo(Sub.class, 42);
+
+    assertField(dci, "field", "field");
+    assertMethod(dci, "foo(I)", Sub.class, "foo", Integer.TYPE);
+    assertMethod(dci, "bar(I)", Sub.class, "bar", Integer.TYPE);
+    assertMethod(dci, "bar(D)", Sub.class, "bar", Double.TYPE);
+    assertNonExistent(dci, "foo(*)");
+    assertNonExistent(dci, "bar(*)");
+
+    assertMethod(dci, "nonover(I)", Super.class, "nonover", Integer.TYPE);
+    assertMethod(dci, "nonover(*)", Super.class, "nonover", Integer.TYPE);
+
+    assertMethod(dci, "over(I)", Super.class, "over", Integer.TYPE);
+    assertMethod(dci, "over(D)", Super.class, "over", Double.TYPE);
+    assertNonExistent(dci, "over(*)");
+  }
+
+  public void testInterface() {
+    DispatchClassInfo dci = new DispatchClassInfo(Intf.class, 42);
+
+    assertMethod(dci, "foo(I)", Intf.class, "foo", Integer.TYPE);
+    assertMethod(dci, "foo(*)", Intf.class, "foo", Integer.TYPE);
+    assertMethod(dci, "bar(I)", Intf.class, "bar", Integer.TYPE);
+    assertMethod(dci, "bar(D)", Intf.class, "bar", Double.TYPE);
+    assertNonExistent(dci, "bar(*)");
+  }
+
+  /**
+   * Test that bridge methods are ignored for wildcard lookups.
+   */
+  public void testBridgeMethod() {
+    @SuppressWarnings("unused")
+    abstract class Super<T> {
+      abstract void set(T x);
+    }
+
+    class Sub extends Super<String> {
+      @Override
+      void set(String x) {
+      }
+    }
+
+    DispatchClassInfo dci = new DispatchClassInfo(Sub.class, 42);
+
+    assertMethod(dci, "set(Ljava/lang/String;)", Sub.class, "set", String.class);
+    assertMethod(dci, "set(*)", Sub.class, "set", String.class);
+    
+    // For backward compatibility, allow calling a bridge method directly
+    assertMethod(dci, "set(Ljava/lang/Object;)", Sub.class, "set", Object.class);
+  }
+
+  private void assertField(DispatchClassInfo dci, String ref, String fieldName) {
+    Member member = lookupMember(dci, ref);
+    Field field = (Field) member;
+    assertEquals(fieldName, field.getName());
+  }
+
+  private void assertMethod(DispatchClassInfo dci, String ref,
+      Class<?> methodClass, String methodName, Class<?> paramType) {
+    Member member = lookupMember(dci, ref);
+    Method method = (Method) member;
+    assertSame(methodClass, member.getDeclaringClass());
+    assertEquals(methodName, method.getName());
+    assertEquals(1, method.getParameterTypes().length);
+    assertEquals(paramType, method.getParameterTypes()[0]);
+  }
+
+  private void assertNonExistent(DispatchClassInfo dci, String badref) {
+    int handle = dci.getMemberId(badref);
+    assertTrue("expected to be a bad reference: " + badref, handle < 0);
+  }
+
+  private Member lookupMember(DispatchClassInfo dci, String ref) {
+    int handle = dci.getMemberId(ref);
+    assertTrue("ref lookup failed: " + ref, handle >= 0);
+    Member member = dci.getMember(handle);
+    return member;
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java b/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java
index 5dddc44..88498c1 100644
--- a/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java
+++ b/dev/core/test/com/google/gwt/dev/util/JsniRefTest.java
@@ -19,7 +19,6 @@
 
 /**
  * Tests the {@link JsniRef} class.
- * 
  */
 public class JsniRefTest extends TestCase {
   public static void testBasics() {
@@ -39,6 +38,7 @@
       assertEquals("someMeth()", ref.memberSignature());
       assertTrue(ref.isMethod());
       assertFalse(ref.isField());
+      assertFalse(ref.matchesAnyOverload());
       assertEquals(0, ref.paramTypes().length);
     }
 
@@ -51,6 +51,7 @@
       assertEquals("someMeth([[ZBCDFIJLjava/lang/String;S)",
           ref.memberSignature());
       assertTrue(ref.isMethod());
+      assertFalse(ref.matchesAnyOverload());
       assertEquals(9, ref.paramTypes().length);
       assertEquals("[[Z", ref.paramTypes()[0]);
       assertEquals("B", ref.paramTypes()[1]);
@@ -64,6 +65,21 @@
     }
 
     {
+      // Test with a wildcard parameter list
+      JsniRef ref = JsniRef.parse("@some.package.SomeClass::someMeth(*)");
+      assertEquals("some.package.SomeClass", ref.className());
+      assertEquals("someMeth", ref.memberName());
+      assertTrue(ref.isMethod());
+      assertTrue(ref.matchesAnyOverload());
+    }
+
+    {
+      // test some badly formatted wildcard strings
+      assertNull(JsniRef.parse("@some.package.SomeClass::someMeth(*"));
+      assertNull(JsniRef.parse("@some.package.SomeClass::someMeth(I*)"));
+    }
+
+    {
       // test with no preceding at sign
       JsniRef ref = JsniRef.parse("some.package.SomeClass::someField");
       assertEquals("some.package.SomeClass", ref.className());
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 3cba452..1c6d9cf 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -31,6 +31,7 @@
 import com.google.gwt.dev.jjs.test.InnerClassTest;
 import com.google.gwt.dev.jjs.test.InnerOuterSuperTest;
 import com.google.gwt.dev.jjs.test.JStaticEvalTest;
+import com.google.gwt.dev.jjs.test.JavaAccessFromJavaScriptTest;
 import com.google.gwt.dev.jjs.test.JsStaticEvalTest;
 import com.google.gwt.dev.jjs.test.JsniConstructorTest;
 import com.google.gwt.dev.jjs.test.JsoTest;
@@ -75,6 +76,7 @@
     suite.addTestSuite(InitialLoadSequenceTest.class);
     suite.addTestSuite(InnerClassTest.class);
     suite.addTestSuite(InnerOuterSuperTest.class);
+    suite.addTestSuite(JavaAccessFromJavaScriptTest.class);
     suite.addTestSuite(JsniConstructorTest.class);
     suite.addTestSuite(JsoTest.class);
     suite.addTestSuite(JsStaticEvalTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/test/JavaAccessFromJavaScriptTest.java b/user/test/com/google/gwt/dev/jjs/test/JavaAccessFromJavaScriptTest.java
new file mode 100644
index 0000000..f70201f
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/JavaAccessFromJavaScriptTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2009 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.jjs.test;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests JSNI references in their main use of accessing Java code from
+ * JavaScript.
+ */
+public class JavaAccessFromJavaScriptTest extends GWTTestCase {
+  private static class Adder implements HasAddOne {
+    /**
+     * This overloaded version is present in the class but not the interface.
+     */
+    public double addOne(double x) {
+      return x + 1.01;
+    }
+
+    public int addOne(int x) {
+      return x + 1;
+    }
+  }
+
+  private interface HasAddOne {
+    int addOne(int x);
+  }
+
+  public static int addOne(int x) {
+    return x + 1;
+  }
+
+  /**
+   * Accesses the virtual addOne() method.
+   */
+  public static native int useAddOne(HasAddOne adder, int y) /*-{
+    return adder.@com.google.gwt.dev.jjs.test.JavaAccessFromJavaScriptTest.HasAddOne::addOne(I)(y);
+  }-*/;
+
+  /**
+   * Accesses the static addOne() method.
+   */
+  public static native int useAddOne(int y) /*-{
+    return @com.google.gwt.dev.jjs.test.JavaAccessFromJavaScriptTest::addOne(I)(y);
+  }-*/;
+
+  /**
+   * Accesses the virtual addOne() method via the wildcard notation to specify a non-overloaded
+   * method.
+   */
+  public static native int useAddOneUsingWildcard(HasAddOne adder, int y) /*-{
+    return adder.@com.google.gwt.dev.jjs.test.JavaAccessFromJavaScriptTest.HasAddOne::addOne(*)(y);
+  }-*/;
+
+  /**
+   * Accesses the addOne() method via the wildcard notation to specify a non-overloaded
+   * method.
+   */
+  public static native int useAddOneUsingWildcard(int y) /*-{
+    return @com.google.gwt.dev.jjs.test.JavaAccessFromJavaScriptTest::addOne(*)(y);
+  }-*/;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.CompilerSuite";
+  }
+
+  public void testBasics() {
+    assertEquals(11, useAddOne(10));
+    assertEquals(11, useAddOneUsingWildcard(10));
+  }
+
+  public void testInterface() {
+    HasAddOne adder = new Adder();
+    assertEquals(11, useAddOne(adder, 10));
+    assertEquals(11, useAddOneUsingWildcard(adder, 10));
+  }
+}