| /******************************************************************************* |
| * Copyright (c) 2000, 2008 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 |
| * Dmitry Stalnov (dstalnov@fusionone.com) - contributed fixes for: |
| * o bug "Inline refactoring showed bogus error" (see bugzilla |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=42753) |
| * o Allow 'this' constructor to be inlined |
| * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38093) |
| *******************************************************************************/ |
| package org.eclipse.wst.jsdt.internal.corext.refactoring.code; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| import org.eclipse.wst.jsdt.core.ITypeRoot; |
| import org.eclipse.wst.jsdt.core.JavaScriptModelException; |
| import org.eclipse.wst.jsdt.core.compiler.IProblem; |
| import org.eclipse.wst.jsdt.core.dom.ASTNode; |
| import org.eclipse.wst.jsdt.core.dom.ASTVisitor; |
| import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.AnonymousClassDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.ArrayAccess; |
| import org.eclipse.wst.jsdt.core.dom.Block; |
| import org.eclipse.wst.jsdt.core.dom.ClassInstanceCreation; |
| import org.eclipse.wst.jsdt.core.dom.Expression; |
| import org.eclipse.wst.jsdt.core.dom.FieldAccess; |
| import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.FunctionInvocation; |
| import org.eclipse.wst.jsdt.core.dom.IBinding; |
| import org.eclipse.wst.jsdt.core.dom.IFunctionBinding; |
| import org.eclipse.wst.jsdt.core.dom.ITypeBinding; |
| import org.eclipse.wst.jsdt.core.dom.IVariableBinding; |
| import org.eclipse.wst.jsdt.core.dom.Modifier; |
| import org.eclipse.wst.jsdt.core.dom.Name; |
| import org.eclipse.wst.jsdt.core.dom.ReturnStatement; |
| import org.eclipse.wst.jsdt.core.dom.SimpleName; |
| import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.StructuralPropertyDescriptor; |
| import org.eclipse.wst.jsdt.core.dom.SuperConstructorInvocation; |
| import org.eclipse.wst.jsdt.core.dom.SuperMethodInvocation; |
| import org.eclipse.wst.jsdt.core.dom.ThisExpression; |
| import org.eclipse.wst.jsdt.core.dom.TypeDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.wst.jsdt.internal.corext.codemanipulation.ImportReferencesCollector; |
| import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.wst.jsdt.internal.corext.dom.LocalVariableIndex; |
| import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages; |
| import org.eclipse.wst.jsdt.internal.corext.refactoring.base.JavaStatusContext; |
| import org.eclipse.wst.jsdt.internal.corext.refactoring.code.flow.FlowContext; |
| import org.eclipse.wst.jsdt.internal.corext.refactoring.code.flow.FlowInfo; |
| import org.eclipse.wst.jsdt.internal.corext.refactoring.code.flow.InOutFlowAnalyzer; |
| |
| class SourceAnalyzer { |
| |
| public static class NameData { |
| private String fName; |
| private List fReferences; |
| public NameData(String n) { |
| fName= n; |
| fReferences= new ArrayList(2); |
| } |
| public String getName() { |
| return fName; |
| } |
| public void addReference(SimpleName ref) { |
| fReferences.add(ref); |
| } |
| public List references() { |
| return fReferences; |
| } |
| } |
| |
| private class ActivationAnalyzer extends ASTVisitor { |
| public RefactoringStatus status= new RefactoringStatus(); |
| private ASTNode fLastNode= getLastNode(); |
| private IFunctionBinding fBinding= getBinding(); |
| public boolean visit(ReturnStatement node) { |
| if (node != fLastNode) { |
| fInterruptedExecutionFlow= true; |
| } |
| return true; |
| } |
| public boolean visit(TypeDeclaration node) { |
| return false; |
| } |
| public boolean visit(AnonymousClassDeclaration node) { |
| return false; |
| } |
| public boolean visit(FunctionInvocation node) { |
| IFunctionBinding methodBinding= node.resolveMethodBinding(); |
| if (methodBinding != null) |
| methodBinding.getMethodDeclaration(); |
| if (fBinding != null && methodBinding != null && fBinding.isEqualTo(methodBinding) && !status.hasFatalError()) { |
| status.addFatalError(RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_recursive_call); |
| return false; |
| } |
| return true; |
| } |
| public boolean visit(SimpleName node) { |
| IBinding binding= node.resolveBinding(); |
| if (binding == null && !status.hasFatalError()) { |
| // fixes bug #42753 |
| if (!ASTNodes.isLabel(node)) { |
| status.addFatalError( |
| RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_declaration_has_errors, |
| JavaStatusContext.create(fTypeRoot, fDeclaration)); |
| return false; |
| } |
| } |
| return true; |
| } |
| public boolean visit(ThisExpression node) { |
| if (node.getQualifier() != null) { |
| status.addFatalError( |
| RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_qualified_this_expressions, |
| JavaStatusContext.create(fTypeRoot, node)); |
| return false; |
| } |
| return true; |
| } |
| private ASTNode getLastNode() { |
| List statements= fDeclaration.getBody().statements(); |
| if (statements.size() == 0) |
| return null; |
| return (ASTNode)statements.get(statements.size() - 1); |
| } |
| private IFunctionBinding getBinding() { |
| IFunctionBinding result= fDeclaration.resolveBinding(); |
| if (result != null) |
| return result.getMethodDeclaration(); |
| return result; |
| } |
| } |
| |
| private class UpdateCollector extends ASTVisitor { |
| private int fTypeCounter; |
| public boolean visit(TypeDeclaration node) { |
| return visitType(node); |
| } |
| public void endVisit(TypeDeclaration node) { |
| fTypeCounter--; |
| } |
| private boolean visitType(AbstractTypeDeclaration node) { |
| if (fTypeCounter++ == 0) { |
| addNameReference(node.getName()); |
| } |
| return true; |
| } |
| public boolean visit(AnonymousClassDeclaration node) { |
| fTypeCounter++; |
| return true; |
| } |
| public void endVisit(AnonymousClassDeclaration node) { |
| fTypeCounter--; |
| } |
| public boolean visit(FieldAccess node) { |
| // only visit the expression and not the simple name |
| node.getExpression().accept(this); |
| addReferencesToName(node.getName()); |
| return false; |
| } |
| public boolean visit(FunctionDeclaration node) { |
| if (node.isConstructor()) { |
| AbstractTypeDeclaration decl= (AbstractTypeDeclaration) ASTNodes.getParent(node, AbstractTypeDeclaration.class); |
| NameData name= (NameData)fNames.get(decl.getName().resolveBinding()); |
| if (name != null) { |
| name.addReference(node.getName()); |
| } |
| } |
| return true; |
| } |
| public boolean visit(FunctionInvocation node) { |
| if (fTypeCounter == 0) { |
| Expression receiver= node.getExpression(); |
| if (receiver == null && !isStaticallyImported(node.getName())) { |
| fImplicitReceivers.add(node); |
| } |
| } |
| return true; |
| } |
| public boolean visit(SuperMethodInvocation node) { |
| if (fTypeCounter == 0) { |
| fHasSuperMethodInvocation= true; |
| } |
| return true; |
| } |
| public boolean visist(SuperConstructorInvocation node) { |
| if (fTypeCounter == 0) { |
| fHasSuperMethodInvocation= true; |
| } |
| return true; |
| } |
| public boolean visit(ClassInstanceCreation node) { |
| if (fTypeCounter == 0) { |
| Expression receiver= node.getExpression(); |
| if (receiver == null) { |
| if (node.resolveTypeBinding().isLocal()) |
| fImplicitReceivers.add(node); |
| } |
| } |
| return true; |
| } |
| public boolean visit(SingleVariableDeclaration node) { |
| if (fTypeCounter == 0) |
| addNameReference(node.getName()); |
| return true; |
| } |
| public boolean visit(VariableDeclarationFragment node) { |
| if (fTypeCounter == 0) |
| addNameReference(node.getName()); |
| return true; |
| } |
| public boolean visit(SimpleName node) { |
| addReferencesToName(node); |
| IBinding binding= node.resolveBinding(); |
| if (binding instanceof ITypeBinding) { |
| ITypeBinding type= (ITypeBinding)binding; |
| if (type.isTypeVariable()) { |
| addTypeVariableReference(type, node); |
| } |
| } else if (binding instanceof IVariableBinding) { |
| IVariableBinding vb= (IVariableBinding)binding; |
| if (vb.isField() && ! isStaticallyImported(node)) { |
| Name topName= ASTNodes.getTopMostName(node); |
| if (node == topName || node == ASTNodes.getLeftMostSimpleName(topName)) { |
| StructuralPropertyDescriptor location= node.getLocationInParent(); |
| if (location != SingleVariableDeclaration.NAME_PROPERTY |
| && location != VariableDeclarationFragment.NAME_PROPERTY) { |
| fImplicitReceivers.add(node); |
| } |
| } |
| } else if (!vb.isField()) { |
| // we have a local. Check if it is a parameter. |
| ParameterData data= (ParameterData)fParameters.get(binding); |
| if (data != null) { |
| ASTNode parent= node.getParent(); |
| if (parent instanceof Expression) { |
| int precedence= OperatorPrecedence.getValue((Expression)parent); |
| if (precedence != -1) { |
| data.setOperatorPrecedence(node, precedence); |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| public boolean visit(ThisExpression node) { |
| if (fTypeCounter == 0) { |
| fImplicitReceivers.add(node); |
| } |
| return true; |
| } |
| private void addReferencesToName(SimpleName node) { |
| IBinding binding= node.resolveBinding(); |
| ParameterData data= (ParameterData)fParameters.get(binding); |
| if (data != null) |
| data.addReference(node); |
| |
| NameData name= (NameData)fNames.get(binding); |
| if (name != null) |
| name.addReference(node); |
| } |
| private void addNameReference(SimpleName name) { |
| fNames.put(name.resolveBinding(), new NameData(name.getIdentifier())); |
| } |
| private void addTypeVariableReference(ITypeBinding variable, SimpleName name) { |
| NameData data= (NameData)fTypeParameterMapping.get(variable); |
| if (data == null) { |
| data= (NameData)fMethodTypeParameterMapping.get(variable); |
| } |
| data.addReference(name); |
| } |
| private boolean isStaticallyImported(Name name) { |
| return fStaticsToImport.contains(name); |
| } |
| } |
| |
| private class VarargAnalyzer extends ASTVisitor { |
| private IBinding fParameter; |
| public VarargAnalyzer(IBinding parameter) { |
| fParameter= parameter; |
| } |
| public boolean visit(ArrayAccess node) { |
| Expression array= node.getArray(); |
| if (array instanceof SimpleName && fParameter.isEqualTo(((SimpleName)array).resolveBinding())) { |
| fArrayAccess= true; |
| } |
| return true; |
| } |
| } |
| |
| private ITypeRoot fTypeRoot; |
| private FunctionDeclaration fDeclaration; |
| private Map fParameters; |
| private Map fNames; |
| private List fImplicitReceivers; |
| |
| private boolean fArrayAccess; |
| private boolean fHasSuperMethodInvocation; |
| |
| private List/*<Name>*/ fTypesToImport; |
| private List/*<Name>*/ fStaticsToImport; |
| |
| private List/*<NameData>*/ fTypeParameterReferences; |
| private Map/*<ITypeBinding, NameData>*/ fTypeParameterMapping; |
| |
| private List/*<NameData>*/ fMethodTypeParameterReferences; |
| private Map/*<ITypeBinding, NameData>*/ fMethodTypeParameterMapping; |
| |
| private boolean fInterruptedExecutionFlow; |
| |
| public SourceAnalyzer(ITypeRoot typeRoot, FunctionDeclaration declaration) { |
| super(); |
| fTypeRoot= typeRoot; |
| fDeclaration= declaration; |
| } |
| |
| public boolean isExecutionFlowInterrupted() { |
| return fInterruptedExecutionFlow; |
| } |
| |
| public RefactoringStatus checkActivation() throws JavaScriptModelException { |
| RefactoringStatus result= new RefactoringStatus(); |
| if (!fTypeRoot.isStructureKnown()) { |
| result.addFatalError( |
| RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_syntax_errors, |
| JavaStatusContext.create(fTypeRoot)); |
| return result; |
| } |
| IProblem[] problems= ASTNodes.getProblems(fDeclaration, ASTNodes.NODE_ONLY, ASTNodes.ERROR); |
| if (problems.length > 0) { |
| result.addFatalError( |
| RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_declaration_has_errors, |
| JavaStatusContext.create(fTypeRoot, fDeclaration)); |
| return result; |
| } |
| final IFunctionBinding declarationBinding= fDeclaration.resolveBinding(); |
| if (declarationBinding != null) { |
| final int modifiers= declarationBinding.getModifiers(); |
| if (Modifier.isAbstract(modifiers)) { |
| result.addFatalError(RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_abstract_methods, JavaStatusContext.create(fTypeRoot, fDeclaration)); |
| return result; |
| } else if (Modifier.isNative(modifiers)) { |
| result.addFatalError(RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_native_methods, JavaStatusContext.create(fTypeRoot, fDeclaration)); |
| return result; |
| } |
| } else { |
| result.addFatalError(RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_methoddeclaration_has_errors, JavaStatusContext.create(fTypeRoot)); |
| return result; |
| } |
| ActivationAnalyzer analyzer= new ActivationAnalyzer(); |
| fDeclaration.accept(analyzer); |
| result.merge(analyzer.status); |
| if (!result.hasFatalError()) { |
| List parameters= fDeclaration.parameters(); |
| fParameters= new HashMap(parameters.size() * 2); |
| for (Iterator iter= parameters.iterator(); iter.hasNext();) { |
| SingleVariableDeclaration element= (SingleVariableDeclaration) iter.next(); |
| IVariableBinding binding= element.resolveBinding(); |
| if (binding == null) { |
| result.addFatalError( |
| RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_declaration_has_errors, |
| JavaStatusContext.create(fTypeRoot, fDeclaration)); |
| return result; |
| } |
| fParameters.put(binding, element.getProperty(ParameterData.PROPERTY)); |
| } |
| fNames= new HashMap(); |
| fImplicitReceivers= new ArrayList(2); |
| |
| fTypeParameterReferences= new ArrayList(0); |
| fTypeParameterMapping= new HashMap(); |
| ITypeBinding declaringType= declarationBinding.getDeclaringClass(); |
| if (declaringType == null) { |
| result.addFatalError( |
| RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_typedeclaration_has_errors, |
| JavaStatusContext.create(fTypeRoot)); |
| return result; |
| } |
| ITypeBinding[] typeParameters= declaringType.getTypeParameters(); |
| for (int i= 0; i < typeParameters.length; i++) { |
| NameData data= new NameData(typeParameters[i].getName()); |
| fTypeParameterReferences.add(data); |
| fTypeParameterMapping.put(typeParameters[i], data); |
| } |
| |
| fMethodTypeParameterReferences= new ArrayList(0); |
| fMethodTypeParameterMapping= new HashMap(); |
| IFunctionBinding method= declarationBinding; |
| typeParameters= method.getTypeParameters(); |
| for (int i= 0; i < typeParameters.length; i++) { |
| NameData data= new NameData(typeParameters[i].getName()); |
| fMethodTypeParameterReferences.add(data); |
| fMethodTypeParameterMapping.put(typeParameters[i], data); |
| } |
| |
| } |
| if (fDeclaration.isVarargs()) { |
| List parameters= fDeclaration.parameters(); |
| VarargAnalyzer vAnalyzer= new VarargAnalyzer( |
| ((SingleVariableDeclaration)parameters.get(parameters.size() - 1)).getName().resolveBinding()); |
| fDeclaration.getBody().accept(vAnalyzer); |
| } |
| return result; |
| } |
| |
| public void initialize() { |
| Block body= fDeclaration.getBody(); |
| // first collect the static imports. This is necessary to not mark |
| // static imported fields and methods as implicit visible. |
| fTypesToImport= new ArrayList(); |
| fStaticsToImport= new ArrayList(); |
| ImportReferencesCollector collector= new ImportReferencesCollector( |
| fTypeRoot.getJavaScriptProject(), null, fTypesToImport, fStaticsToImport); |
| body.accept(collector); |
| |
| // Now collect implicit references and name references |
| body.accept(new UpdateCollector()); |
| |
| int numberOfLocals= LocalVariableIndex.perform(fDeclaration); |
| FlowContext context= new FlowContext(0, numberOfLocals + 1); |
| context.setConsiderAccessMode(true); |
| context.setComputeMode(FlowContext.MERGE); |
| InOutFlowAnalyzer flowAnalyzer= new InOutFlowAnalyzer(context); |
| FlowInfo info= flowAnalyzer.perform(getStatements()); |
| |
| for (Iterator iter= fDeclaration.parameters().iterator(); iter.hasNext();) { |
| SingleVariableDeclaration element= (SingleVariableDeclaration) iter.next(); |
| IVariableBinding binding= element.resolveBinding(); |
| ParameterData data= (ParameterData)element.getProperty(ParameterData.PROPERTY); |
| data.setAccessMode(info.getAccessMode(context, binding)); |
| } |
| } |
| |
| public Collection getUsedNames() { |
| return fNames.values(); |
| } |
| |
| public List getImplicitReceivers() { |
| return fImplicitReceivers; |
| } |
| |
| public List getTypesToImport() { |
| return fTypesToImport; |
| } |
| |
| public List getStaticsToImport() { |
| return fStaticsToImport; |
| } |
| |
| public List getTypeParameterReferences() { |
| return fTypeParameterReferences; |
| } |
| |
| public List getMethodTypeParameterReferences() { |
| return fMethodTypeParameterReferences; |
| } |
| |
| public boolean hasArrayAccess() { |
| return fArrayAccess; |
| } |
| |
| public boolean hasSuperMethodInvocation() { |
| return fHasSuperMethodInvocation; |
| } |
| |
| private ASTNode[] getStatements() { |
| List statements= fDeclaration.getBody().statements(); |
| return (ASTNode[]) statements.toArray(new ASTNode[statements.size()]); |
| } |
| |
| } |