blob: aea1ce848bd8e8cbd8eebeaa9c9fd51e797ee68c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Jesper Kamstrup Linnet (eclipse@kamstrup-linnet.dk) - initial API and implementation
* (report 36180: Callers/Callees view)
* Red Hat Inc. - refactored to jdt.core.manipulation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.callhierarchy;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
class CalleeAnalyzerVisitor extends HierarchicalASTVisitor {
private final CallSearchResultCollector fSearchResults;
private final IMember fMember;
private final CompilationUnit fCompilationUnit;
private final IProgressMonitor fProgressMonitor;
private int fMethodEndPosition;
private int fMethodStartPosition;
CalleeAnalyzerVisitor(IMember member, CompilationUnit compilationUnit, IProgressMonitor progressMonitor) {
fSearchResults = new CallSearchResultCollector();
this.fMember = member;
this.fCompilationUnit= compilationUnit;
this.fProgressMonitor = progressMonitor;
try {
ISourceRange sourceRange = member.getSourceRange();
this.fMethodStartPosition = sourceRange.getOffset();
this.fMethodEndPosition = fMethodStartPosition + sourceRange.getLength();
} catch (JavaModelException jme) {
JavaManipulationPlugin.log(jme);
}
}
/**
* @return a map from handle identifier ({@link String}) to {@link MethodCall}
*/
public Map<String, MethodCall> getCallees() {
return fSearchResults.getCallers();
}
@Override
public boolean visit(ClassInstanceCreation node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
addMethodCall(node.resolveConstructorBinding(), node);
}
return true;
}
/**
* Find all constructor invocations (<code>this(...)</code>) from the called method.
* Since we only traverse into the AST on the wanted method declaration, this method
* should not hit on more constructor invocations than those in the wanted method.
*
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.ConstructorInvocation)
*/
@Override
public boolean visit(ConstructorInvocation node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
addMethodCall(node.resolveConstructorBinding(), node);
}
return true;
}
/**
* @see HierarchicalASTVisitor#visit(org.eclipse.jdt.core.dom.AbstractTypeDeclaration)
*/
@Override
public boolean visit(AbstractTypeDeclaration node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
List<BodyDeclaration> bodyDeclarations= node.bodyDeclarations();
for (Iterator<BodyDeclaration> iter= bodyDeclarations.iterator(); iter.hasNext(); ) {
BodyDeclaration bodyDeclaration= iter.next();
if (bodyDeclaration instanceof MethodDeclaration) {
MethodDeclaration child= (MethodDeclaration) bodyDeclaration;
if (child.isConstructor()) {
addMethodCall(child.resolveBinding(), child.getName());
}
}
}
return false;
}
return true;
}
/**
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodDeclaration)
*/
@Override
public boolean visit(MethodDeclaration node) {
progressMonitorWorked(1);
return isFurtherTraversalNecessary(node);
}
/**
* Find all method invocations from the called method. Since we only traverse into
* the AST on the wanted method declaration, this method should not hit on more
* method invocations than those in the wanted method.
*
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodInvocation)
*/
@Override
public boolean visit(MethodInvocation node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
addMethodCall(node.resolveMethodBinding(), node);
}
return true;
}
/**
* Find invocations of the supertype's constructor from the called method
* (=constructor). Since we only traverse into the AST on the wanted method
* declaration, this method should not hit on more method invocations than those in
* the wanted method.
*
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.SuperConstructorInvocation)
*/
@Override
public boolean visit(SuperConstructorInvocation node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
addMethodCall(node.resolveConstructorBinding(), node);
}
return true;
}
/**
* Find all method invocations from the called method. Since we only traverse into
* the AST on the wanted method declaration, this method should not hit on more
* method invocations than those in the wanted method.
* @param node node to visit
* @return whether children should be visited
*
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodInvocation)
*/
@Override
public boolean visit(SuperMethodInvocation node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
addMethodCall(node.resolveMethodBinding(), node);
}
return true;
}
/**
* When an anonymous class declaration is reached, the traversal should not go further since it's not
* supposed to consider calls inside the anonymous inner class as calls from the outer method.
*
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.AnonymousClassDeclaration)
*/
@Override
public boolean visit(AnonymousClassDeclaration node) {
return isNodeEnclosingMethod(node);
}
/**
* Adds the specified method binding to the search results.
*
* @param calledMethodBinding the called method binding
* @param node the AST node
*/
protected void addMethodCall(IMethodBinding calledMethodBinding, ASTNode node) {
try {
if (calledMethodBinding != null) {
fProgressMonitor.worked(1);
ITypeBinding calledTypeBinding = calledMethodBinding.getDeclaringClass();
IType calledType = null;
if (!calledTypeBinding.isAnonymous()) {
calledType = (IType) calledTypeBinding.getJavaElement();
} else {
if (!"java.lang.Object".equals(calledTypeBinding.getSuperclass().getQualifiedName())) { //$NON-NLS-1$
calledType= (IType) calledTypeBinding.getSuperclass().getJavaElement();
} else {
calledType = (IType) calledTypeBinding.getInterfaces()[0].getJavaElement();
}
}
IMethod calledMethod = findIncludingSupertypes(calledMethodBinding,
calledType, fProgressMonitor);
IMember referencedMember= null;
if (calledMethod == null) {
if (calledMethodBinding.isConstructor() && calledMethodBinding.getParameterTypes().length == 0) {
referencedMember= calledType;
}
} else {
if (calledType.isInterface()) {
calledMethod = findImplementingMethods(calledMethod);
}
if (!isIgnoredBySearchScope(calledMethod)) {
referencedMember= calledMethod;
}
}
final int position= node.getStartPosition();
final int number= fCompilationUnit.getLineNumber(position);
fSearchResults.addMember(fMember, referencedMember, position, position + node.getLength(), number < 1 ? 1 : number);
}
} catch (JavaModelException jme) {
JavaManipulationPlugin.log(jme);
}
}
private static IMethod findIncludingSupertypes(IMethodBinding method, IType type, IProgressMonitor pm) throws JavaModelException {
IMethod inThisType= Bindings.findMethod(method, type);
if (inThisType != null)
return inThisType;
IType[] superTypes= JavaModelUtil.getAllSuperTypes(type, pm);
for (int i= 0; i < superTypes.length; i++) {
IMethod m= Bindings.findMethod(method, superTypes[i]);
if (m != null)
return m;
}
return null;
}
private boolean isIgnoredBySearchScope(IMethod enclosingElement) {
if (enclosingElement != null) {
return !getSearchScope().encloses(enclosingElement);
} else {
return false;
}
}
private IJavaSearchScope getSearchScope() {
return CallHierarchyCore.getDefault().getSearchScope();
}
private boolean isNodeWithinMethod(ASTNode node) {
int nodeStartPosition = node.getStartPosition();
int nodeEndPosition = nodeStartPosition + node.getLength();
if (nodeStartPosition < fMethodStartPosition) {
return false;
}
if (nodeEndPosition > fMethodEndPosition) {
return false;
}
return true;
}
private boolean isNodeEnclosingMethod(ASTNode node) {
int nodeStartPosition = node.getStartPosition();
int nodeEndPosition = nodeStartPosition + node.getLength();
if (nodeStartPosition < fMethodStartPosition && nodeEndPosition > fMethodEndPosition) {
// Is the method completely enclosed by the node?
return true;
}
return false;
}
private boolean isFurtherTraversalNecessary(ASTNode node) {
return isNodeWithinMethod(node) || isNodeEnclosingMethod(node);
}
private IMethod findImplementingMethods(IMethod calledMethod) {
Collection<IJavaElement> implementingMethods = CallHierarchyCore.getDefault()
.getImplementingMethods(calledMethod);
if ((implementingMethods.size() == 0) || (implementingMethods.size() > 1)) {
return calledMethod;
} else {
return (IMethod) implementingMethods.iterator().next();
}
}
private void progressMonitorWorked(int work) {
if (fProgressMonitor != null) {
fProgressMonitor.worked(work);
if (fProgressMonitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}