/******************************************************************************* * Copyright (c) 2000, 2009 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 method - doesn't handle implicit cast" (see * https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941). * o bug inline method: compile error (array related) * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38471) * o inline call that is used in a field initializer * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38137) * o inline call a field initializer: could detect self reference * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=44417) * 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.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.dom.AST; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.ASTVisitor; import org.eclipse.wst.jsdt.core.dom.ArrayCreation; import org.eclipse.wst.jsdt.core.dom.ArrayInitializer; import org.eclipse.wst.jsdt.core.dom.Assignment; import org.eclipse.wst.jsdt.core.dom.Block; import org.eclipse.wst.jsdt.core.dom.BodyDeclaration; import org.eclipse.wst.jsdt.core.dom.DoStatement; import org.eclipse.wst.jsdt.core.dom.EnhancedForStatement; import org.eclipse.wst.jsdt.core.dom.Expression; import org.eclipse.wst.jsdt.core.dom.FieldAccess; import org.eclipse.wst.jsdt.core.dom.FieldDeclaration; import org.eclipse.wst.jsdt.core.dom.ForInStatement; import org.eclipse.wst.jsdt.core.dom.ForStatement; 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.IfStatement; import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; import org.eclipse.wst.jsdt.core.dom.LabeledStatement; import org.eclipse.wst.jsdt.core.dom.Name; import org.eclipse.wst.jsdt.core.dom.ParenthesizedExpression; import org.eclipse.wst.jsdt.core.dom.ReturnStatement; import org.eclipse.wst.jsdt.core.dom.SimpleName; import org.eclipse.wst.jsdt.core.dom.Statement; import org.eclipse.wst.jsdt.core.dom.SuperFieldAccess; import org.eclipse.wst.jsdt.core.dom.SwitchStatement; import org.eclipse.wst.jsdt.core.dom.ThisExpression; import org.eclipse.wst.jsdt.core.dom.Type; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement; import org.eclipse.wst.jsdt.core.dom.WhileStatement; import org.eclipse.wst.jsdt.core.dom.WithStatement; import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite; import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite; import org.eclipse.wst.jsdt.core.dom.rewrite.ListRewrite; import org.eclipse.wst.jsdt.internal.corext.Corext; import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodeFactory; import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes; import org.eclipse.wst.jsdt.internal.corext.dom.CodeScopeBuilder; import org.eclipse.wst.jsdt.internal.corext.dom.HierarchicalASTVisitor; import org.eclipse.wst.jsdt.internal.corext.dom.LocalVariableIndex; import org.eclipse.wst.jsdt.internal.corext.dom.Selection; 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.base.RefactoringStatusCodes; 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.InputFlowAnalyzer; import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment; import org.eclipse.wst.jsdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer; import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringFileBuffers; import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; public class CallInliner { private IJavaScriptUnit fCUnit; private ASTRewrite fRewrite; private ImportRewrite fImportRewrite; private ITextFileBuffer fBuffer; private SourceProvider fSourceProvider; private TypeEnvironment fTypeEnvironment; private BodyDeclaration fBodyDeclaration; private CodeScopeBuilder.Scope fRootScope; private int fNumberOfLocals; private ASTNode fInvocation; private int fInsertionIndex; private ListRewrite fListRewrite; private boolean fNeedsStatement; private ASTNode fTargetNode; private FlowContext fFlowContext; private FlowInfo fFlowInfo; private CodeScopeBuilder.Scope fInvocationScope; private boolean fFieldInitializer; private List fLocals; private CallContext fContext; private class InlineEvaluator extends HierarchicalASTVisitor { private ParameterData fFormalArgument; private boolean fResult; public InlineEvaluator(ParameterData argument) { fFormalArgument= argument; } public boolean getResult() { return fResult; } private boolean setResult(boolean result) { fResult= result; return false; } public boolean visit(Expression node) { int accessMode= fFormalArgument.getSimplifiedAccessMode(); if (accessMode == FlowInfo.WRITE) return setResult(false); if (accessMode == FlowInfo.UNUSED) return setResult(true); if (ASTNodes.isLiteral(node)) return setResult(true); return setResult(fFormalArgument.getNumberOfAccesses() <= 1); } public boolean visit(SimpleName node) { IBinding binding= node.resolveBinding(); if (binding instanceof IVariableBinding) { int accessMode = fFormalArgument.getSimplifiedAccessMode(); if (accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED) return setResult(true); // from now on we only have write accesses. IVariableBinding vb= (IVariableBinding)binding; if (vb.isField()) return setResult(false); return setResult(fFlowInfo.hasAccessMode(fFlowContext, vb, FlowInfo.UNUSED | FlowInfo.WRITE)); } return setResult(false); } public boolean visit(FieldAccess node) { return visit(node.getName()); } public boolean visit(SuperFieldAccess node) { return visit(node.getName()); } public boolean visit(ThisExpression node) { int accessMode= fFormalArgument.getSimplifiedAccessMode(); if (accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED) return setResult(true); return setResult(false); } } // private static class AmbiguousMethodAnalyzer implements TypeBindingVisitor { // private TypeEnvironment fTypeEnvironment; // private TType[] fTypes; // private IFunctionBinding fOriginal; // // public AmbiguousMethodAnalyzer(TypeEnvironment typeEnvironment, IFunctionBinding original, TType[] types) { // fTypeEnvironment= typeEnvironment; // fOriginal= original; // fTypes= types; // } // public boolean visit(ITypeBinding node) { // IFunctionBinding[] methods= node.getDeclaredMethods(); // for (int i= 0; i < methods.length; i++) { // IFunctionBinding candidate= methods[i]; // if (candidate == fOriginal) { // continue; // } // if (fOriginal.getName().equals(candidate.getName())) { // if (canImplicitlyCall(candidate)) { // return false; // } // } // } // return true; // } // /** // * Returns <code>true</code> if the method can be called without explicit casts; // * otherwise <code>false</code>. // */ // private boolean canImplicitlyCall(IFunctionBinding candidate) { // ITypeBinding[] parameters= candidate.getParameterTypes(); // if (parameters.length != fTypes.length) { // return false; // } // for (int i= 0; i < parameters.length; i++) { // if (!fTypes[i].canAssignTo(fTypeEnvironment.create(parameters[i]))) { // return false; // } // } // return true; // } // } public CallInliner(IJavaScriptUnit unit, JavaScriptUnit targetAstRoot, SourceProvider provider) throws CoreException { super(); fCUnit= unit; fBuffer= RefactoringFileBuffers.acquire(fCUnit); fSourceProvider= provider; fImportRewrite= StubUtility.createImportRewrite(targetAstRoot, true); fLocals= new ArrayList(3); fRewrite= ASTRewrite.create(targetAstRoot.getAST()); fRewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer()); fTypeEnvironment= new TypeEnvironment(); } public void dispose() { try { RefactoringFileBuffers.release(fCUnit); } catch (CoreException exception) { JavaScriptPlugin.log(exception); } } public ImportRewrite getImportEdit() { return fImportRewrite; } public ASTNode getTargetNode() { return fTargetNode; } public void initialize(BodyDeclaration declaration) { fBodyDeclaration= declaration; fRootScope= CodeScopeBuilder.perform(declaration, fSourceProvider.getDeclaration().resolveBinding()); fNumberOfLocals= 0; switch (declaration.getNodeType()) { case ASTNode.FUNCTION_DECLARATION: case ASTNode.INITIALIZER: fNumberOfLocals= LocalVariableIndex.perform(declaration); break; } } public RefactoringStatus initialize(ASTNode invocation, int severity) { RefactoringStatus result= new RefactoringStatus(); fInvocation= invocation; fLocals= new ArrayList(3); checkMethodDeclaration(result, severity); if (result.getSeverity() >= severity) return result; initializeRewriteState(); initializeTargetNode(); flowAnalysis(); fContext= new CallContext(fInvocation, fInvocationScope, fTargetNode.getNodeType(), fImportRewrite); try { computeRealArguments(); computeReceiver(); } catch (BadLocationException exception) { JavaScriptPlugin.log(exception); } checkInvocationContext(result, severity); return result; } private void initializeRewriteState() { // field initializer can be inside of a block if used in a local class // but block can't be a child of field initializer if(ASTNodes.getParent(fInvocation, ASTNode.FIELD_DECLARATION) != null) { fFieldInitializer= true; } } private void initializeTargetNode() { ASTNode parent= fInvocation.getParent(); int nodeType= parent.getNodeType(); if (nodeType == ASTNode.EXPRESSION_STATEMENT || nodeType == ASTNode.RETURN_STATEMENT) { fTargetNode= parent; } else { fTargetNode= fInvocation; } } // the checks depend on invocation context and therefore can't be done in SourceAnalyzer private void checkMethodDeclaration(RefactoringStatus result, int severity) { FunctionDeclaration methodDeclaration= fSourceProvider.getDeclaration(); // it is not allowed to inline constructor invocation only if it is used for class instance creation // if constructor is invoked from another constructor then we can inline such invocation if (fInvocation.getNodeType() != ASTNode.CONSTRUCTOR_INVOCATION && methodDeclaration.isConstructor()) { result.addEntry(new RefactoringStatusEntry( severity, RefactoringCoreMessages.CallInliner_constructors, JavaStatusContext.create(fCUnit, fInvocation))); } if (fSourceProvider.hasSuperMethodInvocation() && fInvocation.getNodeType() == ASTNode.FUNCTION_INVOCATION) { Expression receiver= ((FunctionInvocation)fInvocation).getExpression(); if (receiver instanceof ThisExpression) { result.addEntry(new RefactoringStatusEntry( severity, RefactoringCoreMessages.CallInliner_super_into_this_expression, JavaStatusContext.create(fCUnit, fInvocation))); } } } private void checkInvocationContext(RefactoringStatus result, int severity) { if (fInvocation.getNodeType() == ASTNode.FUNCTION_INVOCATION) { Expression exp= ((FunctionInvocation)fInvocation).getExpression(); if (exp != null && exp.resolveTypeBinding() == null) { addEntry(result, RefactoringCoreMessages.CallInliner_receiver_type, RefactoringStatusCodes.INLINE_METHOD_NULL_BINDING, severity); return; } } int nodeType= fTargetNode.getNodeType(); if (nodeType == ASTNode.EXPRESSION_STATEMENT) { if (fSourceProvider.isExecutionFlowInterrupted()) { addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow, RefactoringStatusCodes.INLINE_METHOD_EXECUTION_FLOW, severity); return; } } else if (nodeType == ASTNode.FUNCTION_INVOCATION) { ASTNode parent= fTargetNode.getParent(); if (isReturnStatement(parent)) { //support inlining even if the execution flow is interrupted return; } if (fSourceProvider.isExecutionFlowInterrupted()) { addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow, RefactoringStatusCodes.INLINE_METHOD_EXECUTION_FLOW, severity); return; } if (isAssignment(parent) || isSingleDeclaration(parent)) { // we support inlining expression in assigment and initializers as // long as the execution flow isn't interrupted. return; } else { boolean isFieldDeclaration= ASTNodes.getParent(fInvocation, FieldDeclaration.class) != null; if (!fSourceProvider.isSimpleFunction()) { if (isMultiDeclarationFragment(parent)) { addEntry(result, RefactoringCoreMessages.CallInliner_multiDeclaration, RefactoringStatusCodes.INLINE_METHOD_INITIALIZER_IN_FRAGEMENT, severity); } else if (isFieldDeclaration) { addEntry(result, RefactoringCoreMessages.CallInliner_field_initializer_simple, RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); } else { addEntry(result, RefactoringCoreMessages.CallInliner_simple_functions, RefactoringStatusCodes.INLINE_METHOD_ONLY_SIMPLE_FUNCTIONS, severity); } return; } if (isFieldDeclaration) { int argumentsCount= fContext.arguments.length; for (int i= 0; i < argumentsCount; i++) { ParameterData parameter= fSourceProvider.getParameterData(i); if(parameter.isWrite()) { addEntry(result, RefactoringCoreMessages.CallInliner_field_initialize_write_parameter, RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); return; } } if(fLocals.size() > 0) { addEntry(result, RefactoringCoreMessages.CallInliner_field_initialize_new_local, RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); return; } // verify that the field is not referenced by the initializer method VariableDeclarationFragment variable= (VariableDeclarationFragment)ASTNodes.getParent(fInvocation, ASTNode.VARIABLE_DECLARATION_FRAGMENT); if(fSourceProvider.isVariableReferenced(variable.resolveBinding())) { addEntry(result, RefactoringCoreMessages.CallInliner_field_initialize_self_reference, RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); return; } } } } } private static boolean isAssignment(ASTNode node) { return node instanceof Assignment; } private static boolean isReturnStatement(ASTNode node) { return node instanceof ReturnStatement; } private static boolean isSingleDeclaration(ASTNode node) { int type= node.getNodeType(); if (type == ASTNode.SINGLE_VARIABLE_DECLARATION) return true; if (type == ASTNode.VARIABLE_DECLARATION_FRAGMENT) { node= node.getParent(); if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) { VariableDeclarationStatement vs= (VariableDeclarationStatement)node; return vs.fragments().size() == 1; } } return false; } private static boolean isMultiDeclarationFragment(ASTNode node) { int nodeType= node.getNodeType(); if (nodeType == ASTNode.VARIABLE_DECLARATION_FRAGMENT) { node= node.getParent(); if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) { VariableDeclarationStatement vs= (VariableDeclarationStatement)node; return vs.fragments().size() > 1; } } return false; } private void addEntry(RefactoringStatus result, String message, int code, int severity) { result.addEntry(new RefactoringStatusEntry( severity, message, JavaStatusContext.create(fCUnit, fInvocation), Corext.getPluginId(), code, null)); } private void flowAnalysis() { fInvocationScope= fRootScope.findScope(fTargetNode.getStartPosition(), fTargetNode.getLength()); fInvocationScope.setCursor(fTargetNode.getStartPosition()); fFlowContext= new FlowContext(0, fNumberOfLocals + 1); fFlowContext.setConsiderAccessMode(true); fFlowContext.setComputeMode(FlowContext.ARGUMENTS); Selection selection= Selection.createFromStartLength(fInvocation.getStartPosition(), fInvocation.getLength()); switch (fBodyDeclaration.getNodeType()) { case ASTNode.INITIALIZER: case ASTNode.FIELD_DECLARATION: case ASTNode.FUNCTION_DECLARATION: fFlowInfo= new InputFlowAnalyzer(fFlowContext, selection, true).perform(fBodyDeclaration); break; default: Assert.isTrue(false, "Should not happen"); //$NON-NLS-1$ } } public RefactoringStatus perform(TextEditGroup textEditGroup) throws CoreException { RefactoringStatus result= new RefactoringStatus(); String[] blocks= fSourceProvider.getCodeBlocks(fContext); if(!fFieldInitializer) { initializeInsertionPoint(fSourceProvider.getNumberOfStatements() + fLocals.size()); } addNewLocals(textEditGroup); replaceCall(result, blocks, textEditGroup); return result; } public TextEdit getModifications() { return fRewrite.rewriteAST(fBuffer.getDocument(), fCUnit.getJavaScriptProject().getOptions(true)); } private void computeRealArguments() throws BadLocationException { List arguments= Invocations.getArguments(fInvocation); Set canNotInline= crossCheckArguments(arguments); boolean needsVarargBoxing= needsVarargBoxing(arguments); int varargIndex= fSourceProvider.getVarargIndex(); String[] realArguments= new String[needsVarargBoxing ? varargIndex + 1 : arguments.size()]; for (int i= 0; i < (needsVarargBoxing ? varargIndex : arguments.size()); i++) { Expression expression= (Expression)arguments.get(i); ParameterData parameter= fSourceProvider.getParameterData(i); if (canInline(expression, parameter) && !canNotInline.contains(expression)) { realArguments[i] = getContent(expression); // fixes bugs #35905, #38471 if (argumentNeedsParenthesis(expression, parameter)) { realArguments[i] = "(" + realArguments[i] + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } } else { String name= fInvocationScope.createName(parameter.getName(), true); realArguments[i]= name; fLocals.add(createLocalDeclaration( parameter.getTypeBinding(), name, (Expression)fRewrite.createCopyTarget(expression))); } } if (needsVarargBoxing) { ParameterData parameter= fSourceProvider.getParameterData(varargIndex); String name= fInvocationScope.createName(parameter.getName(), true); realArguments[varargIndex]= name; AST ast= fInvocation.getAST(); Type type= fImportRewrite.addImport(parameter.getTypeBinding(), ast); VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment(); fragment.setName(ast.newSimpleName(name)); ArrayInitializer initializer= ast.newArrayInitializer(); for (int i= varargIndex; i < arguments.size(); i++) { initializer.expressions().add(fRewrite.createCopyTarget((ASTNode)arguments.get(i))); } fragment.setInitializer(initializer); VariableDeclarationStatement decl= ast.newVariableDeclarationStatement(fragment); decl.setType(type); fLocals.add(decl); } fContext.arguments= realArguments; } private boolean needsVarargBoxing(List arguments) { if (!fSourceProvider.isVarargs()) return false; /* if (!fSourceProvider.hasArrayAccess()) return false; */ int index= fSourceProvider.getVarargIndex(); // we have varags but the call doesn't pass any arguments if (index >= arguments.size()) return true; // parameter is array type // one arg if (index == arguments.size() - 1) { ITypeBinding argument= ((Expression)arguments.get(index)).resolveTypeBinding(); if (argument == null) return false; ITypeBinding parameter= fSourceProvider.getParameterData(index).getTypeBinding(); return !fTypeEnvironment.create(argument).canAssignTo(fTypeEnvironment.create(parameter)); } return true; } private boolean argumentNeedsParenthesis(Expression expression, ParameterData param) { if (expression instanceof ArrayCreation) return true; int argPrecedence= OperatorPrecedence.getValue(expression); int paramPrecedence= param.getOperatorPrecedence(); if (argPrecedence != -1 && paramPrecedence != -1) return argPrecedence < paramPrecedence; return false; } private void computeReceiver() throws BadLocationException { Expression receiver= Invocations.getExpression(fInvocation); if (receiver == null) return; final boolean isName= receiver instanceof Name; if (isName) fContext.receiverIsStatic= ((Name)receiver).resolveBinding() instanceof ITypeBinding; if (ASTNodes.isLiteral(receiver) || isName || receiver instanceof ThisExpression) { fContext.receiver= fBuffer.getDocument().get(receiver.getStartPosition(), receiver.getLength()); return; } switch(fSourceProvider.getReceiversToBeUpdated()) { case 0: // Make sure we evaluate the current receiver. Best is to assign to // local. fLocals.add(createLocalDeclaration( receiver.resolveTypeBinding(), fInvocationScope.createName("r", true), //$NON-NLS-1$ (Expression)fRewrite.createCopyTarget(receiver))); return; case 1: fContext.receiver= fBuffer.getDocument().get(receiver.getStartPosition(), receiver.getLength()); return; default: String local= fInvocationScope.createName("r", true); //$NON-NLS-1$ fLocals.add(createLocalDeclaration( receiver.resolveTypeBinding(), local, (Expression)fRewrite.createCopyTarget(receiver))); fContext.receiver= local; return; } } private void addNewLocals(TextEditGroup textEditGroup) { if (fLocals.isEmpty()) return; for (Iterator iter= fLocals.iterator(); iter.hasNext();) { ASTNode element= (ASTNode)iter.next(); fListRewrite.insertAt(element, fInsertionIndex++, textEditGroup); } } private void replaceCall(RefactoringStatus status, String[] blocks, TextEditGroup textEditGroup) { // Inline empty body if (blocks.length == 0) { if (fNeedsStatement) { fRewrite.replace(fTargetNode, fTargetNode.getAST().newEmptyStatement(), textEditGroup); } else { fRewrite.remove(fTargetNode, textEditGroup); } } else { ASTNode node= null; for (int i= 0; i < blocks.length - 1; i++) { node= fRewrite.createStringPlaceholder(blocks[i], ASTNode.RETURN_STATEMENT); fListRewrite.insertAt(node, fInsertionIndex++, textEditGroup); } String block= blocks[blocks.length - 1]; // We can inline a call where the declaration is a function and the call itself // is a statement. In this case we have to create a temporary variable if the // returned expression must be evaluated. if (fContext.callMode == ASTNode.EXPRESSION_STATEMENT && fSourceProvider.hasReturnValue()) { if (fSourceProvider.mustEvaluateReturnedExpression()) { if (fSourceProvider.returnValueNeedsLocalVariable()) { IFunctionBinding invocation= Invocations.resolveBinding(fInvocation); node= createLocalDeclaration( invocation.getReturnType(), fInvocationScope.createName(fSourceProvider.getMethodName(), true), (Expression)fRewrite.createStringPlaceholder(block, ASTNode.FUNCTION_INVOCATION)); } else { node= fTargetNode.getAST().newExpressionStatement( (Expression)fRewrite.createStringPlaceholder(block, ASTNode.FUNCTION_INVOCATION)); } } else { node= null; } } else if (fTargetNode instanceof Expression) { node= fRewrite.createStringPlaceholder(block, ASTNode.FUNCTION_INVOCATION); if (needsParenthesis()) { ParenthesizedExpression pExp= fTargetNode.getAST().newParenthesizedExpression(); pExp.setExpression((Expression)node); node= pExp; } } else { node= fRewrite.createStringPlaceholder(block, ASTNode.RETURN_STATEMENT); } // Now replace the target node with the source node if (node != null) { if (fTargetNode == null) { fListRewrite.insertAt(node, fInsertionIndex++, textEditGroup); } else { fRewrite.replace(fTargetNode, node, textEditGroup); } } else { if (fTargetNode != null) { fRewrite.remove(fTargetNode, textEditGroup); } } } } private boolean needsParenthesis() { if (!fSourceProvider.needsReturnedExpressionParenthesis()) return false; ASTNode parent= fTargetNode.getParent(); int type= parent.getNodeType(); return type == ASTNode.FUNCTION_INVOCATION || (parent instanceof Expression && type != ASTNode.ASSIGNMENT) || (fSourceProvider.returnsConditionalExpression() && type == ASTNode.VARIABLE_DECLARATION_FRAGMENT && ((VariableDeclarationFragment)parent).getInitializer() == fTargetNode); } private VariableDeclarationStatement createLocalDeclaration(ITypeBinding type, String name, Expression initializer) { String typeName= fImportRewrite.addImport(type); VariableDeclarationStatement decl= (VariableDeclarationStatement)ASTNodeFactory.newStatement( fInvocation.getAST(), typeName + " " + name + ";"); //$NON-NLS-1$ //$NON-NLS-2$ ((VariableDeclarationFragment)decl.fragments().get(0)).setInitializer(initializer); return decl; } /** * Checks whether arguments are passed to the method which do some assignments * inside the expression. If so these arguments can't be inlined into the * calling method since the assignments might be reorder. An example is: * <code> * add((field=args).length,field.hashCode()); * </code> * Field might not be initialized when the arguments are reorder in the called * method. */ private Set crossCheckArguments(List arguments) { final Set assigned= new HashSet(); final Set result= new HashSet(); for (Iterator iter= arguments.iterator(); iter.hasNext();) { final Expression expression= (Expression) iter.next(); expression.accept(new ASTVisitor() { public boolean visit(Assignment node) { Expression lhs= node.getLeftHandSide(); if (lhs instanceof Name) { IBinding binding= ((Name)lhs).resolveBinding(); if (binding instanceof IVariableBinding) { assigned.add(binding); result.add(expression); } } return true; } }); } for (Iterator iter= arguments.iterator(); iter.hasNext();) { final Expression expression= (Expression) iter.next(); if (!result.contains(expression)) { expression.accept(new HierarchicalASTVisitor() { public boolean visit(Name node) { IBinding binding= node.resolveBinding(); if (binding != null && assigned.contains(binding)) result.add(expression); return false; } }); } } return result; } private boolean canInline(Expression actualParameter, ParameterData formalParameter) { InlineEvaluator evaluator= new InlineEvaluator(formalParameter); actualParameter.accept(evaluator); return evaluator.getResult(); } private void initializeInsertionPoint(int nos) { fInsertionIndex= -1; fNeedsStatement= false; // if we have a constructor invocation the invocation itself is already a statement ASTNode parentStatement= fInvocation instanceof Statement ? fInvocation : ASTNodes.getParent(fInvocation, Statement.class); ASTNode container= parentStatement.getParent(); int type= container.getNodeType(); if (type == ASTNode.BLOCK) { Block block= (Block)container; fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY); fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement); } else if (type == ASTNode.SWITCH_STATEMENT) { SwitchStatement switchStatement= (SwitchStatement)container; fListRewrite= fRewrite.getListRewrite(switchStatement, SwitchStatement.STATEMENTS_PROPERTY); fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement); } else if (isControlStatement(container) || type == ASTNode.LABELED_STATEMENT) { fNeedsStatement= true; if (nos > 1 || needsBlockAroundDanglingIf()) { Block block= fInvocation.getAST().newBlock(); fInsertionIndex= 0; Statement currentStatement= null; switch(type) { case ASTNode.LABELED_STATEMENT: currentStatement= ((LabeledStatement)container).getBody(); break; case ASTNode.FOR_STATEMENT: currentStatement= ((ForStatement)container).getBody(); break; case ASTNode.FOR_IN_STATEMENT: currentStatement= ((ForInStatement)container).getBody(); break; case ASTNode.ENHANCED_FOR_STATEMENT: currentStatement= ((EnhancedForStatement)container).getBody(); break; case ASTNode.WHILE_STATEMENT: currentStatement= ((WhileStatement)container).getBody(); break; case ASTNode.WITH_STATEMENT: currentStatement= ((WithStatement)container).getBody(); break; case ASTNode.DO_STATEMENT: currentStatement= ((DoStatement)container).getBody(); break; case ASTNode.IF_STATEMENT: IfStatement node= (IfStatement)container; Statement thenPart= node.getThenStatement(); if (fTargetNode == thenPart || ASTNodes.isParent(fTargetNode, thenPart)) { currentStatement= thenPart; } else { currentStatement= node.getElseStatement(); } break; } Assert.isNotNull(currentStatement); fRewrite.replace(currentStatement, block, null); fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY); // The method to be inlined is not the body of the control statement. if (currentStatement != fTargetNode) { fListRewrite.insertLast(fRewrite.createCopyTarget(currentStatement), null); } else { // We can't replace a copy with something else. So we // have to insert all statements to be inlined. fTargetNode= null; } } } // We only insert one new statement or we delete the existing call. // So there is no need to have an insertion index. } private boolean needsBlockAroundDanglingIf() { /* see https://bugs.eclipse.org/bugs/show_bug.cgi?id=169331 * * Situation: * boolean a, b; * void toInline() { * if (a) * hashCode(); * } * void m() { * if (b) * toInline(); * else * toString(); * } * => needs block around inlined "if (a)..." to avoid attaching else to wrong if. */ return fTargetNode.getLocationInParent() == IfStatement.THEN_STATEMENT_PROPERTY && fTargetNode.getParent().getStructuralProperty(IfStatement.ELSE_STATEMENT_PROPERTY) != null && fSourceProvider.isDangligIf(); } private String getContent(ASTNode node) throws BadLocationException { return fBuffer.getDocument().get(node.getStartPosition(), node.getLength()); } private boolean isControlStatement(ASTNode node) { int type= node.getNodeType(); return type == ASTNode.IF_STATEMENT || type == ASTNode.FOR_STATEMENT || type == ASTNode.ENHANCED_FOR_STATEMENT || type == ASTNode.WHILE_STATEMENT || type == ASTNode.DO_STATEMENT; } }