blob: 46c0b9f6199165e7cc13c28dd356943781b55b25 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.search;
import java.util.ArrayList;
import java.util.List;
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.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
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.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;
import org.eclipse.jdt.core.dom.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.util.Messages;
public class ExceptionOccurrencesFinder extends ASTVisitor implements IOccurrencesFinder {
public static final String ID= "ExceptionOccurrencesFinder"; //$NON-NLS-1$
public static final String IS_EXCEPTION= "isException"; //$NON-NLS-1$
private CompilationUnit fASTRoot;
private ASTNode fSelectedNode;
private ITypeBinding fException;
private ASTNode fStart;
private TryStatement fTryStatement;
private List<OccurrenceLocation> fResult;
private String fDescription;
private List<ITypeBinding> fCaughtExceptions;
public ExceptionOccurrencesFinder() {
fResult= new ArrayList<>();
}
@Override
public String initialize(CompilationUnit root, int offset, int length) {
return initialize(root, NodeFinder.perform(root, offset, length));
}
@Override
public String initialize(CompilationUnit root, ASTNode node) {
fASTRoot= root;
if (node == null)
return SearchMessages.ExceptionOccurrencesFinder_no_exception;
// The ExceptionOccurrencesFinder selects the whole type, no matter what part of it was selected. MethodExitsFinder behaves similar.
if (node instanceof Name) {
node= ASTNodes.getTopMostName((Name) node);
}
if (node.getLocationInParent() == TagElement.FRAGMENTS_PROPERTY) {
MethodDeclaration method= ASTResolving.findParentMethodDeclaration(node);
if (method == null)
return SearchMessages.ExceptionOccurrencesFinder_no_exception;
// in Javadoc tag:
TagElement tagElement= (TagElement) node.getParent();
String tagName= tagElement.getTagName();
if (node instanceof Name
&& node == tagElement.fragments().get(0)
&& (TagElement.TAG_EXCEPTION.equals(tagName) || TagElement.TAG_THROWS.equals(tagName))) {
fSelectedNode= node;
fException= ((Name) node).resolveTypeBinding();
fStart= method;
}
} else {
Type type= ASTNodes.getTopMostType(node);
if (type == null) {
return SearchMessages.ExceptionOccurrencesFinder_no_exception;
}
// in method's "throws" list:
if (type.getLocationInParent() == MethodDeclaration.THROWN_EXCEPTION_TYPES_PROPERTY) {
MethodDeclaration method= ASTResolving.findParentMethodDeclaration(node);
if (method == null)
return SearchMessages.ExceptionOccurrencesFinder_no_exception;
fSelectedNode= type;
fException= type.resolveBinding();
fStart= method;
}
// in catch clause:
Type topType= type;
if (type.getLocationInParent() == UnionType.TYPES_PROPERTY) {
topType= (Type) type.getParent();
}
if (topType.getLocationInParent() == SingleVariableDeclaration.TYPE_PROPERTY
&& topType.getParent().getLocationInParent() == CatchClause.EXCEPTION_PROPERTY) {
fSelectedNode= type;
fException= type.resolveBinding();
fTryStatement= (TryStatement) topType.getParent().getParent().getParent();
fStart= fTryStatement.getBody();
}
}
if (fException == null || fStart == null)
return SearchMessages.ExceptionOccurrencesFinder_no_exception;
fDescription= Messages.format(SearchMessages.ExceptionOccurrencesFinder_occurrence_description, BasicElementLabels.getJavaElementName(fException.getName()));
return null;
}
private void performSearch() {
fCaughtExceptions= new ArrayList<>();
fStart.accept(this);
if (fTryStatement != null) {
handleResourceDeclarations(fTryStatement);
}
if (fSelectedNode != null) {
fResult.add(new OccurrenceLocation(fSelectedNode.getStartPosition(), fSelectedNode.getLength(), F_EXCEPTION_DECLARATION, fDescription));
}
}
private void handleResourceDeclarations(TryStatement tryStatement) {
List<Expression> resources= tryStatement.resources();
for (Expression expression : resources) {
expression.accept(this);
}
//check if the exception is thrown as a result of resource#close()
boolean exitMarked= false;
for (Expression variable : resources) {
ITypeBinding typeBinding= null;
if (variable instanceof VariableDeclarationExpression) {
typeBinding= ((VariableDeclarationExpression) variable).getType().resolveBinding();
} else if (variable instanceof Name) {
typeBinding= ((Name) variable).resolveTypeBinding();
}
if (typeBinding != null) {
IMethodBinding methodBinding= Bindings.findMethodInHierarchy(typeBinding, "close", new ITypeBinding[0]); //$NON-NLS-1$
if (methodBinding != null) {
for (ITypeBinding exceptionType : methodBinding.getExceptionTypes()) {
if (matches(exceptionType)) {
// a close() throws the caught exception
// mark name of resource
if (variable instanceof VariableDeclarationExpression) {
VariableDeclarationExpression varDeclExpr= (VariableDeclarationExpression) variable;
for (VariableDeclarationFragment fragment : (List<VariableDeclarationFragment>) varDeclExpr.fragments()) {
SimpleName name= fragment.getName();
fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fDescription));
}
} else if (variable instanceof Name) {
Name name= (Name) variable;
fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fDescription));
}
if (!exitMarked) {
// mark exit position
exitMarked= true;
Block body= tryStatement.getBody();
int offset= body.getStartPosition() + body.getLength() - 1; // closing bracket of try block
fResult.add(new OccurrenceLocation(offset, 1, 0, Messages.format(SearchMessages.ExceptionOccurrencesFinder_occurrence_implicit_close_description,
BasicElementLabels.getJavaElementName(fException.getName()))));
}
}
}
}
}
}
}
@Override
public OccurrenceLocation[] getOccurrences() {
performSearch();
if (fResult.isEmpty())
return null;
return fResult.toArray(new OccurrenceLocation[fResult.size()]);
}
@Override
public int getSearchKind() {
return K_EXCEPTION_OCCURRENCE;
}
@Override
public CompilationUnit getASTRoot() {
return fASTRoot;
}
@Override
public String getJobLabel() {
return SearchMessages.ExceptionOccurrencesFinder_searchfor ;
}
@Override
public String getElementName() {
if (fSelectedNode != null) {
return ASTNodes.asString(fSelectedNode);
}
return null;
}
@Override
public String getUnformattedPluralLabel() {
return SearchMessages.ExceptionOccurrencesFinder_label_plural;
}
@Override
public String getUnformattedSingularLabel() {
return SearchMessages.ExceptionOccurrencesFinder_label_singular;
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
return false;
}
@Override
public boolean visit(CastExpression node) {
if ("java.lang.ClassCastException".equals(fException.getQualifiedName())) { //$NON-NLS-1$
Type type= node.getType();
fResult.add(new OccurrenceLocation(type.getStartPosition(), type.getLength(), 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(ClassInstanceCreation node) {
if (matches(node.resolveConstructorBinding())) {
Type type= node.getType();
fResult.add(new OccurrenceLocation(type.getStartPosition(), type.getLength(), 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(ConstructorInvocation node) {
if (matches(node.resolveConstructorBinding())) {
// mark 'this'
fResult.add(new OccurrenceLocation(node.getStartPosition(), 4, 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(LambdaExpression node) {
return false;
}
@Override
public boolean visit(MethodDeclaration node) {
Javadoc javadoc= node.getJavadoc();
if (javadoc != null) {
List<TagElement> tags= javadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (TagElement.TAG_EXCEPTION.equals(tagName) || TagElement.TAG_THROWS.equals(tagName)) {
ASTNode name= (ASTNode) tag.fragments().get(0);
if (name instanceof Name) {
if (name != fSelectedNode && Bindings.equals(fException, ((Name) name).resolveBinding())) {
fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fDescription));
}
}
}
}
}
List<Type> thrownExceptionTypes= node.thrownExceptionTypes();
for (Type type : thrownExceptionTypes) {
if (type != fSelectedNode && Bindings.equals(fException, type.resolveBinding())) {
fResult.add(new OccurrenceLocation(type.getStartPosition(), type.getLength(), 0, fDescription));
}
}
Block body= node.getBody();
if (body != null) {
node.getBody().accept(this);
}
return false;
}
@Override
public boolean visit(MethodInvocation node) {
if (matches(node.resolveMethodBinding())) {
SimpleName name= node.getName();
fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(SuperConstructorInvocation node) {
if (matches(node.resolveConstructorBinding())) {
// mark 'super'
fResult.add(new OccurrenceLocation(node.getStartPosition(), 5, 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(SuperMethodInvocation node) {
if (matches(node.resolveMethodBinding())) {
SimpleName name= node.getName();
fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(ThrowStatement node) {
if (matches(node.getExpression().resolveTypeBinding())) {
// mark 'throw'
fResult.add(new OccurrenceLocation(node.getStartPosition(), 5, 0, fDescription));
}
return super.visit(node);
}
@Override
public boolean visit(TryStatement node) {
int currentSize= fCaughtExceptions.size();
List<CatchClause> catchClauses= node.catchClauses();
for (CatchClause catchClause : catchClauses) {
Type type= catchClause.getException().getType();
if (type instanceof UnionType) {
List<Type> types= ((UnionType) type).types();
for (Type type2 : types) {
addCaughtException(type2);
}
} else {
addCaughtException(type);
}
}
node.getBody().accept(this);
handleResourceDeclarations(node);
int toRemove= fCaughtExceptions.size() - currentSize;
for (int i= toRemove; i > 0; i--) {
fCaughtExceptions.remove(currentSize);
}
// visit catch and finally
for (CatchClause catchClause : catchClauses) {
catchClause.accept(this);
}
if (node.getFinally() != null)
node.getFinally().accept(this);
// return false. We have visited the body by ourselves.
return false;
}
private void addCaughtException(Type type) {
ITypeBinding typeBinding= type.resolveBinding();
if (typeBinding != null) {
fCaughtExceptions.add(typeBinding);
}
}
@Override
public boolean visit(TypeDeclarationStatement node) {
// don't dive into local type declarations.
return false;
}
private boolean matches(IMethodBinding binding) {
if (binding == null)
return false;
for (ITypeBinding exception : binding.getExceptionTypes()) {
if(matches(exception))
return true;
}
return false;
}
private boolean matches(ITypeBinding exception) {
if (exception == null)
return false;
if (isCaught(exception))
return false;
while (exception != null) {
if (Bindings.equals(fException, exception))
return true;
exception= exception.getSuperclass();
}
return false;
}
private boolean isCaught(ITypeBinding binding) {
for (ITypeBinding catchException : fCaughtExceptions) {
if (catches(catchException, binding))
return true;
}
return false;
}
private boolean catches(ITypeBinding catchTypeBinding, ITypeBinding throwTypeBinding) {
while (throwTypeBinding != null) {
if (throwTypeBinding == catchTypeBinding)
return true;
throwTypeBinding= throwTypeBinding.getSuperclass();
}
return false;
}
public IOccurrencesFinder getNewInstance() {
return new ExceptionOccurrencesFinder();
}
@Override
public String getID() {
return ID;
}
}