/**
   * 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());
      }
    }
  }
 /** Constructed by {@link MemberFactory#get(JFieldType)}. */
 public StandardFieldMember(MemberFactory factory, JField field) {
   this.enclosing = factory.get(field.getEnclosingType());
   this.sourceName = field.getEnclosingType().getName() + "::" + field.getName();
 }
  /**
   * 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 HasEnclosingType findJsniRefTarget(
      JsniRef ref, JProgram program, JsniRefLookup.ErrorReporter errorReporter) {
    String className = ref.className();
    JType type = null;
    if (!className.equals("null")) {
      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 (type == null) {
        if (fieldName.equals("nullField")) {
          return program.getNullField();
        }

      } else if (fieldName.equals(JsniRef.CLASS)) {
        JClassLiteral lit = program.getLiteralClass(type);
        return lit.getField();

      } else if (type instanceof JPrimitiveType) {
        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 instanceof JPrimitiveType) {
      errorReporter.reportError("May not refer to methods on primitive types");
      return null;
    } else {
      // look for a method
      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig =
          new LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>>();
      String methodName = ref.memberName();
      String jsniSig = ref.memberSignature();
      if (type == null) {
        if (jsniSig.equals("nullMethod()")) {
          return program.getNullMethod();
        }
      } else {
        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();
        }
      }

      // 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;
      }
    }
  }