/******************************************************************************* * 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.ui.search; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.Block; 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.EnumDeclaration; 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.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SuperMethodInvocation; 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.TypeDeclaration; 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.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.Bindings; import org.eclipse.jdt.internal.corext.dom.LocalVariableIndex; import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowContext; import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowInfo; import org.eclipse.jdt.internal.corext.refactoring.code.flow.InOutFlowAnalyzer; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; public class MethodExitsFinder extends ASTVisitor implements IOccurrencesFinder { public static final String ID= "MethodExitsFinder"; //$NON-NLS-1$ private MethodDeclaration fMethodDeclaration; private List<OccurrenceLocation> fResult; private List<ITypeBinding> fCatchedExceptions; private String fExitDescription; private CompilationUnit fASTRoot; public String initialize(CompilationUnit root, int offset, int length) { return initialize(root, NodeFinder.perform(root, offset, length)); } /** * @param root the AST root * @param node the selected node * @return returns a message if there is a problem */ public String initialize(CompilationUnit root, ASTNode node) { fASTRoot= root; if (node instanceof ReturnStatement) { fMethodDeclaration= (MethodDeclaration)ASTNodes.getParent(node, ASTNode.METHOD_DECLARATION); if (fMethodDeclaration == null) return SearchMessages.MethodExitsFinder_no_return_type_selected; return null; } Type type= null; if (node instanceof Type) { type= (Type)node; } else if (node instanceof Name) { Name name= ASTNodes.getTopMostName((Name)node); if (name.getParent() instanceof Type) { type= (Type)name.getParent(); } } if (type == null) return SearchMessages.MethodExitsFinder_no_return_type_selected; type= ASTNodes.getTopMostType(type); if (!(type.getParent() instanceof MethodDeclaration)) return SearchMessages.MethodExitsFinder_no_return_type_selected; fMethodDeclaration= (MethodDeclaration)type.getParent(); fExitDescription= Messages.format(SearchMessages.MethodExitsFinder_occurrence_exit_description, BasicElementLabels.getJavaElementName(fMethodDeclaration.getName().toString())); return null; } private void performSearch() { fResult= new ArrayList<OccurrenceLocation>(); markReferences(); if (!fResult.isEmpty()) { Type returnType= fMethodDeclaration.getReturnType2(); if (returnType != null) { String desc= Messages.format(SearchMessages.MethodExitsFinder_occurrence_return_description, BasicElementLabels.getJavaElementName(fMethodDeclaration.getName().toString())); fResult.add(new OccurrenceLocation(returnType.getStartPosition(), returnType.getLength(), 0, desc)); } } } public OccurrenceLocation[] getOccurrences() { performSearch(); if (fResult.isEmpty()) return null; return fResult.toArray(new OccurrenceLocation[fResult.size()]); } private void markReferences() { fCatchedExceptions= new ArrayList<ITypeBinding>(); boolean isVoid= true; Type returnType= fMethodDeclaration.getReturnType2(); if (returnType != null) { ITypeBinding returnTypeBinding= returnType.resolveBinding(); isVoid= returnTypeBinding != null && Bindings.isVoidType(returnTypeBinding); } fMethodDeclaration.accept(this); Block block= fMethodDeclaration.getBody(); if (block != null) { List<Statement> statements= block.statements(); if (statements.size() > 0) { Statement last= statements.get(statements.size() - 1); int maxVariableId= LocalVariableIndex.perform(fMethodDeclaration); FlowContext flowContext= new FlowContext(0, maxVariableId + 1); flowContext.setConsiderAccessMode(false); flowContext.setComputeMode(FlowContext.ARGUMENTS); InOutFlowAnalyzer flowAnalyzer= new InOutFlowAnalyzer(flowContext); FlowInfo info= flowAnalyzer.perform(new ASTNode[] {last}); if (!info.isNoReturn() && !isVoid) { if (!info.isPartialReturn()) return; } } int offset= fMethodDeclaration.getStartPosition() + fMethodDeclaration.getLength() - 1; // closing bracket fResult.add(new OccurrenceLocation(offset, 1, 0, fExitDescription)); } } @Override public boolean visit(TypeDeclaration node) { // Don't dive into a local type. return false; } @Override public boolean visit(AnonymousClassDeclaration node) { // Don't dive into a local type. return false; } @Override public boolean visit(AnnotationTypeDeclaration node) { // Don't dive into a local type. return false; } @Override public boolean visit(EnumDeclaration node) { // Don't dive into a local type. return false; } @Override public boolean visit(ReturnStatement node) { fResult.add(new OccurrenceLocation(node.getStartPosition(), node.getLength(), 0, fExitDescription)); return super.visit(node); } @Override public boolean visit(TryStatement node) { int currentSize= fCatchedExceptions.size(); List<CatchClause> catchClauses= node.catchClauses(); for (Iterator<CatchClause> iter= catchClauses.iterator(); iter.hasNext();) { Type type= iter.next().getException().getType(); if (type instanceof UnionType) { List<Type> types= ((UnionType) type).types(); for (Iterator<Type> iterator= types.iterator(); iterator.hasNext();) { addCatchedException(iterator.next()); } } else { addCatchedException(type); } } node.getBody().accept(this); if (node.getAST().apiLevel() >= AST.JLS4) { List<VariableDeclarationExpression> resources= node.resources(); for (Iterator<VariableDeclarationExpression> iterator= resources.iterator(); iterator.hasNext();) { iterator.next().accept(this); } //check if the method could exit as a result of resource#close() boolean exitMarked= false; for (VariableDeclarationExpression variable : resources) { Type type= variable.getType(); IMethodBinding methodBinding= Bindings.findMethodInHierarchy(type.resolveBinding(), "close", new ITypeBinding[0]); //$NON-NLS-1$ if (methodBinding != null) { ITypeBinding[] exceptionTypes= methodBinding.getExceptionTypes(); for (int j= 0; j < exceptionTypes.length; j++) { if (isExitPoint(exceptionTypes[j])) { // a close() throws an uncaught exception // mark name of resource for (VariableDeclarationFragment fragment : (List<VariableDeclarationFragment>) variable.fragments()) { SimpleName name= fragment.getName(); fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fExitDescription)); } if (!exitMarked) { // mark exit position exitMarked= true; Block body= node.getBody(); int offset= body.getStartPosition() + body.getLength() - 1; // closing bracket of try block fResult.add(new OccurrenceLocation(offset, 1, 0, Messages.format(SearchMessages.MethodExitsFinder_occurrence_exit_impclict_close_description, BasicElementLabels.getJavaElementName(fMethodDeclaration.getName().toString())))); } } } } } } int toRemove= fCatchedExceptions.size() - currentSize; for (int i= toRemove; i > 0; i--) { fCatchedExceptions.remove(currentSize); } // visit catch and finally for (Iterator<CatchClause> iter= catchClauses.iterator(); iter.hasNext();) { iter.next().accept(this); } if (node.getFinally() != null) node.getFinally().accept(this); // return false. We have visited the body by ourselves. return false; } private void addCatchedException(Type type) { ITypeBinding typeBinding= type.resolveBinding(); if (typeBinding != null) { fCatchedExceptions.add(typeBinding); } } @Override public boolean visit(ThrowStatement node) { ITypeBinding exception= node.getExpression().resolveTypeBinding(); if (isExitPoint(exception)) { // mark 'throw' fResult.add(new OccurrenceLocation(node.getStartPosition(), 5, 0, fExitDescription)); } return true; } @Override public boolean visit(MethodInvocation node) { if (isExitPoint(node.resolveMethodBinding())) { SimpleName name= node.getName(); fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fExitDescription)); } return true; } @Override public boolean visit(SuperMethodInvocation node) { if (isExitPoint(node.resolveMethodBinding())) { SimpleName name= node.getName(); fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fExitDescription)); } return true; } @Override public boolean visit(ClassInstanceCreation node) { if (isExitPoint(node.resolveConstructorBinding())) { Type name= node.getType(); fResult.add(new OccurrenceLocation(name.getStartPosition(), name.getLength(), 0, fExitDescription)); } return true; } @Override public boolean visit(ConstructorInvocation node) { if (isExitPoint(node.resolveConstructorBinding())) { // mark 'this' fResult.add(new OccurrenceLocation(node.getStartPosition(), 4, 0, fExitDescription)); } return true; } @Override public boolean visit(SuperConstructorInvocation node) { if (isExitPoint(node.resolveConstructorBinding())) { // mark 'super' fResult.add(new OccurrenceLocation(node.getStartPosition(), 5, 0, fExitDescription)); } return true; } private boolean isExitPoint(ITypeBinding binding) { if (binding == null) return false; return !isCatched(binding); } private boolean isExitPoint(IMethodBinding binding) { if (binding == null) return false; ITypeBinding[] exceptions= binding.getExceptionTypes(); for (int i= 0; i < exceptions.length; i++) { if (!isCatched(exceptions[i])) return true; } return false; } private boolean isCatched(ITypeBinding binding) { for (Iterator<ITypeBinding> iter= fCatchedExceptions.iterator(); iter.hasNext();) { ITypeBinding catchException= iter.next(); 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 CompilationUnit getASTRoot() { return fASTRoot; } public String getElementName() { return fMethodDeclaration.getName().getIdentifier(); } public String getID() { return ID; } public String getJobLabel() { return SearchMessages.MethodExitsFinder_job_label; } public int getSearchKind() { return IOccurrencesFinder.K_BREAK_TARGET_OCCURRENCE; } public String getUnformattedPluralLabel() { return SearchMessages.MethodExitsFinder_label_plural; } public String getUnformattedSingularLabel() { return SearchMessages.MethodExitsFinder_label_singular; } }