/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.core.search.matching; import java.util.HashMap; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.*; import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.search.*; import org.eclipse.jdt.internal.compiler.ast.*; import org.eclipse.jdt.internal.compiler.env.IBinaryType; import org.eclipse.jdt.internal.compiler.lookup.*; import org.eclipse.jdt.internal.compiler.util.SimpleSet; import org.eclipse.jdt.internal.core.search.BasicSearchEngine; public class MethodLocator extends PatternLocator { protected MethodPattern pattern; protected boolean isDeclarationOfReferencedMethodsPattern; //extra reference info public char[][][] allSuperDeclaringTypeNames; //method declarations which parameters verification fail private HashMap methodDeclarationsWithInvalidParam = new HashMap(); public MethodLocator(MethodPattern pattern) { super(pattern); this.pattern = pattern; this.isDeclarationOfReferencedMethodsPattern = this.pattern instanceof DeclarationOfReferencedMethodsPattern; } /* * Clear caches */ protected void clear() { this.methodDeclarationsWithInvalidParam = new HashMap(); } protected int fineGrain() { return this.pattern.fineGrain; } private MethodBinding getMethodBinding(ReferenceBinding type, char[] methodName, TypeBinding[] argumentTypes) { MethodBinding[] methods = type.getMethods(methodName); MethodBinding method = null; methodsLoop: for (int i=0, length=methods.length; i<length; i++) { method = methods[i]; TypeBinding[] parameters = method.parameters; if (argumentTypes.length == parameters.length) { for (int j=0,l=parameters.length; j<l; j++) { if (parameters[j].erasure() != argumentTypes[j].erasure()) { continue methodsLoop; } } return method; } } return null; } public void initializePolymorphicSearch(MatchLocator locator) { long start = 0; if (BasicSearchEngine.VERBOSE) { start = System.currentTimeMillis(); } try { this.allSuperDeclaringTypeNames = new SuperTypeNamesCollector( this.pattern, this.pattern.declaringSimpleName, this.pattern.declaringQualification, locator, this.pattern.declaringType, locator.progressMonitor).collect(); } catch (JavaModelException e) { // inaccurate matches will be found } if (BasicSearchEngine.VERBOSE) { System.out.println("Time to initialize polymorphic search: "+(System.currentTimeMillis()-start)); //$NON-NLS-1$ } } /* * Return whether a type name is in pattern all super declaring types names. */ private boolean isTypeInSuperDeclaringTypeNames(char[][] typeName) { if (this.allSuperDeclaringTypeNames == null) return false; int length = this.allSuperDeclaringTypeNames.length; for (int i= 0; i<length; i++) { if (CharOperation.equals(this.allSuperDeclaringTypeNames[i], typeName)) { return true; } } return false; } /** * Returns whether the code gen will use an invoke virtual for * this message send or not. */ protected boolean isVirtualInvoke(MethodBinding method, MessageSend messageSend) { return !method.isStatic() && !method.isPrivate() && !messageSend.isSuperAccess(); } public int match(ASTNode node, MatchingNodeSet nodeSet) { int declarationsLevel = IMPOSSIBLE_MATCH; if (this.pattern.findReferences) { if (node instanceof ImportReference) { // With static import, we can have static method reference in import reference ImportReference importRef = (ImportReference) node; int length = importRef.tokens.length-1; if (importRef.isStatic() && ((importRef.bits & ASTNode.OnDemand) == 0) && matchesName(this.pattern.selector, importRef.tokens[length])) { char[][] compoundName = new char[length][]; System.arraycopy(importRef.tokens, 0, compoundName, 0, length); char[] declaringType = CharOperation.concat(this.pattern.declaringQualification, this.pattern.declaringSimpleName, '.'); if (matchesName(declaringType, CharOperation.concatWith(compoundName, '.'))) { declarationsLevel = this.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; } } } } return nodeSet.addMatch(node, declarationsLevel); } //public int match(ConstructorDeclaration node, MatchingNodeSet nodeSet) - SKIP IT //public int match(Expression node, MatchingNodeSet nodeSet) - SKIP IT //public int match(FieldDeclaration node, MatchingNodeSet nodeSet) - SKIP IT public int match(MethodDeclaration node, MatchingNodeSet nodeSet) { if (!this.pattern.findDeclarations) return IMPOSSIBLE_MATCH; // Verify method name if (!matchesName(this.pattern.selector, node.selector)) return IMPOSSIBLE_MATCH; // Verify parameters types boolean resolve = this.pattern.mustResolve; if (this.pattern.parameterSimpleNames != null) { int length = this.pattern.parameterSimpleNames.length; ASTNode[] args = node.arguments; int argsLength = args == null ? 0 : args.length; if (length != argsLength) return IMPOSSIBLE_MATCH; for (int i = 0; i < argsLength; i++) { if (args != null && !matchesTypeReference(this.pattern.parameterSimpleNames[i], ((Argument) args[i]).type)) { // Do not return as impossible when source level is at least 1.5 if (this.mayBeGeneric) { if (!this.pattern.mustResolve) { // Set resolution flag on node set in case of types was inferred in parameterized types from generic ones... // (see bugs https://bugs.eclipse.org/bugs/show_bug.cgi?id=79990, 96761, 96763) nodeSet.mustResolve = true; resolve = true; } this.methodDeclarationsWithInvalidParam.put(node, null); } else { return IMPOSSIBLE_MATCH; } } } } // Verify type arguments (do not reject if pattern has no argument as it can be an erasure match) if (this.pattern.hasMethodArguments()) { if (node.typeParameters == null || node.typeParameters.length != this.pattern.methodArguments.length) return IMPOSSIBLE_MATCH; } // Method declaration may match pattern return nodeSet.addMatch(node, resolve ? POSSIBLE_MATCH : ACCURATE_MATCH); } public int match(MemberValuePair node, MatchingNodeSet nodeSet) { if (!this.pattern.findReferences) return IMPOSSIBLE_MATCH; if (!matchesName(this.pattern.selector, node.name)) return IMPOSSIBLE_MATCH; return nodeSet.addMatch(node, this.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); } public int match(MessageSend node, MatchingNodeSet nodeSet) { if (!this.pattern.findReferences) return IMPOSSIBLE_MATCH; if (!matchesName(this.pattern.selector, node.selector)) return IMPOSSIBLE_MATCH; if (this.pattern.parameterSimpleNames != null && (!this.pattern.varargs || ((node.bits & ASTNode.InsideJavadoc) != 0))) { int length = this.pattern.parameterSimpleNames.length; ASTNode[] args = node.arguments; int argsLength = args == null ? 0 : args.length; if (length != argsLength) return IMPOSSIBLE_MATCH; } return nodeSet.addMatch(node, this.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); } //public int match(Reference node, MatchingNodeSet nodeSet) - SKIP IT public int match(Annotation node, MatchingNodeSet nodeSet) { if (!this.pattern.findReferences) return IMPOSSIBLE_MATCH; MemberValuePair[] pairs = node.memberValuePairs(); if (pairs == null || pairs.length == 0) return IMPOSSIBLE_MATCH; int length = pairs.length; MemberValuePair pair = null; for (int i=0; i<length; i++) { pair = node.memberValuePairs()[i]; if (matchesName(this.pattern.selector, pair.name)) { ASTNode possibleNode = (node instanceof SingleMemberAnnotation) ? (ASTNode) node : pair; return nodeSet.addMatch(possibleNode, this.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); } } return IMPOSSIBLE_MATCH; } //public int match(TypeDeclaration node, MatchingNodeSet nodeSet) - SKIP IT //public int match(TypeReference node, MatchingNodeSet nodeSet) - SKIP IT protected int matchContainer() { if (this.pattern.findReferences) { // need to look almost everywhere to find in javadocs and static import return ALL_CONTAINER; } return CLASS_CONTAINER; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.core.search.matching.PatternLocator#matchLevelAndReportImportRef(org.eclipse.jdt.internal.compiler.ast.ImportReference, org.eclipse.jdt.internal.compiler.lookup.Binding, org.eclipse.jdt.internal.core.search.matching.MatchLocator) * Accept to report match of static field on static import */ protected void matchLevelAndReportImportRef(ImportReference importRef, Binding binding, MatchLocator locator) throws CoreException { if (importRef.isStatic() && binding instanceof MethodBinding) { super.matchLevelAndReportImportRef(importRef, binding, locator); } } protected int matchMethod(MethodBinding method, boolean skipImpossibleArg) { if (!matchesName(this.pattern.selector, method.selector)) return IMPOSSIBLE_MATCH; int level = ACCURATE_MATCH; // look at return type only if declaring type is not specified if (this.pattern.declaringSimpleName == null) { // TODO (frederic) use this call to refine accuracy on return type // int newLevel = resolveLevelForType(this.pattern.returnSimpleName, this.pattern.returnQualification, this.pattern.returnTypeArguments, 0, method.returnType); int newLevel = resolveLevelForType(this.pattern.returnSimpleName, this.pattern.returnQualification, method.returnType); if (level > newLevel) { if (newLevel == IMPOSSIBLE_MATCH) return IMPOSSIBLE_MATCH; level = newLevel; // can only be downgraded } } // parameter types int parameterCount = this.pattern.parameterSimpleNames == null ? -1 : this.pattern.parameterSimpleNames.length; if (parameterCount > -1) { // global verification if (method.parameters == null) return INACCURATE_MATCH; if (parameterCount != method.parameters.length) return IMPOSSIBLE_MATCH; if (!method.isValidBinding() && ((ProblemMethodBinding)method).problemId() == ProblemReasons.Ambiguous) { // return inaccurate match for ambiguous call (bug 80890) return INACCURATE_MATCH; } // verify each parameter for (int i = 0; i < parameterCount; i++) { TypeBinding argType = method.parameters[i]; int newLevel = IMPOSSIBLE_MATCH; if (argType.isMemberType()) { // only compare source name for member type (bug 41018) newLevel = CharOperation.match(this.pattern.parameterSimpleNames[i], argType.sourceName(), this.isCaseSensitive) ? ACCURATE_MATCH : IMPOSSIBLE_MATCH; } else { // TODO (frederic) use this call to refine accuracy on parameter types // newLevel = resolveLevelForType(this.pattern.parameterSimpleNames[i], this.pattern.parameterQualifications[i], this.pattern.parametersTypeArguments[i], 0, argType); newLevel = resolveLevelForType(this.pattern.parameterSimpleNames[i], this.pattern.parameterQualifications[i], argType); } if (level > newLevel) { if (newLevel == IMPOSSIBLE_MATCH) { if (skipImpossibleArg) { // Do not consider match as impossible while finding declarations and source level >= 1.5 // (see bugs https://bugs.eclipse.org/bugs/show_bug.cgi?id=79990, 96761, 96763) newLevel = level; } else { return IMPOSSIBLE_MATCH; } } level = newLevel; // can only be downgraded } } } return level; } private boolean matchOverriddenMethod(ReferenceBinding type, MethodBinding method, MethodBinding matchMethod) { if (type == null || this.pattern.selector == null) return false; // matches superclass if (!type.isInterface() && !CharOperation.equals(type.compoundName, TypeConstants.JAVA_LANG_OBJECT)) { ReferenceBinding superClass = type.superclass(); if (superClass.isParameterizedType()) { MethodBinding[] methods = superClass.getMethods(this.pattern.selector); int length = methods.length; for (int i = 0; i<length; i++) { if (methods[i].areParametersEqual(method)) { if (matchMethod == null) { if (methodParametersEqualsPattern(methods[i].original())) return true; } else { if (methods[i].original().areParametersEqual(matchMethod)) return true; } } } } if (matchOverriddenMethod(superClass, method, matchMethod)) { return true; } } // matches interfaces ReferenceBinding[] interfaces = type.superInterfaces(); if (interfaces == null) return false; int iLength = interfaces.length; for (int i = 0; i<iLength; i++) { if (interfaces[i].isParameterizedType()) { MethodBinding[] methods = interfaces[i].getMethods(this.pattern.selector); int length = methods.length; for (int j = 0; j<length; j++) { if (methods[j].areParametersEqual(method)) { if (matchMethod == null) { if (methodParametersEqualsPattern(methods[j].original())) return true; } else { if (methods[j].original().areParametersEqual(matchMethod)) return true; } } } } if (matchOverriddenMethod(interfaces[i], method, matchMethod)) { return true; } } return false; } protected void matchReportReference(ASTNode reference, IJavaElement element, Binding elementBinding, int accuracy, MatchLocator locator) throws CoreException { matchReportReference(reference, element, null, null, elementBinding, accuracy, locator); } /** * @see org.eclipse.jdt.internal.core.search.matching.PatternLocator#matchReportReference(org.eclipse.jdt.internal.compiler.ast.ASTNode, org.eclipse.jdt.core.IJavaElement, Binding, int, org.eclipse.jdt.internal.core.search.matching.MatchLocator) */ protected void matchReportReference(ASTNode reference, IJavaElement element, IJavaElement localElement, IJavaElement[] otherElements, Binding elementBinding, int accuracy, MatchLocator locator) throws CoreException { MethodBinding methodBinding = (reference instanceof MessageSend) ? ((MessageSend)reference).binding: ((elementBinding instanceof MethodBinding) ? (MethodBinding) elementBinding : null); if (this.isDeclarationOfReferencedMethodsPattern) { if (methodBinding == null) return; // need exact match to be able to open on type ref if (accuracy != SearchMatch.A_ACCURATE) return; // element that references the method must be included in the enclosing element DeclarationOfReferencedMethodsPattern declPattern = (DeclarationOfReferencedMethodsPattern) this.pattern; while (element != null && !declPattern.enclosingElement.equals(element)) element = element.getParent(); if (element != null) { reportDeclaration(methodBinding, locator, declPattern.knownMethods); } } else { MethodReferenceMatch methodReferenceMatch = locator.newMethodReferenceMatch(element, elementBinding, accuracy, -1, -1, false /*not constructor*/, false/*not synthetic*/, reference); methodReferenceMatch.setLocalElement(localElement); this.match = methodReferenceMatch; if (this.pattern.findReferences && reference instanceof MessageSend) { IJavaElement focus = this.pattern.focus; // verify closest match if pattern was bound // (see bug 70827) if (focus != null && focus.getElementType() == IJavaElement.METHOD) { if (methodBinding != null && methodBinding.declaringClass != null) { boolean isPrivate = Flags.isPrivate(((IMethod) focus).getFlags()); if (isPrivate && !CharOperation.equals(methodBinding.declaringClass.sourceName, focus.getParent().getElementName().toCharArray())) { return; // finally the match was not possible } } } matchReportReference((MessageSend)reference, locator, ((MessageSend)reference).binding); } else { if (reference instanceof SingleMemberAnnotation) { reference = ((SingleMemberAnnotation)reference).memberValuePairs()[0]; this.match.setImplicit(true); } int offset = reference.sourceStart; int length = reference.sourceEnd - offset + 1; this.match.setOffset(offset); this.match.setLength(length); locator.report(this.match); } } } void matchReportReference(MessageSend messageSend, MatchLocator locator, MethodBinding methodBinding) throws CoreException { // Look if there's a need to special report for parameterized type boolean isParameterized = false; if (methodBinding instanceof ParameterizedGenericMethodBinding) { // parameterized generic method isParameterized = true; // Update match regarding method type arguments ParameterizedGenericMethodBinding parameterizedMethodBinding = (ParameterizedGenericMethodBinding) methodBinding; this.match.setRaw(parameterizedMethodBinding.isRaw); TypeBinding[] typeArguments = /*parameterizedMethodBinding.isRaw ? null :*/ parameterizedMethodBinding.typeArguments; updateMatch(typeArguments, locator, this.pattern.methodArguments, this.pattern.hasMethodParameters()); // Update match regarding declaring class type arguments if (methodBinding.declaringClass.isParameterizedType() || methodBinding.declaringClass.isRawType()) { ParameterizedTypeBinding parameterizedBinding = (ParameterizedTypeBinding)methodBinding.declaringClass; if (!this.pattern.hasTypeArguments() && this.pattern.hasMethodArguments() || parameterizedBinding.isParameterizedWithOwnVariables()) { // special case for pattern which defines method arguments but not its declaring type // in this case, we do not refine accuracy using declaring type arguments...! } else { updateMatch(parameterizedBinding, this.pattern.getTypeArguments(), this.pattern.hasTypeParameters(), 0, locator); } } else if (this.pattern.hasTypeArguments()) { this.match.setRule(SearchPattern.R_ERASURE_MATCH); } // Update match regarding method parameters // TODO ? (frederic) // Update match regarding method return type // TODO ? (frederic) // Special case for errors if (this.match.getRule() != 0 && messageSend.resolvedType == null) { this.match.setRule(SearchPattern.R_ERASURE_MATCH); } } else if (methodBinding instanceof ParameterizedMethodBinding) { isParameterized = true; if (methodBinding.declaringClass.isParameterizedType() || methodBinding.declaringClass.isRawType()) { ParameterizedTypeBinding parameterizedBinding = (ParameterizedTypeBinding)methodBinding.declaringClass; if (!parameterizedBinding.isParameterizedWithOwnVariables()) { updateMatch(parameterizedBinding, this.pattern.getTypeArguments(), this.pattern.hasTypeParameters(), 0, locator); } } else if (this.pattern.hasTypeArguments()) { this.match.setRule(SearchPattern.R_ERASURE_MATCH); } // Update match regarding method parameters // TODO ? (frederic) // Update match regarding method return type // TODO ? (frederic) // Special case for errors if (this.match.getRule() != 0 && messageSend.resolvedType == null) { this.match.setRule(SearchPattern.R_ERASURE_MATCH); } } else if (this.pattern.hasMethodArguments()) { // binding has no type params, compatible erasure if pattern does this.match.setRule(SearchPattern.R_ERASURE_MATCH); } // See whether it is necessary to report or not if (this.match.getRule() == 0) return; // impossible match boolean report = (this.isErasureMatch && this.match.isErasure()) || (this.isEquivalentMatch && this.match.isEquivalent()) || this.match.isExact(); if (!report) return; // Report match int offset = (int) (messageSend.nameSourcePosition >>> 32); this.match.setOffset(offset); this.match.setLength(messageSend.sourceEnd - offset + 1); if (isParameterized && this.pattern.hasMethodArguments()) { locator.reportAccurateParameterizedMethodReference(this.match, messageSend, messageSend.typeArguments); } else { locator.report(this.match); } } /* * Return whether method parameters are equals to pattern ones. */ private boolean methodParametersEqualsPattern(MethodBinding method) { TypeBinding[] methodParameters = method.parameters; int length = methodParameters.length; if (length != this.pattern.parameterSimpleNames.length) return false; for (int i = 0; i < length; i++) { char[] paramQualifiedName = qualifiedPattern(this.pattern.parameterSimpleNames[i], this.pattern.parameterQualifications[i]); if (!CharOperation.match(paramQualifiedName, methodParameters[i].readableName(), this.isCaseSensitive)) { return false; } } return true; } public SearchMatch newDeclarationMatch(ASTNode reference, IJavaElement element, Binding elementBinding, int accuracy, int length, MatchLocator locator) { if (elementBinding != null) { MethodBinding methodBinding = (MethodBinding) elementBinding; // If method parameters verification was not valid, then try to see if method arguments can match a method in hierarchy if (this.methodDeclarationsWithInvalidParam.containsKey(reference)) { // First see if this reference has already been resolved => report match if validated Boolean report = (Boolean) this.methodDeclarationsWithInvalidParam.get(reference); if (report != null) { if (report.booleanValue()) { return super.newDeclarationMatch(reference, element, elementBinding, accuracy, length, locator); } return null; } if (matchOverriddenMethod(methodBinding.declaringClass, methodBinding, null)) { this.methodDeclarationsWithInvalidParam.put(reference, Boolean.TRUE); return super.newDeclarationMatch(reference, element, elementBinding, accuracy, length, locator); } if (isTypeInSuperDeclaringTypeNames(methodBinding.declaringClass.compoundName)) { MethodBinding patternBinding = locator.getMethodBinding(this.pattern); if (patternBinding != null) { if (!matchOverriddenMethod(patternBinding.declaringClass, patternBinding, methodBinding)) { this.methodDeclarationsWithInvalidParam.put(reference, Boolean.FALSE); return null; } } this.methodDeclarationsWithInvalidParam.put(reference, Boolean.TRUE); return super.newDeclarationMatch(reference, element, elementBinding, accuracy, length, locator); } this.methodDeclarationsWithInvalidParam.put(reference, Boolean.FALSE); return null; } } return super.newDeclarationMatch(reference, element, elementBinding, accuracy, length, locator); } protected int referenceType() { return IJavaElement.METHOD; } protected void reportDeclaration(MethodBinding methodBinding, MatchLocator locator, SimpleSet knownMethods) throws CoreException { ReferenceBinding declaringClass = methodBinding.declaringClass; IType type = locator.lookupType(declaringClass); if (type == null) return; // case of a secondary type // Report match for binary if (type.isBinary()) { IMethod method = null; TypeBinding[] parameters = methodBinding.original().parameters; int parameterLength = parameters.length; char[][] parameterTypes = new char[parameterLength][]; for (int i = 0; i<parameterLength; i++) { char[] typeName = parameters[i].qualifiedSourceName(); for (int j=0, dim=parameters[i].dimensions(); j<dim; j++) { typeName = CharOperation.concat(typeName, new char[] {'[', ']'}); } parameterTypes[i] = typeName; } method = locator.createBinaryMethodHandle(type, methodBinding.selector, parameterTypes); if (method == null || knownMethods.addIfNotIncluded(method) == null) return; IResource resource = type.getResource(); if (resource == null) resource = type.getJavaProject().getProject(); IBinaryType info = locator.getBinaryInfo((org.eclipse.jdt.internal.core.ClassFile)type.getClassFile(), resource); locator.reportBinaryMemberDeclaration(resource, method, methodBinding, info, SearchMatch.A_ACCURATE); return; } // When source is available, report match if method is found in the declaring type IResource resource = type.getResource(); if (declaringClass instanceof ParameterizedTypeBinding) declaringClass = ((ParameterizedTypeBinding) declaringClass).genericType(); ClassScope scope = ((SourceTypeBinding) declaringClass).scope; if (scope != null) { TypeDeclaration typeDecl = scope.referenceContext; AbstractMethodDeclaration methodDecl = typeDecl.declarationOf(methodBinding.original()); if (methodDecl != null) { // Create method handle from method declaration String methodName = new String(methodBinding.selector); Argument[] arguments = methodDecl.arguments; int length = arguments == null ? 0 : arguments.length; String[] parameterTypes = new String[length]; for (int i = 0; i < length; i++) { char[][] typeName = arguments[i].type.getParameterizedTypeName(); parameterTypes[i] = Signature.createTypeSignature(CharOperation.concatWith(typeName, '.'), false); } IMethod method = type.getMethod(methodName, parameterTypes); if (method == null || knownMethods.addIfNotIncluded(method) == null) return; // Create and report corresponding match int offset = methodDecl.sourceStart; this.match = new MethodDeclarationMatch(method, SearchMatch.A_ACCURATE, offset, methodDecl.sourceEnd-offset+1, locator.getParticipant(), resource); locator.report(this.match); } } } public int resolveLevel(ASTNode possibleMatchingNode) { if (this.pattern.findReferences) { if (possibleMatchingNode instanceof MessageSend) { return resolveLevel((MessageSend) possibleMatchingNode); } if (possibleMatchingNode instanceof SingleMemberAnnotation) { SingleMemberAnnotation annotation = (SingleMemberAnnotation) possibleMatchingNode; return resolveLevel(annotation.memberValuePairs()[0].binding); } if (possibleMatchingNode instanceof MemberValuePair) { MemberValuePair memberValuePair = (MemberValuePair) possibleMatchingNode; return resolveLevel(memberValuePair.binding); } } if (this.pattern.findDeclarations) { if (possibleMatchingNode instanceof MethodDeclaration) { return resolveLevel(((MethodDeclaration) possibleMatchingNode).binding); } } return IMPOSSIBLE_MATCH; } public int resolveLevel(Binding binding) { if (binding == null) return INACCURATE_MATCH; if (!(binding instanceof MethodBinding)) return IMPOSSIBLE_MATCH; MethodBinding method = (MethodBinding) binding; boolean skipVerif = this.pattern.findDeclarations && this.mayBeGeneric; int methodLevel = matchMethod(method, skipVerif); if (methodLevel == IMPOSSIBLE_MATCH) { if (method != method.original()) methodLevel = matchMethod(method.original(), skipVerif); if (methodLevel == IMPOSSIBLE_MATCH) { return IMPOSSIBLE_MATCH; } else { method = method.original(); } } // declaring type if (this.pattern.declaringSimpleName == null && this.pattern.declaringQualification == null) return methodLevel; // since any declaring class will do boolean subType = !method.isStatic() && !method.isPrivate(); if (subType && this.pattern.declaringQualification != null && method.declaringClass != null && method.declaringClass.fPackage != null) { subType = CharOperation.compareWith(this.pattern.declaringQualification, method.declaringClass.fPackage.shortReadableName()) == 0; } int declaringLevel = subType ? resolveLevelAsSubtype(this.pattern.declaringSimpleName, this.pattern.declaringQualification, method.declaringClass, method.selector, null) : resolveLevelForType(this.pattern.declaringSimpleName, this.pattern.declaringQualification, method.declaringClass); return (methodLevel & MATCH_LEVEL_MASK) > (declaringLevel & MATCH_LEVEL_MASK) ? declaringLevel : methodLevel; // return the weaker match } protected int resolveLevel(MessageSend messageSend) { MethodBinding method = messageSend.binding; if (method == null) { return INACCURATE_MATCH; } if (messageSend.resolvedType == null) { // Closest match may have different argument numbers when ProblemReason is NotFound // see MessageSend#resolveType(BlockScope) // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=97322 int argLength = messageSend.arguments == null ? 0 : messageSend.arguments.length; if (this.pattern.parameterSimpleNames == null || argLength == this.pattern.parameterSimpleNames.length) { return INACCURATE_MATCH; } return IMPOSSIBLE_MATCH; } int methodLevel = matchMethod(method, false); if (methodLevel == IMPOSSIBLE_MATCH) { if (method != method.original()) methodLevel = matchMethod(method.original(), false); if (methodLevel == IMPOSSIBLE_MATCH) return IMPOSSIBLE_MATCH; method = method.original(); } // receiver type if (this.pattern.declaringSimpleName == null && this.pattern.declaringQualification == null) return methodLevel; // since any declaring class will do int declaringLevel; if (isVirtualInvoke(method, messageSend) && (messageSend.actualReceiverType instanceof ReferenceBinding)) { ReferenceBinding methodReceiverType = (ReferenceBinding) messageSend.actualReceiverType; declaringLevel = resolveLevelAsSubtype(this.pattern.declaringSimpleName, this.pattern.declaringQualification, methodReceiverType, method.selector, method.parameters); if (declaringLevel == IMPOSSIBLE_MATCH) { if (method.declaringClass == null || this.allSuperDeclaringTypeNames == null) { declaringLevel = INACCURATE_MATCH; } else { if (resolveLevelAsSuperInvocation(methodReceiverType, method.parameters, true)) { declaringLevel = methodLevel // since this is an ACCURATE_MATCH so return the possibly weaker match | SUPER_INVOCATION_FLAVOR; // this is an overridden method => add flavor to returned level } } } if ((declaringLevel & FLAVORS_MASK) != 0) { // level got some flavors => return it return declaringLevel; } } else { declaringLevel = resolveLevelForType(this.pattern.declaringSimpleName, this.pattern.declaringQualification, method.declaringClass); } return (methodLevel & MATCH_LEVEL_MASK) > (declaringLevel & MATCH_LEVEL_MASK) ? declaringLevel : methodLevel; // return the weaker match } /** * Returns whether the given reference type binding matches or is a subtype of a type * that matches the given qualified pattern. * Returns ACCURATE_MATCH if it does. * Returns INACCURATE_MATCH if resolve fails * Returns IMPOSSIBLE_MATCH if it doesn't. */ protected int resolveLevelAsSubtype(char[] simplePattern, char[] qualifiedPattern, ReferenceBinding type, char[] methodName, TypeBinding[] argumentTypes) { if (type == null) return INACCURATE_MATCH; int level = resolveLevelForType(simplePattern, qualifiedPattern, type); if (level != IMPOSSIBLE_MATCH) { MethodBinding method = argumentTypes == null ? null : getMethodBinding(type, methodName, argumentTypes); if (((method != null && !method.isAbstract()) || !type.isAbstract()) && !type.isInterface()) { // if concrete, then method is overridden level |= OVERRIDDEN_METHOD_FLAVOR; } return level; } // matches superclass if (!type.isInterface() && !CharOperation.equals(type.compoundName, TypeConstants.JAVA_LANG_OBJECT)) { level = resolveLevelAsSubtype(simplePattern, qualifiedPattern, type.superclass(), methodName, argumentTypes); if (level != IMPOSSIBLE_MATCH) { if (argumentTypes != null) { // need to verify if method may be overridden MethodBinding method = getMethodBinding(type, methodName, argumentTypes); if (method != null) { // one method match in hierarchy if ((level & OVERRIDDEN_METHOD_FLAVOR) != 0) { // this method is already overridden on a super class, current match is impossible return IMPOSSIBLE_MATCH; } if (!method.isAbstract() && !type.isInterface()) { // store the fact that the method is overridden level |= OVERRIDDEN_METHOD_FLAVOR; } } } return level | SUB_INVOCATION_FLAVOR; // add flavor to returned level } } // matches interfaces ReferenceBinding[] interfaces = type.superInterfaces(); if (interfaces == null) return INACCURATE_MATCH; for (int i = 0; i < interfaces.length; i++) { level = resolveLevelAsSubtype(simplePattern, qualifiedPattern, interfaces[i], methodName, null); if (level != IMPOSSIBLE_MATCH) { if (!type.isAbstract() && !type.isInterface()) { // if concrete class, then method is overridden level |= OVERRIDDEN_METHOD_FLAVOR; } return level | SUB_INVOCATION_FLAVOR; // add flavor to returned level } } return IMPOSSIBLE_MATCH; } /* * Return whether the given type binding or one of its possible super interfaces * matches a type in the declaring type names hierarchy. */ private boolean resolveLevelAsSuperInvocation(ReferenceBinding type, TypeBinding[] argumentTypes, boolean methodAlreadyVerified) { char[][] compoundName = type.compoundName; for (int i = 0, max = this.allSuperDeclaringTypeNames.length; i < max; i++) { if (CharOperation.equals(this.allSuperDeclaringTypeNames[i], compoundName)) { // need to verify if the type implements the pattern method if (methodAlreadyVerified) return true; // already verified before enter into this method (see resolveLevel(MessageSend)) MethodBinding[] methods = type.getMethods(this.pattern.selector); for (int j=0, length=methods.length; j<length; j++) { MethodBinding method = methods[j]; TypeBinding[] parameters = method.parameters; if (argumentTypes.length == parameters.length) { boolean found = true; for (int k=0,l=parameters.length; k<l; k++) { if (parameters[k].erasure() != argumentTypes[k].erasure()) { found = false; break; } } if (found) { return true; } } } break; } } // If the given type is an interface then a common super interface may be found // in a parallel branch of the super hierarchy, so we need to verify all super interfaces. // If it's a class then there's only one possible branch for the hierarchy and // this branch has been already verified by the test above if (type.isInterface()) { ReferenceBinding[] interfaces = type.superInterfaces(); if (interfaces == null) return false; for (int i = 0; i < interfaces.length; i++) { if (resolveLevelAsSuperInvocation(interfaces[i], argumentTypes, false)) { return true; } } } return false; } public String toString() { return "Locator for " + this.pattern.toString(); //$NON-NLS-1$ } }