| /* |
| * 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.dev.jjs.ast.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JConstructor; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JField; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.util.JsniRef; |
| |
| 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}. |
| * |
| * @deprecated find alternatives, only a couple of corner cases use this now. |
| */ |
| @Deprecated |
| public class JsniRefLookup { |
| /** |
| * A callback used to indicate the reason for a failed JSNI lookup. |
| */ |
| public interface ErrorReporter { |
| void reportError(String error); |
| } |
| |
| /** |
| * Look up a JSNI reference. |
| * |
| * @param ref The reference to look up |
| * @param program The program to look up the reference in |
| * @param errorReporter A callback used to indicate the reason for a failed |
| * JSNI lookup |
| * @return The item referred to, or <code>null</code> if it could not be |
| * found. If the return value is <code>null</code>, |
| * <code>errorReporter</code> will have been invoked. |
| */ |
| public static JNode findJsniRefTarget(JsniRef ref, JProgram program, |
| JsniRefLookup.ErrorReporter errorReporter) { |
| String className = ref.className(); |
| JType type = program.getTypeFromJsniRef(className); |
| |
| if (type == null) { |
| errorReporter.reportError("Unresolvable native reference to type '" + className + "'"); |
| return null; |
| } |
| |
| if (!ref.isMethod()) { |
| // look for a field |
| String fieldName = ref.memberName(); |
| |
| if (fieldName.equals(JsniRef.CLASS)) { |
| return type; |
| |
| } else if (type.isPrimitiveType()) { |
| errorReporter.reportError("May not refer to fields on primitive types"); |
| return null; |
| |
| } else if (type instanceof JArrayType) { |
| errorReporter.reportError("May not refer to fields on array types"); |
| return null; |
| |
| } else { |
| for (JField field : ((JDeclaredType) type).getFields()) { |
| if (field.getName().equals(fieldName)) { |
| return field; |
| } |
| } |
| } |
| |
| errorReporter.reportError("Unresolvable native reference to field '" + fieldName |
| + "' in type '" + className + "'"); |
| return null; |
| } else if (type.isPrimitiveType()) { |
| errorReporter.reportError("May not refer to methods on primitive types"); |
| return null; |
| } else { |
| // look for a method |
| LinkedHashMap<String, LinkedHashMap<String, JMethod>> matchesBySig = |
| new LinkedHashMap<String, LinkedHashMap<String, JMethod>>(); |
| String methodName = ref.memberName(); |
| String jsniSig = ref.memberSignature(); |
| findMostDerivedMembers(matchesBySig, (JDeclaredType) type, methodName, true); |
| LinkedHashMap<String, JMethod> 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(); |
| } |
| |
| // 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 = ""; |
| // 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 = ", "; |
| } |
| errorReporter.reportError("Unresolvable native reference to method '" + methodName |
| + "' in type '" + className + "' (did you mean " + suggestList.toString() + "?)"); |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Add a member to the table of most derived members. |
| * |
| * @param matchesBySig The table so far of most derived members |
| * @param member The member to add to it |
| * @param refSig The string used to refer to that member, possibly shortened |
| * @param fullSig The fully qualified signature for that member |
| */ |
| private static void addMember(LinkedHashMap<String, LinkedHashMap<String, JMethod>> matchesBySig, |
| JMethod member, String refSig, String fullSig) { |
| LinkedHashMap<String, JMethod> matchesByFullSig = matchesBySig.get(refSig); |
| if (matchesByFullSig == null) { |
| matchesByFullSig = new LinkedHashMap<String, JMethod>(); |
| matchesBySig.put(refSig, matchesByFullSig); |
| } |
| 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, JMethod>> matchesBySig, JDeclaredType targetType, |
| String memberName, boolean addConstructorsAndPrivates) { |
| /* |
| * 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 instanceof JConstructor && JsniRef.NEW.equals(memberName)) { |
| if (!addConstructorsAndPrivates) { |
| continue; |
| } |
| StringBuilder sb = new StringBuilder(); |
| sb.append(JsniRef.NEW); |
| sb.append("("); |
| for (JParameter param : method.getParams()) { |
| sb.append(param.getType().getJsniSignatureName()); |
| } |
| sb.append(")"); |
| String fullSig = sb.toString(); |
| String wildcardSig = JsniRef.NEW + "(" + JsniRef.WILDCARD_PARAM_LIST + ")"; |
| addMember(matchesBySig, method, fullSig, fullSig); |
| addMember(matchesBySig, method, wildcardSig, fullSig); |
| } else if (method.getName().equals(memberName)) { |
| if (!addConstructorsAndPrivates && method.isPrivate()) { |
| continue; |
| } |
| String fullSig = method.getJsniSignature(false, false); |
| String wildcardSig = method.getName() + "(" + JsniRef.WILDCARD_PARAM_LIST + ")"; |
| addMember(matchesBySig, method, fullSig, fullSig); |
| addMember(matchesBySig, method, wildcardSig, fullSig); |
| } |
| } |
| } |
| |
| private static void removeSyntheticMembers( |
| LinkedHashMap<String, LinkedHashMap<String, JMethod>> matchesBySig) { |
| for (LinkedHashMap<String, JMethod> matchesByFullSig : matchesBySig.values()) { |
| Set<String> toRemove = new LinkedHashSet<String>(); |
| for (String fullSig : matchesByFullSig.keySet()) { |
| JMethod member = matchesByFullSig.get(fullSig); |
| if (member.isSynthetic()) { |
| toRemove.add(fullSig); |
| } |
| } |
| for (String fullSig : toRemove) { |
| matchesByFullSig.remove(fullSig); |
| } |
| } |
| } |
| } |