blob: b90bc14ab29f9008d883fcfde835bd2bbcba4ef3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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:
* Jesper Kamstrup Linnet (eclipse@kamstrup-linnet.dk) - initial API and implementation
* (report 36180: Callers/Callees view)
*******************************************************************************/
package org.eclipse.jdt.internal.corext.callhierarchy;
import java.util.Collection;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
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.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
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.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.ui.JavaPlugin;
class CalleeAnalyzerVisitor extends ASTVisitor {
private CallSearchResultCollector fSearchResults;
private IMethod fMethod;
private CompilationUnit fCompilationUnit;
private IProgressMonitor fProgressMonitor;
private int fMethodEndPosition;
private int fMethodStartPosition;
CalleeAnalyzerVisitor(IMethod method, CompilationUnit compilationUnit, IProgressMonitor progressMonitor) {
fSearchResults = new CallSearchResultCollector();
this.fMethod = method;
this.fCompilationUnit= compilationUnit;
this.fProgressMonitor = progressMonitor;
try {
ISourceRange sourceRange = method.getSourceRange();
this.fMethodStartPosition = sourceRange.getOffset();
this.fMethodEndPosition = fMethodStartPosition + sourceRange.getLength();
} catch (JavaModelException jme) {
JavaPlugin.log(jme);
}
}
/**
* Method getCallees.
*
* @return CallerElement
*/
public Map getCallees() {
return fSearchResults.getCallers();
}
/* (non-Javadoc)
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.ClassInstanceCreation)
*/
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)
*/
public boolean visit(ConstructorInvocation node) {
progressMonitorWorked(1);
if (!isFurtherTraversalNecessary(node)) {
return false;
}
if (isNodeWithinMethod(node)) {
addMethodCall(node.resolveConstructorBinding(), node);
}
return true;
}
/**
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodDeclaration)
*/
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)
*/
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)
*/
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.
*
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodInvocation)
*/
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)
*/
public boolean visit(AnonymousClassDeclaration node) {
return isNodeEnclosingMethod(node);
}
/**
* Adds the specified method binding to the search results.
*
* @param calledMethodBinding
* @param 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(fMethod, referencedMember, position, position + node.getLength(), number < 1 ? 1 : number);
}
} catch (JavaModelException jme) {
JavaPlugin.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 CallHierarchy.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 implementingMethods = CallHierarchy.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();
}
}
}
}