All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jdt.internal.core.search.matching.MethodLocator Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2000, 2016 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
 *     Samrat Dhillon [email protected] - Search for method references is
 *               returning methods as overriden even if the superclass's method is 
 *               only package-visible - https://bugs.eclipse.org/357547
 *******************************************************************************/
package org.eclipse.jdt.internal.core.search.matching;

import java.util.Arrays;
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.BinaryMethod;
import org.eclipse.jdt.internal.core.search.BasicSearchEngine;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class MethodLocator extends PatternLocator {

protected MethodPattern pattern;
protected boolean isDeclarationOfReferencedMethodsPattern;

//extra reference info
public char[][][] allSuperDeclaringTypeNames;

// This is set only if focus is null. In these cases
// it will be hard to determine if the super class is of the same package
// at a latter point. Hence, this array is created with all the super class 
// names of the same package name as of the matching class name.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=357547
private char[][][] samePkgSuperDeclaringTypeNames;

private MatchLocator matchLocator;
//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 ReferenceBinding getMatchingSuper(ReferenceBinding binding) {
	if (binding == null) return null;
	ReferenceBinding superBinding = binding.superclass();
	int level = resolveLevelForType(this.pattern.declaringSimpleName, this.pattern.declaringQualification, superBinding);
	if (level != IMPOSSIBLE_MATCH) return superBinding;
	// matches superclass
	if (!binding.isInterface() && !CharOperation.equals(binding.compoundName, TypeConstants.JAVA_LANG_OBJECT)) {
		superBinding = getMatchingSuper(superBinding);
		if (superBinding != null) return superBinding;
	}
	// matches interfaces
	ReferenceBinding[] interfaces = binding.superInterfaces();
	if (interfaces == null) return null;
	for (int i = 0; i < interfaces.length; i++) {
		level = resolveLevelForType(this.pattern.declaringSimpleName, this.pattern.declaringQualification, interfaces[i]);
		if (level != IMPOSSIBLE_MATCH) return interfaces[i];
		superBinding = getMatchingSuper(interfaces[i]);
		if (superBinding != null) return superBinding;
	}
	return null;
}

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 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;
		}
		boolean foundTypeVariable = false;
		MethodBinding focusMethodBinding = null;
		boolean checkedFocus = false;
		boolean isBinary = this.pattern!= null && this.pattern.focus instanceof BinaryMethod;
		// verify each parameter
		for (int i = 0; i < parameterCount; i++) {
			TypeBinding argType = method.parameters[i];
			int newLevel = IMPOSSIBLE_MATCH;
			boolean foundLevel = false;
			if (argType.isMemberType() || this.pattern.parameterQualifications[i] != null) {
				if (!checkedFocus) {
					focusMethodBinding = this.matchLocator.getMethodBinding(this.pattern);
					checkedFocus = true;
				}
				if (focusMethodBinding != null) {// textual comparison insufficient
					TypeBinding[] parameters = focusMethodBinding.parameters;
					if (parameters.length >= parameterCount) {
						newLevel = (isBinary ? argType.erasure().isEquivalentTo((parameters[i].erasure())) :argType.isEquivalentTo((parameters[i]))) ? 
								ACCURATE_MATCH : IMPOSSIBLE_MATCH;
						foundLevel = true;
					}
				}
			} 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)
						if (!foundLevel) {
							newLevel = level;
						}
					} else if (argType.isTypeVariable()) {
						newLevel = level;
						foundTypeVariable = true;
					} else {
						return IMPOSSIBLE_MATCH;
					}
				}
				level = newLevel; // can only be downgraded
			}
		}
		if (foundTypeVariable) {
			if (!method.isStatic() && !method.isPrivate()) {
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=123836, No point in textually comparing type variables, captures etc with concrete types. 
				if (!checkedFocus)
					focusMethodBinding = this.matchLocator.getMethodBinding(this.pattern);
				if (focusMethodBinding != null) {
					if (matchOverriddenMethod(focusMethodBinding.declaringClass, focusMethodBinding, method)) {
						return ACCURATE_MATCH;
					}
				}
			} 
			return IMPOSSIBLE_MATCH;
		}
	}

	return level;
}
// This works for only methods of parameterized types.
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>> 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 (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, methodReceiverType.qualifiedPackageName(), method.isDefault());
		if (declaringLevel == IMPOSSIBLE_MATCH) {
			if (method.declaringClass == null || this.allSuperDeclaringTypeNames == null) {
				declaringLevel = INACCURATE_MATCH;
			} else {
				char[][][] superTypeNames = (method.isDefault() && this.pattern.focus == null) ? this.samePkgSuperDeclaringTypeNames: this.allSuperDeclaringTypeNames;
				if (superTypeNames != null && resolveLevelAsSuperInvocation(methodReceiverType, method.parameters, superTypeNames, 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
}

protected int resolveLevel(ReferenceExpression referenceExpression) {
	MethodBinding method = referenceExpression.getMethodBinding();
	if (method == null || !method.isValidBinding())
		return INACCURATE_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;
	ReferenceBinding ref = checkMethodRef(method, referenceExpression);
	if (ref != null) {
		declaringLevel = resolveLevelAsSubtype(this.pattern.declaringSimpleName, this.pattern.declaringQualification, ref, method.selector, method.parameters, ref.qualifiedPackageName(), method.isDefault());
		if (declaringLevel == IMPOSSIBLE_MATCH) {
			if (method.declaringClass == null || this.allSuperDeclaringTypeNames == null) {
				declaringLevel = INACCURATE_MATCH;
			} else {
				char[][][] superTypeNames = (method.isDefault() && this.pattern.focus == null) ? this.samePkgSuperDeclaringTypeNames: this.allSuperDeclaringTypeNames;
				if (superTypeNames != null && resolveLevelAsSuperInvocation(ref, method.parameters, superTypeNames, true)) {
						declaringLevel = methodLevel // since this is an ACCURATE_MATCH so return the possibly weaker match
							| SUPER_INVOCATION_FLAVOR; // TODO: not an invocation really but ref -> 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, char[] packageName, boolean isDefault) {
	if (type == null) return INACCURATE_MATCH;

	int level = resolveLevelForType(simplePattern, qualifiedPattern, type);
	if (level != IMPOSSIBLE_MATCH) {
		if (isDefault && !CharOperation.equals(packageName, type.qualifiedPackageName())) {
			return 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, packageName, isDefault);
		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, packageName, isDefault);
		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, char[][][] superTypeNames, boolean methodAlreadyVerified) {
	char[][] compoundName = type.compoundName;
	for (int i = 0, max = superTypeNames.length; i < max; i++) {
		if (CharOperation.equals(superTypeNames[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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy