blob: 5e45b9b7fa12a288e47c49b962aa1f22c0cd2105 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* 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;
}*/
}