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