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));
+ }
+}