// changed API of IBinaryMethod (between Eclipse 4.5 and Eclipse 4.6)
  // therefore adapting to this via reflection to use the correct existing method
  private static IBinaryAnnotation[] getParameterAnnotation(
      IBinaryMethod newMethod, int i, char[] fileName) {
    IBinaryAnnotation[] result = null;

    // try the old method first
    try {

      try {
        Method getParameterAnnotationsMethod =
            newMethod.getClass().getMethod("getParameterAnnotations", int.class);
        if (getParameterAnnotationsMethod != null) {
          getParameterAnnotationsMethod.setAccessible(true);
          result = (IBinaryAnnotation[]) getParameterAnnotationsMethod.invoke(newMethod, i);
        }
      } catch (NoSuchMethodException e) {

        // if the old method is not there, try the new one
        Method getParameterAnnotationsMethod =
            newMethod.getClass().getMethod("getParameterAnnotations", int.class, char[].class);
        if (getParameterAnnotationsMethod != null) {
          getParameterAnnotationsMethod.setAccessible(true);
          result =
              (IBinaryAnnotation[]) getParameterAnnotationsMethod.invoke(newMethod, i, fileName);
        }
      }

    } catch (Exception e) {
      SpringCore.log(e);
    }

    return result;
  }
  private static boolean parameterAnnotationsEquals(
      IBinaryMethod newMethod, IBinaryMethod existingMethod, char[] fileName, int flags) {

    char[][] argumentNames = newMethod.getArgumentNames();
    char[][] existingArgumentNames = existingMethod.getArgumentNames();

    if (argumentNames == null && existingArgumentNames == null) return true;

    int argumentCount = argumentNames != null ? argumentNames.length : 0;
    int existingArgumentCount = existingArgumentNames != null ? existingArgumentNames.length : 0;

    if (argumentCount != existingArgumentCount) return false;

    for (int i = 0; i < argumentCount; i++) {
      IBinaryAnnotation[] parameterAnnotations = getParameterAnnotation(newMethod, i, fileName);
      IBinaryAnnotation[] existingParameterAnnotations =
          getParameterAnnotation(existingMethod, i, fileName);

      if (!annotationsEqual(parameterAnnotations, existingParameterAnnotations, flags)) {
        return false;
      }
    }

    return true;
  }
  boolean matchMethod(MethodPattern pattern, Object binaryInfo, IBinaryType enclosingBinaryType) {
    if (!pattern.findDeclarations) return false; // only relevant when finding declarations
    if (!(binaryInfo instanceof IBinaryMethod)) return false;

    IBinaryMethod method = (IBinaryMethod) binaryInfo;
    if (!pattern.matchesName(pattern.selector, method.getSelector())) return false;
    if (!checkDeclaringType(
        enclosingBinaryType,
        pattern.declaringSimpleName,
        pattern.declaringQualification,
        pattern.isCaseSensitive(),
        pattern.isCamelCase())) return false;

    // look at return type only if declaring type is not specified
    boolean checkReturnType =
        pattern.declaringSimpleName == null
            && (pattern.returnSimpleName != null || pattern.returnQualification != null);
    boolean checkParameters = pattern.parameterSimpleNames != null;
    if (checkReturnType || checkParameters) {
      char[] methodDescriptor = convertClassFileFormat(method.getMethodDescriptor());
      if (checkReturnType) {
        char[] returnTypeSignature =
            Signature.toCharArray(Signature.getReturnType(methodDescriptor));
        if (!checkTypeName(
            pattern.returnSimpleName,
            pattern.returnQualification,
            returnTypeSignature,
            pattern.isCaseSensitive(),
            pattern.isCamelCase())) return false;
      }
      if (checkParameters
          && !checkParameters(
              methodDescriptor,
              pattern.parameterSimpleNames,
              pattern.parameterQualifications,
              pattern.isCaseSensitive(),
              pattern.isCamelCase())) return false;
    }
    return true;
  }
  boolean matchConstructor(
      ConstructorPattern pattern, Object binaryInfo, IBinaryType enclosingBinaryType) {
    if (!pattern.findDeclarations) return false; // only relevant when finding declarations
    if (!(binaryInfo instanceof IBinaryMethod)) return false;

    IBinaryMethod method = (IBinaryMethod) binaryInfo;
    if (!method.isConstructor()) return false;
    if (!checkDeclaringType(
        enclosingBinaryType,
        pattern.declaringSimpleName,
        pattern.declaringQualification,
        pattern.isCaseSensitive(),
        pattern.isCamelCase())) return false;
    if (pattern.parameterSimpleNames != null) {
      char[] methodDescriptor = convertClassFileFormat(method.getMethodDescriptor());
      if (!checkParameters(
          methodDescriptor,
          pattern.parameterSimpleNames,
          pattern.parameterQualifications,
          pattern.isCaseSensitive(),
          pattern.isCamelCase())) return false;
    }
    return true;
  }
  private boolean hasStructuralChanges(
      ClassFileReader reader, TypeStructure existingType, int flags) {
    if (existingType == null) {
      return true;
    }

    // modifiers
    if (!modifiersEqual(reader.getModifiers(), existingType.modifiers)) {
      return true;
    }

    // generic signature
    if (!CharOperation.equals(reader.getGenericSignature(), existingType.genericSignature)) {
      return true;
    }

    // superclass name
    if (!CharOperation.equals(reader.getSuperclassName(), existingType.superclassName)) {
      return true;
    }

    // class level annotations
    if ((flags & FLAG_ANNOTATION) != 0) {
      IBinaryAnnotation[] existingAnnotations = existingType.getAnnotations();
      IBinaryAnnotation[] newAnnotations = reader.getAnnotations();
      if (!annotationsEqual(existingAnnotations, newAnnotations, flags)) {
        return true;
      }
    }

    // tag bits; standard annotations like @Deprecated
    if (reader.getTagBits() != existingType.getTagBits()) {
      return true;
    }

    // interfaces
    char[][] existingIfs = existingType.interfaces;
    char[][] newIfsAsChars = reader.getInterfaceNames();
    if (newIfsAsChars == null) {
      newIfsAsChars = EMPTY_CHAR_ARRAY;
    } // damn I'm lazy...
    if (existingIfs == null) {
      existingIfs = EMPTY_CHAR_ARRAY;
    }
    if (existingIfs.length != newIfsAsChars.length) return true;
    new_interface_loop:
    for (int i = 0; i < newIfsAsChars.length; i++) {
      for (int j = 0; j < existingIfs.length; j++) {
        if (CharOperation.equals(existingIfs[j], newIfsAsChars[i])) {
          continue new_interface_loop;
        }
      }
      return true;
    }

    // fields
    IBinaryField[] newFields = reader.getFields();
    if (newFields == null) {
      newFields = TypeStructure.NoField;
    }

    IBinaryField[] existingFs = existingType.binFields;
    if (newFields.length != existingFs.length) return true;
    new_field_loop:
    for (int i = 0; i < newFields.length; i++) {
      IBinaryField field = newFields[i];
      char[] fieldName = field.getName();
      for (int j = 0; j < existingFs.length; j++) {
        if (CharOperation.equals(existingFs[j].getName(), fieldName)) {
          if (!modifiersEqual(field.getModifiers(), existingFs[j].getModifiers())) {
            return true;
          }
          if (!CharOperation.equals(existingFs[j].getTypeName(), field.getTypeName())) {
            return true;
          }
          if ((flags & FLAG_ANNOTATION) != 0) {
            if (!annotationsEqual(field.getAnnotations(), existingFs[j].getAnnotations(), flags)) {
              return true;
            }
          }
          continue new_field_loop;
        }
      }
      return true;
    }

    // methods
    IBinaryMethod[] newMethods = reader.getMethods();
    if (newMethods == null) {
      newMethods = TypeStructure.NoMethod;
    }

    char[] fileName = reader.getFileName();

    IBinaryMethod[] existingMs = existingType.binMethods;
    if (newMethods.length != existingMs.length) return true;
    new_method_loop:
    for (int i = 0; i < newMethods.length; i++) {
      IBinaryMethod method = newMethods[i];
      char[] methodName = method.getSelector();
      for (int j = 0; j < existingMs.length; j++) {
        if (CharOperation.equals(existingMs[j].getSelector(), methodName)) {
          // candidate match
          if (!CharOperation.equals(
              method.getMethodDescriptor(), existingMs[j].getMethodDescriptor())) {
            continue; // might be overloading
          } else {
            // matching sigs
            if (!modifiersEqual(method.getModifiers(), existingMs[j].getModifiers())) {
              return true;
            }
            if ((flags & FLAG_ANNOTATION) != 0) {
              if (!annotationsEqual(
                  method.getAnnotations(), existingMs[j].getAnnotations(), flags)) {
                return true;
              }

              if (!parameterAnnotationsEquals(method, existingMs[j], fileName, flags)) {
                return true;
              }
            }
            continue new_method_loop;
          }
        }
      }
      return true; // (no match found)
    }

    return false;
  }
  /** Locate declaration in the current class file. This class file is always in a jar. */
  public void locateMatches(MatchLocator locator, ClassFile classFile, IBinaryType info)
      throws CoreException {
    SearchPattern pattern = locator.pattern;

    // check annotations references
    matchAnnotations(pattern, locator, classFile, info);

    // check class definition
    BinaryType binaryType = (BinaryType) classFile.getType();
    if (matchBinary(pattern, info, null)) {
      binaryType =
          new ResolvedBinaryType(
              (JavaElement) binaryType.getParent(),
              binaryType.getElementName(),
              binaryType.getKey());
      locator.reportBinaryMemberDeclaration(null, binaryType, null, info, SearchMatch.A_ACCURATE);
      return;
    }

    // Define arrays to store methods/fields from binary type if necessary
    IBinaryMethod[] binaryMethods = info.getMethods();
    int bMethodsLength = binaryMethods == null ? 0 : binaryMethods.length;
    IBinaryMethod[] unresolvedMethods = null;
    char[][] binaryMethodSignatures = null;
    boolean hasUnresolvedMethods = false;

    // Get fields from binary type info
    IBinaryField[] binaryFields = info.getFields();
    int bFieldsLength = binaryFields == null ? 0 : binaryFields.length;
    IBinaryField[] unresolvedFields = null;
    boolean hasUnresolvedFields = false;

    // Report as many accurate matches as possible
    int accuracy = SearchMatch.A_ACCURATE;
    boolean mustResolve = pattern.mustResolve;
    if (mustResolve) {
      BinaryTypeBinding binding = locator.cacheBinaryType(binaryType, info);
      if (binding != null) {
        // filter out element not in hierarchy scope
        if (!locator.typeInHierarchy(binding)) return;

        // Search matches on resolved methods
        MethodBinding[] availableMethods = binding.availableMethods();
        int aMethodsLength = availableMethods == null ? 0 : availableMethods.length;
        hasUnresolvedMethods = bMethodsLength != aMethodsLength;
        for (int i = 0; i < aMethodsLength; i++) {
          MethodBinding method = availableMethods[i];
          char[] methodSignature = method.genericSignature();
          if (methodSignature == null) methodSignature = method.signature();

          // Report the match if possible
          int level = locator.patternLocator.resolveLevel(method);
          if (level != PatternLocator.IMPOSSIBLE_MATCH) {
            IMethod methodHandle =
                binaryType.getMethod(
                    new String(
                        method.isConstructor()
                            ? binding.compoundName[binding.compoundName.length - 1]
                            : method.selector),
                    CharOperation.toStrings(
                        Signature.getParameterTypes(convertClassFileFormat(methodSignature))));
            accuracy =
                level == PatternLocator.ACCURATE_MATCH
                    ? SearchMatch.A_ACCURATE
                    : SearchMatch.A_INACCURATE;
            locator.reportBinaryMemberDeclaration(null, methodHandle, method, info, accuracy);
          }

          // Remove method from unresolved list
          if (hasUnresolvedMethods) {
            if (binaryMethodSignatures
                == null) { // Store binary method signatures to avoid multiple computation
              binaryMethodSignatures = new char[bMethodsLength][];
              for (int j = 0; j < bMethodsLength; j++) {
                IBinaryMethod binaryMethod = binaryMethods[j];
                char[] signature = binaryMethod.getGenericSignature();
                if (signature == null) signature = binaryMethod.getMethodDescriptor();
                binaryMethodSignatures[j] = signature;
              }
            }
            for (int j = 0; j < bMethodsLength; j++) {
              if (CharOperation.equals(binaryMethods[j].getSelector(), method.selector)
                  && CharOperation.equals(binaryMethodSignatures[j], methodSignature)) {
                if (unresolvedMethods == null) {
                  System.arraycopy(
                      binaryMethods,
                      0,
                      unresolvedMethods = new IBinaryMethod[bMethodsLength],
                      0,
                      bMethodsLength);
                }
                unresolvedMethods[j] = null;
                break;
              }
            }
          }
        }

        // Search matches on resolved fields
        FieldBinding[] availableFields = binding.availableFields();
        int aFieldsLength = availableFields == null ? 0 : availableFields.length;
        hasUnresolvedFields = bFieldsLength != aFieldsLength;
        for (int i = 0; i < aFieldsLength; i++) {
          FieldBinding field = availableFields[i];

          // Report the match if possible
          int level = locator.patternLocator.resolveLevel(field);
          if (level != PatternLocator.IMPOSSIBLE_MATCH) {
            IField fieldHandle = binaryType.getField(new String(field.name));
            accuracy =
                level == PatternLocator.ACCURATE_MATCH
                    ? SearchMatch.A_ACCURATE
                    : SearchMatch.A_INACCURATE;
            locator.reportBinaryMemberDeclaration(null, fieldHandle, field, info, accuracy);
          }

          // Remove the field from unresolved list
          if (hasUnresolvedFields) {
            for (int j = 0; j < bFieldsLength; j++) {
              if (CharOperation.equals(binaryFields[j].getName(), field.name)) {
                if (unresolvedFields == null) {
                  System.arraycopy(
                      binaryFields,
                      0,
                      unresolvedFields = new IBinaryField[bFieldsLength],
                      0,
                      bFieldsLength);
                }
                unresolvedFields[j] = null;
                break;
              }
            }
          }
        }

        // If all methods/fields were accurate then returns now
        if (!hasUnresolvedMethods && !hasUnresolvedFields) {
          return;
        }
      }
      accuracy = SearchMatch.A_INACCURATE;
    }

    // Report inaccurate methods
    if (mustResolve) binaryMethods = unresolvedMethods;
    bMethodsLength = binaryMethods == null ? 0 : binaryMethods.length;
    for (int i = 0; i < bMethodsLength; i++) {
      IBinaryMethod method = binaryMethods[i];
      if (method == null) continue; // impossible match or already reported as accurate
      if (matchBinary(pattern, method, info)) {
        char[] name;
        if (method.isConstructor()) {
          name = info.getName();
          int lastSlash = CharOperation.lastIndexOf('/', name);
          if (lastSlash != -1) {
            name = CharOperation.subarray(name, lastSlash + 1, name.length);
          }
        } else {
          name = method.getSelector();
        }
        String selector = new String(name);
        char[] methodSignature = binaryMethodSignatures == null ? null : binaryMethodSignatures[i];
        if (methodSignature == null) {
          methodSignature = method.getGenericSignature();
          if (methodSignature == null) methodSignature = method.getMethodDescriptor();
        }
        String[] parameterTypes =
            CharOperation.toStrings(
                Signature.getParameterTypes(convertClassFileFormat(methodSignature)));
        IMethod methodHandle = binaryType.getMethod(selector, parameterTypes);
        methodHandle =
            new ResolvedBinaryMethod(binaryType, selector, parameterTypes, methodHandle.getKey());
        locator.reportBinaryMemberDeclaration(null, methodHandle, null, info, accuracy);
      }
    }

    // Report inaccurate fields
    if (mustResolve) binaryFields = unresolvedFields;
    bFieldsLength = binaryFields == null ? 0 : binaryFields.length;
    for (int i = 0; i < bFieldsLength; i++) {
      IBinaryField field = binaryFields[i];
      if (field == null) continue; // impossible match or already reported as accurate
      if (matchBinary(pattern, field, info)) {
        String fieldName = new String(field.getName());
        IField fieldHandle = binaryType.getField(fieldName);
        fieldHandle = new ResolvedBinaryField(binaryType, fieldName, fieldHandle.getKey());
        locator.reportBinaryMemberDeclaration(null, fieldHandle, null, info, accuracy);
      }
    }
  }