| /******************************************************************************* |
| * 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.dltk.internal.javascript.corext.refactoring.code; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.dltk.core.IMethod; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.internal.javascript.corext.refactoring.RefactoringCoreMessages; |
| import org.eclipse.dltk.internal.javascript.corext.refactoring.code.flow.FlowContext; |
| import org.eclipse.dltk.internal.javascript.corext.refactoring.code.flow.FlowInfo; |
| import org.eclipse.dltk.internal.javascript.corext.refactoring.code.flow.InOutFlowAnalyzer; |
| import org.eclipse.dltk.internal.javascript.corext.refactoring.code.flow.VariableBinding; |
| import org.eclipse.dltk.javascript.core.dom.CallExpression; |
| import org.eclipse.dltk.javascript.core.dom.DomPackage; |
| import org.eclipse.dltk.javascript.core.dom.Expression; |
| import org.eclipse.dltk.javascript.core.dom.FunctionExpression; |
| import org.eclipse.dltk.javascript.core.dom.Identifier; |
| import org.eclipse.dltk.javascript.core.dom.Node; |
| import org.eclipse.dltk.javascript.core.dom.Parameter; |
| import org.eclipse.dltk.javascript.core.dom.PropertyAccessExpression; |
| import org.eclipse.dltk.javascript.core.dom.ReturnStatement; |
| import org.eclipse.dltk.javascript.core.dom.Statement; |
| import org.eclipse.dltk.javascript.core.dom.ThisExpression; |
| import org.eclipse.dltk.javascript.core.dom.VariableReference; |
| import org.eclipse.dltk.javascript.core.dom.rewrite.RefactoringUtils; |
| import org.eclipse.dltk.javascript.core.dom.rewrite.VariableLookup; |
| import org.eclipse.dltk.javascript.core.dom.util.DomSwitch; |
| import org.eclipse.emf.common.util.TreeIterator; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| |
| |
| 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 DomSwitch<Boolean> { |
| public RefactoringStatus status= new RefactoringStatus(); |
| private Node fLastNode= getLastNode(); |
| //private IMethodBinding fBinding= getBinding(); |
| public void traverse(Node node) { |
| TreeIterator<EObject> it = node.eAllContents(); |
| while(it.hasNext()) { |
| if (doSwitch(it.next()) != null) |
| it.prune(); |
| } |
| } |
| public Boolean caseReturnStatement(ReturnStatement node) { |
| if (node != fLastNode) { |
| interruptedFlow= true; |
| } |
| return null; |
| } |
| public Boolean caseFunctionExpression(FunctionExpression node) { |
| return false; |
| } |
| public Boolean caseVariableReference(VariableReference node) { |
| IModelElement[] elems; |
| try { |
| elems = cu.codeSelect(node.getBegin(), node.getEnd()-node.getBegin()); |
| } catch (ModelException e) { |
| status.addFatalError(RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_declaration_has_errors); |
| return false; |
| } |
| for(IModelElement elem : elems) |
| if (elem.equals(method)) { |
| status.addFatalError(RefactoringCoreMessages.InlineMethodRefactoring_SourceAnalyzer_recursive_call); |
| return false; |
| } |
| return false; |
| } |
| private Node getLastNode() { |
| List<Statement> statements= declaration.getBody().getStatements(); |
| if (statements.size() == 0) |
| return null; |
| return (Node)statements.get(statements.size() - 1); |
| } |
| } |
| |
| private class UpdateCollector extends DomSwitch<Boolean> { |
| void traverse(Node node) { |
| TreeIterator<EObject> it = node.eAllContents(); |
| while(it.hasNext()) { |
| if (doSwitch(it.next()) != null) |
| it.prune(); |
| } |
| } |
| public Boolean caseFunctionExpression(FunctionExpression node) { |
| return true; |
| } |
| /*private int fTypeCounter; |
| public boolean visit(TypeDeclaration node) { |
| return visitType(node); |
| } |
| public void endVisit(TypeDeclaration node) { |
| fTypeCounter--; |
| } |
| public boolean visit(EnumDeclaration node) { |
| return visitType(node); |
| } |
| public void endVisit(EnumDeclaration node) { |
| fTypeCounter--; |
| } |
| public boolean visit(AnnotationTypeDeclaration node) { |
| return visitType(node); |
| } |
| public void endVisit(AnnotationTypeDeclaration 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(MethodDeclaration 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 caseCallExpression(CallExpression node) { |
| //if (fTypeCounter == 0) { |
| Expression receiver = RefactoringUtils.getReceiver(node); |
| if (receiver == null) { |
| implicitReceiversCount++; |
| } |
| //} |
| return null; |
| } |
| /*public boolean visit(SuperMethodInvocation node) { |
| if (fTypeCounter == 0) { |
| fHasSuperMethodInvocation= true; |
| } |
| return true; |
| } |
| public boolean visit(SuperConstructorInvocation node) { |
| if (fTypeCounter == 0) { |
| fHasSuperMethodInvocation= true; |
| } |
| return true; |
| }*/ |
| /*public boolean visit(NewExpression 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.getExpressionPrecedence((Expression)parent); |
| if (precedence != Integer.MAX_VALUE) { |
| data.setOperatorPrecedence(precedence); |
| } |
| } |
| } |
| } |
| } |
| return true; |
| }*/ |
| public Boolean caseThisExpression(ThisExpression node) { |
| //if (fTypeCounter == 0) { |
| implicitReceiversCount++; |
| //} |
| 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 ISourceModule cu; |
| private IMethod method; |
| private FunctionExpression declaration; |
| private ParameterData[] paramData; |
| /*private Map fParameters; |
| private Map fNames;*/ |
| private int implicitReceiversCount; |
| |
| /*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 interruptedFlow; |
| |
| public SourceAnalyzer(FunctionExpression declaration, IMethod method) { |
| this.cu = method.getSourceModule(); |
| this.method = method; |
| this.declaration= declaration; |
| } |
| |
| public boolean isExecutionFlowInterrupted() { |
| return interruptedFlow; |
| } |
| |
| public RefactoringStatus checkActivation() { |
| 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 IMethodBinding 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(); |
| analyzer.traverse(declaration); |
| 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(); |
| IMethodBinding 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() { |
| //BlockStatement body= declaration.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.collect(body, fTypeRoot.getJavaProject(), null, fTypesToImport, fStaticsToImport); |
| |
| // Now collect implicit references and name references |
| new UpdateCollector().traverse(declaration.getBody()); |
| |
| Map<Identifier,VariableBinding> bindings = VariableLookup.findBindings(declaration); |
| FlowContext context= new FlowContext(bindings); |
| context.setConsiderAccessMode(true); |
| context.setComputeMode(FlowContext.Mode.MERGE); |
| InOutFlowAnalyzer flowAnalyzer= new InOutFlowAnalyzer(context); |
| List<Statement> statements = declaration.getBody().getStatements(); |
| FlowInfo info= flowAnalyzer.perform(statements.toArray(new Node[statements.size()])); |
| paramData = new ParameterData[declaration.getParameters().size()]; |
| int i=0; |
| Map<VariableBinding,ParameterData> datas = new HashMap<VariableBinding,ParameterData>(); |
| for(Parameter element : declaration.getParameters()) { |
| VariableBinding binding=bindings.get(element.getName()); |
| //ParameterData data= (ParameterData)element.getProperty(ParameterData.PROPERTY); |
| ParameterData data = paramData[i++] = new ParameterData(element.getName()); |
| data.setAccessMode(info.getAccessMode(context, binding)); |
| datas.put(binding, data); |
| } |
| |
| for(Map.Entry<Identifier,VariableBinding> entry : bindings.entrySet()) { |
| Identifier key = entry.getKey(); |
| VariableBinding value = entry.getValue(); |
| if (value.getDeclaration() != key) { |
| ParameterData data = datas.get(value); |
| if (data != null) { |
| data.addReference(key); |
| if (key.eContainer().eContainmentFeature() == DomPackage.eINSTANCE.getCallExpression_Applicant()) |
| data.setFunction(true); |
| } |
| } |
| } |
| } |
| |
| public String getCode() throws ModelException { |
| return cu.getSource(); |
| } |
| |
| /*public Collection getUsedNames() { |
| return fNames.values(); |
| }*/ |
| |
| public int getImplicitReceiversCount() { |
| return implicitReceiversCount; |
| } |
| |
| /*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; |
| }*/ |
| |
| public ParameterData getParameterData(int index) { |
| return paramData[index]; |
| } |
| |
| public ISourceModule getSourceModule() { |
| return cu; |
| } |
| } |