/******************************************************************************* * Copyright (c) 2000, 2010 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.dltk.internal.javascript.corext.refactoring.code; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.SourceRange; import org.eclipse.dltk.internal.corext.refactoring.base.ScriptStatusContext; import org.eclipse.dltk.internal.javascript.core.manipulation.JavascriptManipulationPlugin; 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.InputFlowAnalyzer; import org.eclipse.dltk.internal.javascript.corext.refactoring.code.flow.VariableBinding; import org.eclipse.dltk.internal.javascript.corext.refactoring.util.Selection; import org.eclipse.dltk.javascript.core.dom.BinaryExpression; import org.eclipse.dltk.javascript.core.dom.BinaryOperator; import org.eclipse.dltk.javascript.core.dom.BlockStatement; import org.eclipse.dltk.javascript.core.dom.CallExpression; import org.eclipse.dltk.javascript.core.dom.ConditionalExpression; import org.eclipse.dltk.javascript.core.dom.DomFactory; import org.eclipse.dltk.javascript.core.dom.DomPackage; import org.eclipse.dltk.javascript.core.dom.Expression; import org.eclipse.dltk.javascript.core.dom.ExpressionStatement; import org.eclipse.dltk.javascript.core.dom.FunctionExpression; import org.eclipse.dltk.javascript.core.dom.Identifier; import org.eclipse.dltk.javascript.core.dom.IfStatement; import org.eclipse.dltk.javascript.core.dom.Node; import org.eclipse.dltk.javascript.core.dom.ParenthesizedExpression; import org.eclipse.dltk.javascript.core.dom.ReturnStatement; import org.eclipse.dltk.javascript.core.dom.Statement; import org.eclipse.dltk.javascript.core.dom.UnaryExpression; import org.eclipse.dltk.javascript.core.dom.VariableDeclaration; import org.eclipse.dltk.javascript.core.dom.VariableReference; import org.eclipse.dltk.javascript.core.dom.VariableStatement; 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.emf.ecore.EReference; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; public class CallInliner { private ISourceModule cu; //private ASTRewrite fRewrite; //private ImportRewrite fImportRewrite; private SourceProvider sourceProvider; //private TypeEnvironment fTypeEnvironment; private Node bodyDeclaration; //private CodeScopeBuilder.Scope fRootScope; //private int fNumberOfLocals; private CallExpression invocation; /*private int fInsertionIndex; private ListRewrite fListRewrite; private boolean fNeedsStatement;*/ private Node targetNode; private FlowContext flowContext; private FlowInfo flowInfo; private Map<Identifier,VariableBinding> bindings; private Expression[] realArguments; //private CodeScopeBuilder.Scope invocationScope; //private boolean fFieldInitializer; private Set<String> usedNames; private List<String> locals; private List<Expression> localInitializers; private Expression receiverExpr; //private CallContext fContext; private class InlineEvaluator extends DomSwitch<Boolean> { private ParameterData formalArgument; private boolean result; public InlineEvaluator(ParameterData argument) { formalArgument= argument; } public boolean getResult() { return result; } public void traverse(Node node) { int accessMode= formalArgument.getSimplifiedAccessMode(); switch(node.eClass().getClassifierID()) { case DomPackage.VARIABLE_REFERENCE: VariableBinding binding = bindings.get(((VariableReference)node).getVariable()); result = accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED || flowInfo.hasAccessMode(flowContext, binding, FlowInfo.UNUSED | FlowInfo.WRITE); case DomPackage.BOOLEAN_LITERAL: case DomPackage.NULL_LITERAL: case DomPackage.NUMERIC_LITERAL: case DomPackage.REGULAR_EXPRESSION_LITERAL: case DomPackage.STRING_LITERAL: case DomPackage.THIS_EXPRESSION: result = accessMode != FlowInfo.WRITE; break; case DomPackage.PARENTHESIZED_EXPRESSION: traverse(((ParenthesizedExpression)node).getEnclosed()); break; case DomPackage.PROPERTY_ACCESS_EXPRESSION: case DomPackage.ARRAY_ACCESS_EXPRESSION: result = accessMode == FlowInfo.UNUSED || accessMode == FlowInfo.READ && formalArgument.getNumberOfAccesses() <= 1; result = result && !formalArgument.isFunction(); break; default: result = accessMode == FlowInfo.UNUSED || accessMode == FlowInfo.READ && formalArgument.getNumberOfAccesses() <= 1; } } } /*private static class AmbiguousMethodAnalyzer implements TypeBindingVisitor { private TypeEnvironment fTypeEnvironment; private TType[] fTypes; private IMethodBinding fOriginal; public AmbiguousMethodAnalyzer(TypeEnvironment typeEnvironment, IMethodBinding original, TType[] types) { fTypeEnvironment= typeEnvironment; fOriginal= original; fTypes= types; } public boolean visit(ITypeBinding node) { IMethodBinding[] methods= node.getDeclaredMethods(); for (int i= 0; i < methods.length; i++) { IMethodBinding 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>. * @param candidate the method to test * @return <code>true</code> if the method can be called without explicit casts * private boolean canImplicitlyCall(IMethodBinding 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; } }*/ private final class ReceiverAnalyzer { private final Map<VariableBinding, Expression> replacements; private final Map<Identifier, VariableBinding> bindings; private ReceiverAnalyzer(Map<VariableBinding, Expression> replacements, Map<Identifier, VariableBinding> bindings) { this.replacements = replacements; this.bindings = bindings; } void traverse(List<? extends Node> list, Node node) { traverse(list); if (node != null) traverse(node); } void traverse(List<? extends Node> list) { for(int i=0;i<list.size();i++) { Expression expr = process(list.get(i)); if (expr != null) ((List<Expression>)list).set(i, expr); } } void traverse(Node node) { for(EReference ref : node.eClass().getEAllReferences()) if (ref.isMany()) traverse((List<? extends Node>)node.eGet(ref)); else { Expression expr = process((Node)node.eGet(ref)); if (expr != null) node.eSet(ref,expr); } } Expression process(Node value) { if (value == null) return null; switch(value.eClass().getClassifierID()) { case DomPackage.IDENTIFIER: { VariableBinding binding = bindings.get((Identifier)value); Expression res = replacements.get(binding); if (res == null) return null; if (res instanceof VariableReference) { ((Identifier)value).setName(((VariableReference) res).getVariable().getName()); return null; } throw new IllegalStateException("Replacing variable with expression without removing declaration"); // $NON-NLS-1 } case DomPackage.VARIABLE_REFERENCE: VariableBinding binding = bindings.get(((VariableReference)value).getVariable()); if (binding == null) return null; Expression res = replacements.get(binding); if (res != null) { res = (Expression)EcoreUtil.copy(res); if (needsParentheses(res,(Expression)value)) { ParenthesizedExpression pExp = DomFactory.eINSTANCE.createParenthesizedExpression(); pExp.setEnclosed((Expression)res); res = pExp; } return res; } return null; case DomPackage.THIS_EXPRESSION: return receiverExpr==null ? null : (Expression)EcoreUtil.copy(receiverExpr); default: traverse(value); return null; } } } public CallInliner(ISourceModule cu, SourceProvider provider) throws CoreException { this.cu = cu; sourceProvider= provider; //fImportRewrite= StubUtility.createImportRewrite(targetAstRoot, true); /*fRewrite= ASTRewrite.create(targetAstRoot.getAST()); fRewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer()); fTypeEnvironment= new TypeEnvironment();*/ } /*public ImportRewrite getImportEdit() { return fImportRewrite; } public ASTNode getTargetNode() { return fTargetNode; }*/ public void initialize(Node declaration) { bodyDeclaration= declaration; bindings = VariableLookup.findBindings(declaration); /*fRootScope= CodeScopeBuilder.perform(declaration, fSourceProvider.getDeclaration().resolveBinding()); fNumberOfLocals= 0; switch (declaration.getNodeType()) { case ASTNode.METHOD_DECLARATION: case ASTNode.INITIALIZER: fNumberOfLocals= LocalVariableIndex.perform(declaration); break; }*/ } public RefactoringStatus initialize(CallExpression invocation, int severity) { RefactoringStatus result= new RefactoringStatus(); this.invocation= invocation; locals = new ArrayList<String>(); localInitializers = new ArrayList<Expression>(); //checkMethodDeclaration(result, severity); //if (result.getSeverity() >= severity) // return result; //initializeRewriteState(); initializeTargetNode(); flowAnalysis(); //context= new CallContext(invocation, invocationScope, targetNode.getNodeType(), importRewrite); try { computeRealArguments(); computeReceiver(); } catch (ModelException exception) { JavascriptManipulationPlugin.log(exception); } checkInvocationContext(result, severity); return result; } /*private void initializeRewriteState() { fFieldInitializer= false; ASTNode parent= fInvocation.getParent(); do { if (parent instanceof FieldDeclaration) { fFieldInitializer= true; return; } else if (parent instanceof Block) { return; } parent= parent.getParent(); } while (parent != null); }*/ private void initializeTargetNode() { Node parent= (Node)invocation.eContainer(); int nodeType= parent.eClass().getClassifierID(); if (nodeType == DomPackage.EXPRESSION_STATEMENT || nodeType == DomPackage.RETURN_STATEMENT) { targetNode= parent; } else { targetNode= invocation; } } // the checks depend on invocation context and therefore can't be done in SourceAnalyzer //private void checkMethodDeclaration(RefactoringStatus result, int severity) { // FunctionExpression methodDeclaration= sourceProvider.getDeclaration(); /*if (fSourceProvider.hasSuperMethodInvocation() && fInvocation.getNodeType() == ASTNode.METHOD_INVOCATION) { Expression receiver= ((MethodInvocation)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) { //Expression exp= getReceiver(invocation); //if (exp != null && exp.resolveTypeBinding() == null) { // addEntry(result, RefactoringCoreMessages.CallInliner_receiver_type, // RefactoringStatusCodes.INLINE_METHOD_NULL_BINDING, severity); // return; //} int nodeType= targetNode.eClass().getClassifierID(); if (nodeType == DomPackage.EXPRESSION_STATEMENT) { if (sourceProvider.isExecutionFlowInterrupted()) { addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow); return; } } else if (nodeType == DomPackage.CALL_EXPRESSION) { Node parent= (Node)targetNode.eContainer(); int parentType = parent.eClass().getClassifierID(); if (parentType == DomPackage.RETURN_STATEMENT) { //support inlining even if the execution flow is interrupted return; } if (sourceProvider.isExecutionFlowInterrupted()) { addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow); return; } if (parentType == DomPackage.BINARY_EXPRESSION && RefactoringUtils.isAssignment(((BinaryExpression)parent).getOperation()) && parent.eContainer().eClass().getClassifierID() == DomPackage.EXPRESSION_STATEMENT) { // we support inlining expression in assigment and initializers as // long as the execution flow isn't interrupted. return; } if (parentType == DomPackage.VARIABLE_DECLARATION) { VariableDeclaration decl = (VariableDeclaration)parent; VariableStatement stmt = (VariableStatement)decl.eContainer(); if (stmt.getDeclarations().get(0) == decl) return; } if (parentType == DomPackage.NEW_EXPRESSION) { addEntry(result, RefactoringCoreMessages.CallInliner_constructors); return; } //boolean isFieldDeclaration= ASTNodes.getParent(invocation, FieldDeclaration.class) != null; if (!sourceProvider.isSimpleFunction()) { if (isMultiDeclarationFragment(parent)) { addEntry(result, RefactoringCoreMessages.CallInliner_multiDeclaration); /*} else if (isFieldDeclaration) { addEntry(result, RefactoringCoreMessages.CallInliner_field_initializer_simple, RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity);*/ } else { addEntry(result, RefactoringCoreMessages.CallInliner_simple_functions); } 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(Node node) { return node instanceof VariableDeclaration && ((VariableStatement)node.eContainer()).getDeclarations().size() > 1; } private void addEntry(RefactoringStatus result, String message) { result.addEntry(new RefactoringStatusEntry( RefactoringStatus.ERROR, message, ScriptStatusContext.create(cu, new SourceRange(invocation.getBegin(),invocation.getEnd()-invocation.getBegin())), JavascriptManipulationPlugin.getPluginId(),RefactoringStatusEntry.NO_CODE)); } private void flowAnalysis() { //invocationScope = rootScope.findScope(targetNode.getBegin(), targetNode.getEnd()); //invocationScope.setCursor(targetNode.getBegin()); flowContext= new FlowContext(bindings); flowContext.setConsiderAccessMode(true); flowContext.setComputeMode(FlowContext.Mode.ARGUMENTS); Selection selection= Selection.createFromStartLength(invocation.getBegin(), invocation.getEnd()-invocation.getBegin()); flowInfo= new InputFlowAnalyzer(flowContext, selection, true).perform(bodyDeclaration); } /*public TextEdit getModifications() { return fRewrite.rewriteAST(fBuffer.getDocument(), fCUnit.getJavaProject().getOptions(true)); }*/ private String getUnusedName(String candidate) { if (!usedNames.contains(candidate)) { usedNames.add(candidate); return candidate; } for(int i=1;;i++) { String res = candidate+i; if (!usedNames.contains(res)) { usedNames.add(res); return res; } } } private Set<String> computeRealArguments() throws ModelException { List<Expression> arguments=invocation.getArguments(); Set<Expression> canNotInline=crossCheckArguments(arguments); //boolean needsVarargBoxing=needsVarargBoxing(arguments); //int varargIndex= fSourceProvider.getVarargIndex(); realArguments= new Expression[arguments.size()]; usedNames = VariableLookup.getVisibleNames(invocation); for (int i= 0; i < arguments.size(); i++) { Expression expression= (Expression)arguments.get(i); ParameterData parameter= sourceProvider.getParameterData(i); if (canInline(expression, parameter) && !canNotInline.contains(expression)) { realArguments[i] = (Expression)EcoreUtil.copy(expression); //if (argumentNeedsParentheses(expression, parameter)) { // realArguments[i] = "(" + realArguments[i] + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //} } else { //String name= invocationScope.createName(parameter.getName(), true); Identifier id = DomFactory.eINSTANCE.createIdentifier(); String name = getUnusedName(parameter.getName()); id.setName(name); VariableReference ref = DomFactory.eINSTANCE.createVariableReference(); ref.setVariable(id); realArguments[i]=ref; locals.add(name); localInitializers.add(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); }*/ return usedNames; } private void computeReceiver() { Expression receiver = RefactoringUtils.getReceiver(invocation); if (receiver == null) { receiverExpr = null; return; } //final boolean isName = receiver instanceof VariableReference; //if (isName) // fContext.receiverIsStatic= ((Name)receiver).resolveBinding() instanceof ITypeBinding; switch(receiver.eClass().getClassifierID()) { case DomPackage.BOOLEAN_LITERAL: case DomPackage.NULL_LITERAL: case DomPackage.NUMERIC_LITERAL: case DomPackage.REGULAR_EXPRESSION_LITERAL: case DomPackage.STRING_LITERAL: case DomPackage.THIS_EXPRESSION: case DomPackage.VARIABLE_REFERENCE: receiverExpr = receiver; return; } if (sourceProvider.getReceiversToBeUpdated() != 1) { while(receiver instanceof ParenthesizedExpression) { Expression expr = ((ParenthesizedExpression)receiver).getEnclosed(); if (expr instanceof BinaryExpression && ((BinaryExpression) expr).getOperation() == BinaryOperator.COMMA) break; receiver = expr; } localInitializers.add(receiver); Identifier id = DomFactory.eINSTANCE.createIdentifier(); String name = getUnusedName("r"); id.setName(name); locals.add(name); VariableReference ref = DomFactory.eINSTANCE.createVariableReference(); ref.setVariable(id); receiverExpr = ref; } else receiverExpr = receiver; return; } /*public String[] getCodeBlocks() { final ASTRewrite rewriter = ASTRewrite.create(fDeclaration.getAST()); replaceParameterWithExpression(rewriter, context.arguments); updateImplicitReceivers(rewriter, context); makeNamesUnique(rewriter, context.scope); updateTypeReferences(rewriter, context); updateStaticReferences(rewriter, context); updateTypeVariables(rewriter, context); updateMethodTypeVariable(rewriter, context); List ranges = null; if (hasReturnValue()) { if (context.callMode == ASTNode.RETURN_STATEMENT) { ranges = getStatementRanges(); } else { ranges = getExpressionRanges(); } } else { ASTNode last = getLastStatement(); if (last != null && last.getNodeType() == ASTNode.RETURN_STATEMENT) { ranges = getReturnStatementRanges(); } else { ranges = getStatementRanges(); } } final TextEdit dummy = rewriter.rewriteAST(fDocument, fTypeRoot .getJavaProject().getOptions(true)); int size = ranges.size(); RangeMarker[] markers = new RangeMarker[size]; for (int i = 0; i < markers.length; i++) { IRegion range = (IRegion) ranges.get(i); markers[i] = new RangeMarker(range.getOffset(), range.getLength()); } int split; if (size <= 1) { split = Integer.MAX_VALUE; } else { IRegion region = (IRegion) ranges.get(0); split = region.getOffset() + region.getLength(); } TextEdit[] edits = dummy.removeChildren(); for (int i = 0; i < edits.length; i++) { TextEdit edit = edits[i]; int pos = edit.getOffset() >= split ? 1 : 0; markers[pos].addChild(edit); } MultiTextEdit root = new MultiTextEdit(0, fDocument.getLength()); root.addChildren(markers); try { TextEditProcessor processor = new TextEditProcessor(fDocument, root, TextEdit.CREATE_UNDO | TextEdit.UPDATE_REGIONS); UndoEdit undo = processor.performEdits(); String[] result = getBlocks(markers); // It is faster to undo the changes than coping the buffer over and // over again. processor = new TextEditProcessor(fDocument, undo, TextEdit.UPDATE_REGIONS); processor.performEdits(); return result; } catch (MalformedTreeException exception) { JavaPlugin.log(exception); } catch (BadLocationException exception) { JavaPlugin.log(exception); } return new String[] {}; }*/ /* * @param status the status * @return <code>true</code> if explicit cast is needed otherwise <code>false</code> * private boolean needsExplicitCast(RefactoringStatus status) { // if the return type of the method is the same as the type of the // returned expression then we don't need an explicit cast. if (fSourceProvider.returnTypeMatchesReturnExpressions()) return false; ASTNode parent= fTargetNode.getParent(); int nodeType= parent.getNodeType(); if (nodeType == ASTNode.METHOD_INVOCATION) { MethodInvocation methodInvocation= (MethodInvocation)parent; if(methodInvocation.getExpression() == fTargetNode) return false; IMethodBinding method= methodInvocation.resolveMethodBinding(); if (method == null) { status.addError(RefactoringCoreMessages.CallInliner_cast_analysis_error, JavaStatusContext.create(fCUnit, methodInvocation)); return false; } ITypeBinding[] parameters= method.getParameterTypes(); int argumentIndex= methodInvocation.arguments().indexOf(fInvocation); List returnExprs= fSourceProvider.getReturnExpressions(); // it is inferred that only methods consisting of a single // return statement can be inlined as parameters in other // method invocations if (returnExprs.size() != 1) return false; parameters[argumentIndex]= ((Expression)returnExprs.get(0)).resolveTypeBinding(); ITypeBinding type= ASTNodes.getReceiverTypeBinding(methodInvocation); TypeBindingVisitor visitor= new AmbiguousMethodAnalyzer( fTypeEnvironment, method, fTypeEnvironment.create(parameters)); if(!visitor.visit(type)) { return true; } else if(type.isInterface()) { return !Bindings.visitInterfaces(type, visitor); } else if(Modifier.isAbstract(type.getModifiers())) { return !Bindings.visitHierarchy(type, visitor); } else { // it is not needed to visit interfaces if receiver is a concrete class return !Bindings.visitSuperclasses(type, visitor); } } return false; }*/ private static boolean needsParentheses(Expression expression, Expression destination) { int inner = OperatorPrecedence.getExpressionPrecedence(expression); int outer; EReference feature = destination.eContainmentFeature(); if (feature == DomPackage.eINSTANCE.getCallExpression_Arguments()) { outer = OperatorPrecedence.SEQUENCE; } else if (feature == DomPackage.eINSTANCE.getArrayLiteral_Elements()) { outer = OperatorPrecedence.SEQUENCE; } else if (feature == DomPackage.eINSTANCE.getParenthesizedExpression_Enclosed()) { outer = OperatorPrecedence.SEQUENCE-1; } else if (destination.eContainer() instanceof Expression) { outer = OperatorPrecedence.getExpressionPrecedence((Expression)destination.eContainer()); } else if (destination.eContainer() instanceof VariableDeclaration) { outer = OperatorPrecedence.SEQUENCE; } else { outer = OperatorPrecedence.SEQUENCE-1; } Node node = destination; while (node != null) { EReference ref = node.eContainmentFeature(); node = (Node)node.eContainer(); if (ref == DomPackage.eINSTANCE.getBinaryExpression_Left() || ref == DomPackage.eINSTANCE.getBinaryExpression_Right() || ref == DomPackage.eINSTANCE.getConditionalExpression_Predicate() || ref == DomPackage.eINSTANCE.getConditionalExpression_Alternative() || ref == DomPackage.eINSTANCE.getVariableDeclaration_Initializer() || ref == DomPackage.eINSTANCE.getVariableStatement_Declarations() || ref == DomPackage.eINSTANCE.getConstStatement_Declarations()) continue; if (ref == DomPackage.eINSTANCE.getForStatement_Initialization() || ref == DomPackage.eINSTANCE.getForInStatement_Item() || ref == DomPackage.eINSTANCE.getForEachInStatement_Item()) { if (!isNodeNoIn(expression)) return true; } break; } if (inner != outer) return inner < outer; if (feature == DomPackage.eINSTANCE.getBinaryExpression_Right()) { return !RefactoringUtils.isAssignment(((BinaryExpression)destination.eContainer()).getOperation()); } return false; } private static boolean isNodeNoIn(Node node) { switch(node.eClass().getClassifierID()) { case DomPackage.BINARY_EXPRESSION: BinaryExpression be = (BinaryExpression)node; return be.getOperation() != BinaryOperator.IN && isNodeNoIn(be.getLeft()) && isNodeNoIn(be.getRight()); case DomPackage.CONDITIONAL_EXPRESSION: ConditionalExpression ce = (ConditionalExpression)node; return isNodeNoIn(ce.getPredicate()) && isNodeNoIn(ce.getAlternative()); default: return true; } } /*private boolean needsParentheses(Statement last) { if (!(last instanceof ReturnStatement)) return false; if (!needsParentheses(((ReturnStatement)last).getExpression())) return false; Node parent= (Node)targetNode.eContainer(); int type= parent.eClass().getClassifierID(); boolean isAssignment = type == DomPackage.BINARY_EXPRESSION && RefactoringUtils.isAssignment(((BinaryExpression)parent).getOperation()); return type == DomPackage.CALL_EXPRESSION || (parent instanceof Expression && !isAssignment) || (returnsConditionalExpression(last) && type == DomPackage.VARIABLE_DECLARATION && ((VariableDeclaration)parent).getInitializer() == targetNode); } private boolean returnsConditionalExpression(Statement last) { if (last instanceof ReturnStatement) { return ((ReturnStatement) last).getExpression() instanceof ConditionalExpression; } return false; }*/ /*private VariableDeclarationStatement createLocalDeclaration(ITypeBinding type, String name, Expression initializer) { ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fTargetNode, fImportRewrite); String typeName= fImportRewrite.addImport(type, context); 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. * @param arguments the arguments * @return all arguments that cannot be inlined */ private Set<Expression> crossCheckArguments(List<Expression> arguments) { //final Set<Expression> assigned= new HashSet<Expression>(); final Set<Expression> result= new HashSet<Expression>(); for (Expression arg : arguments) { TreeIterator<EObject> it = arg.eAllContents(); while (it.hasNext()) { Node node = (Node)it.next(); if (node.eClass().getClassifierID() == DomPackage.BINARY_EXPRESSION) { BinaryExpression expr = (BinaryExpression)node; if (RefactoringUtils.isAssignment(expr.getOperation())) result.add(expr); } if (node.eClass().getClassifierID() == DomPackage.UNARY_EXPRESSION) { UnaryExpression expr = (UnaryExpression)node; if (RefactoringUtils.hasSideEffect(expr.getOperation())) result.add(expr); } } } /*for(Expression expression : arguments) { 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); evaluator.traverse(actualParameter); //actualParameter.accept(evaluator); return evaluator.getResult(); } public RefactoringStatus perform() { Node parentStatement=invocation; while (!(parentStatement.eContainmentFeature().getEReferenceType().getClassifierID() == DomPackage.STATEMENT)) parentStatement = (Node)parentStatement.eContainer(); Node container = (Node)parentStatement.eContainer(); EReference ref = parentStatement.eContainmentFeature(); int nos = sourceProvider.getDeclaration().getBody().getStatements().size() + locals.size(); if (!ref.isMany() && (nos > 1 || needsBlockAroundDanglingIf())) { BlockStatement block = DomFactory.eINSTANCE.createBlockStatement(); block.getStatements().add((Statement)parentStatement); container.eSet(ref, block); container = block; ref = DomPackage.eINSTANCE.getBlockStatement_Statements(); } List<Statement> list = null; int idx = -1; if (ref.isMany()) { list = (List<Statement>)container.eGet(ref); idx = list.lastIndexOf(parentStatement); } if (!locals.isEmpty()) { int i=0; for(String str : locals) { Expression init = localInitializers.get(i++); Identifier id = DomFactory.eINSTANCE.createIdentifier(); id.setName(str); VariableDeclaration decl = DomFactory.eINSTANCE.createVariableDeclaration(); decl.setIdentifier(id); //TODO find out if we need to create a copy decl.setInitializer(init); VariableStatement stmt = DomFactory.eINSTANCE.createVariableStatement(); stmt.getDeclarations().add(decl); list.add(idx++, stmt); } } //String block=sourceProvider.getCode(); FunctionExpression declCopy = (FunctionExpression)EcoreUtil.copy(sourceProvider.getDeclaration()); if (!cu.equals(sourceProvider.getSourceModule())) { // TODO handle this without killing old formatting TreeIterator<EObject> it = declCopy.eAllContents(); while(it.hasNext()) { Node node = (Node)it.next(); node.setBegin(-1); node.setEnd(-1); } } final Map<Identifier, VariableBinding> bindings = VariableLookup.findBindings(declCopy); final Map<VariableBinding, Expression> replacements = new HashMap<VariableBinding, Expression>(); for(int i=0;i<realArguments.length;i++) { VariableBinding binding = bindings.get(declCopy.getParameters().get(i).getName()); replacements.put(binding, realArguments[i]); } List<Statement> stmts = new ArrayList<Statement>(); stmts.addAll(declCopy.getBody().getStatements()); List<Identifier> references = VariableLookup.findReferences(declCopy.getBody(), usedNames); for(Identifier id : references) { VariableBinding binding = bindings.get(id); if (binding != null && !replacements.containsKey(binding)) { Identifier var = DomFactory.eINSTANCE.createIdentifier(); var.setName(getUnusedName(id.getName())); VariableReference expr = DomFactory.eINSTANCE.createVariableReference(); expr.setVariable(var); replacements.put(binding,expr); } } Node node = null; if (stmts.isEmpty() && targetNode != null) { if (ref.isMany()) { list.remove(idx); } else { container.eSet(ref, DomFactory.eINSTANCE.createEmptyStatement()); } } else { for(int i= 0; i < stmts.size()-1; i++) { list.add(idx++,stmts.get(i)); } node = stmts.get(stmts.size()-1); if (node.eClass().getClassifierID() == DomPackage.RETURN_STATEMENT) { node = ((ReturnStatement)node).getExpression(); if (targetNode instanceof Expression) { if (needsParentheses((Expression)node,(Expression)targetNode)) { ParenthesizedExpression pExp = DomFactory.eINSTANCE.createParenthesizedExpression(); pExp.setEnclosed((Expression)node); node= pExp; } Node cont = (Node)targetNode.eContainer(); EReference target = targetNode.eContainmentFeature(); if (target.isMany()) { List<Expression> targets = (List<Expression>)cont.eGet(target); targets.set(targets.lastIndexOf(targetNode),(Expression)node); } else cont.eSet(target, node); } else if (targetNode.eClass().getClassifierID() == DomPackage.EXPRESSION_STATEMENT) { if (sourceProvider.mustEvaluateReturnedExpression()) { ExpressionStatement stmt = DomFactory.eINSTANCE.createExpressionStatement(); stmt.setExpression((Expression)node); list.set(idx,stmt); } else list.remove(idx); } else { list.set(idx,stmts.get(stmts.size()-1)); } } else { list.set(idx,stmts.get(stmts.size()-1)); } } new ReceiverAnalyzer(replacements, bindings).traverse(stmts,node); return new RefactoringStatus(); } /*private void initializeInsertionPoint(int nos) { switch(container.eClass().getClassifierID()) { case DomPackage.BLOCK_STATEMENT: Block block= (Block)container; fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY); fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement); break; case DomPackage.SWITCH_STATEMENT: SwitchStatement switchStatement= (SwitchStatement)container; fListRewrite= fRewrite.getListRewrite(switchStatement, SwitchStatement.STATEMENTS_PROPERTY); fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement); break; default: // 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.ENHANCED_FOR_STATEMENT: currentStatement= ((EnhancedForStatement)container).getBody(); break; case ASTNode.WHILE_STATEMENT: currentStatement= ((WhileStatement)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; * function toInline() { * if (a) * hashCode() * } * function m() { * if (b) * toInline() * else * toString() * } * => needs block around inlined "if (a)..." to avoid attaching else to wrong if. */ return targetNode.eContainmentFeature() == DomPackage.eINSTANCE.getIfStatement_Consequent() && ((IfStatement)targetNode.eContainer()).getAlternative() == null && sourceProvider.isDanglingIf(); } /*private String getContent(Node node) throws ModelException { return cu.getBuffer().getText(node.getBegin(), node.getEnd()); }*/ /*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; }*/ }