blob: 63b388a23813f59a5c885b798c1dcc757cc08cff [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://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 fix for
* o bug "inline method - doesn't handle implicit cast" (see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941).
* 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)
* Microsoft Corporation - copied to jdt.core.manipulation
* Microsoft Corporation - read formatting options from the compilation unit
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.RangeMarker;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditProcessor;
import org.eclipse.text.edits.UndoEdit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ChildPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation;
import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
import org.eclipse.jdt.internal.core.manipulation.dom.NecessaryParenthesesChecker;
import org.eclipse.jdt.internal.core.manipulation.util.Strings;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.CodeScopeBuilder;
import org.eclipse.jdt.internal.corext.refactoring.code.SourceAnalyzer.NameData;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringFileBuffers;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
/**
* A SourceProvider encapsulates a piece of code (source) and the logic
* to inline it into given CallContexts.
*/
public class SourceProvider {
private ITypeRoot fTypeRoot;
private IDocument fDocument;
private MethodDeclaration fDeclaration;
private SourceAnalyzer fAnalyzer;
private boolean fMustEvalReturnedExpression;
private boolean fReturnValueNeedsLocalVariable;
private List<Expression> fReturnExpressions;
private IDocument fSource;
private static final int EXPRESSION_MODE= 1;
private static final int STATEMENT_MODE= 2;
private static final int RETURN_STATEMENT_MODE= 3;
private int fMarkerMode;
private class ReturnAnalyzer extends ASTVisitor {
@Override
public boolean visit(ReturnStatement node) {
Expression expression= node.getExpression();
if (!ASTNodes.isLiteral(expression) && !(expression instanceof Name)) {
fMustEvalReturnedExpression= true;
}
if (Invocations.isInvocation(expression) || expression instanceof ClassInstanceCreation) {
fReturnValueNeedsLocalVariable= false;
}
fReturnExpressions.add(expression);
return false;
}
}
public SourceProvider(ITypeRoot typeRoot, MethodDeclaration declaration) {
super();
fTypeRoot= typeRoot;
fDeclaration= declaration;
List<SingleVariableDeclaration> parameters= fDeclaration.parameters();
for (SingleVariableDeclaration element : parameters) {
ParameterData data= new ParameterData(element);
element.setProperty(ParameterData.PROPERTY, data);
}
fAnalyzer= new SourceAnalyzer(fTypeRoot, fDeclaration);
fReturnValueNeedsLocalVariable= true;
fReturnExpressions= new ArrayList<>();
}
/**
* TODO: unit's source does not match contents of source document and declaration node.
* @param typeRoot the type root
* @param source document containing the content of the type root
* @param declaration method declaration node
*/
public SourceProvider(ITypeRoot typeRoot, IDocument source, MethodDeclaration declaration) {
this(typeRoot, declaration);
fSource= source;
}
public RefactoringStatus checkActivation() throws JavaModelException {
return fAnalyzer.checkActivation();
}
public void initialize() throws JavaModelException {
fDocument= fSource == null ? new Document(fTypeRoot.getBuffer().getContents()) : fSource;
fAnalyzer.initialize();
if (hasReturnValue()) {
ASTNode last= getLastStatement();
if (last != null) {
ReturnAnalyzer analyzer= new ReturnAnalyzer();
last.accept(analyzer);
}
}
}
public boolean isExecutionFlowInterrupted() {
return fAnalyzer.isExecutionFlowInterrupted();
}
static class VariableReferenceFinder extends ASTVisitor {
private boolean fResult;
private IVariableBinding fBinding;
public VariableReferenceFinder(IVariableBinding binding) {
fBinding= binding;
}
public boolean getResult() {
return fResult;
}
@Override
public boolean visit(SimpleName node) {
if(!fResult) {
fResult= Bindings.equals(fBinding, node.resolveBinding());
}
return false;
}
}
/**
* Checks whether variable is referenced by the method declaration or not.
*
* @param binding binding of variable to check.
* @return <code>true</code> if variable is referenced by the method, otherwise <code>false</code>
*/
public boolean isVariableReferenced(IVariableBinding binding) {
VariableReferenceFinder finder= new VariableReferenceFinder(binding);
fDeclaration.accept(finder);
return finder.getResult();
}
public boolean hasReturnValue() {
IMethodBinding binding= fDeclaration.resolveBinding();
return binding.getReturnType() != fDeclaration.getAST().resolveWellKnownType("void"); //$NON-NLS-1$
}
// returns true if the declaration has a vararg and
// the body contains an array access to the vararg
public boolean hasArrayAccess() {
return fAnalyzer.hasArrayAccess();
}
public boolean hasSuperMethodInvocation() {
return fAnalyzer.hasSuperMethodInvocation();
}
public boolean mustEvaluateReturnedExpression() {
return fMustEvalReturnedExpression;
}
public boolean returnValueNeedsLocalVariable() {
return fReturnValueNeedsLocalVariable;
}
public int getNumberOfStatements() {
return fDeclaration.getBody().statements().size();
}
public boolean isSimpleFunction() {
List<Statement> statements= fDeclaration.getBody().statements();
if (statements.size() != 1)
return false;
return statements.get(0) instanceof ReturnStatement;
}
public boolean isLastStatementReturn() {
List<Statement> statements= fDeclaration.getBody().statements();
if (statements.size() == 0)
return false;
return statements.get(statements.size() - 1) instanceof ReturnStatement;
}
public boolean isDangligIf() {
List<Statement> statements= fDeclaration.getBody().statements();
if (statements.size() != 1)
return false;
ASTNode p= statements.get(0);
while (true) {
if (p instanceof IfStatement) {
return ((IfStatement) p).getElseStatement() == null;
} else {
ChildPropertyDescriptor childD;
if (p instanceof WhileStatement) {
childD= WhileStatement.BODY_PROPERTY;
} else if (p instanceof ForStatement) {
childD= ForStatement.BODY_PROPERTY;
} else if (p instanceof EnhancedForStatement) {
childD= EnhancedForStatement.BODY_PROPERTY;
} else if (p instanceof DoStatement) {
childD= DoStatement.BODY_PROPERTY;
} else if (p instanceof LabeledStatement) {
childD= LabeledStatement.BODY_PROPERTY;
} else {
return false;
}
Statement body= (Statement) p.getStructuralProperty(childD);
if (body instanceof Block) {
return false;
} else {
p= body;
}
}
}
}
public MethodDeclaration getDeclaration() {
return fDeclaration;
}
public String getMethodName() {
return fDeclaration.getName().getIdentifier();
}
public ITypeBinding getReturnType() {
return fDeclaration.resolveBinding().getReturnType();
}
public List<Expression> getReturnExpressions() {
return fReturnExpressions;
}
public boolean returnTypeMatchesReturnExpressions() {
ITypeBinding returnType= getReturnType();
for (Expression expression : fReturnExpressions) {
if (!Bindings.equals(returnType, expression.resolveTypeBinding()))
return false;
}
return true;
}
public ParameterData getParameterData(int index) {
SingleVariableDeclaration decl= (SingleVariableDeclaration)fDeclaration.parameters().get(index);
return (ParameterData)decl.getProperty(ParameterData.PROPERTY);
}
public ITypeRoot getTypeRoot() {
return fTypeRoot;
}
public boolean needsReturnedExpressionParenthesis(ASTNode parent, StructuralPropertyDescriptor locationInParent) {
ASTNode last= getLastStatement();
if (last instanceof ReturnStatement) {
return NecessaryParenthesesChecker.needsParentheses(((ReturnStatement)last).getExpression(), parent, locationInParent);
}
return false;
}
public boolean returnsConditionalExpression() {
ASTNode last= getLastStatement();
if (last instanceof ReturnStatement) {
return ((ReturnStatement)last).getExpression() instanceof ConditionalExpression;
}
return false;
}
public int getReceiversToBeUpdated() {
return fAnalyzer.getImplicitReceivers().size();
}
public boolean isVarargs() {
return fDeclaration.isVarargs();
}
public int getVarargIndex() {
return fDeclaration.parameters().size() - 1;
}
public TextEdit getDeleteEdit() {
final ASTRewrite rewriter= ASTRewrite.create(fDeclaration.getAST());
rewriter.remove(fDeclaration, null);
Map<String, String> options= fTypeRoot instanceof ICompilationUnit ?
((ICompilationUnit) fTypeRoot).getOptions(true) : fTypeRoot.getJavaProject().getOptions(true);
return rewriter.rewriteAST(fDocument, options);
}
public String[] getCodeBlocks(CallContext context, ImportRewrite importRewrite) throws CoreException {
final ASTRewrite rewriter= ASTRewrite.create(fDeclaration.getAST());
replaceParameterWithExpression(rewriter, context, importRewrite);
updateImplicitReceivers(rewriter, context);
makeNamesUnique(rewriter, context.scope);
updateTypeReferences(rewriter, context);
updateStaticReferences(rewriter, context);
updateTypeVariables(rewriter, context);
updateMethodTypeVariable(rewriter, context);
List<IRegion> 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();
}
}
Map<String, String> options= fTypeRoot instanceof ICompilationUnit ?
((ICompilationUnit) fTypeRoot).getOptions(true) : fTypeRoot.getJavaProject().getOptions(true);
final TextEdit dummy= rewriter.rewriteAST(fDocument, options);
int size= ranges.size();
RangeMarker[] markers= new RangeMarker[size];
for (int i= 0; i < markers.length; i++) {
IRegion range= ranges.get(i);
markers[i]= new RangeMarker(range.getOffset(), range.getLength());
}
int split;
if (size <= 1) {
split= Integer.MAX_VALUE;
} else {
IRegion region= ranges.get(0);
split= region.getOffset() + region.getLength();
}
for (TextEdit edit : dummy.removeChildren()) {
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 | BadLocationException exception) {
JavaManipulationPlugin.log(exception);
}
return new String[] {};
}
private Expression createParenthesizedExpression(Expression newExpression, AST ast) {
ParenthesizedExpression parenthesized= ast.newParenthesizedExpression();
parenthesized.setExpression(newExpression);
return parenthesized;
}
private void replaceParameterWithExpression(ASTRewrite rewriter, CallContext context, ImportRewrite importRewrite) throws CoreException {
Expression[] arguments= context.arguments;
try {
ITextFileBuffer buffer= RefactoringFileBuffers.acquire(context.compilationUnit);
for (int i= 0; i < arguments.length; i++) {
Expression expression= arguments[i];
String expressionString= null;
if (expression instanceof SimpleName) {
expressionString= ((SimpleName)expression).getIdentifier();
} else {
try {
expressionString= buffer.getDocument().get(expression.getStartPosition(), expression.getLength());
} catch (BadLocationException exception) {
JavaManipulationPlugin.log(exception);
continue;
}
}
ParameterData parameter= getParameterData(i);
List<SimpleName> references= parameter.references();
for (SimpleName simpleName : references) {
ASTNode element= simpleName;
Expression newExpression= (Expression)rewriter.createStringPlaceholder(expressionString, expression.getNodeType());
AST ast= rewriter.getAST();
ITypeBinding explicitCast= ASTNodes.getExplicitCast(expression, (Expression)element);
if (explicitCast != null) {
CastExpression cast= ast.newCastExpression();
if (NecessaryParenthesesChecker.needsParentheses(expression, cast, CastExpression.EXPRESSION_PROPERTY)) {
newExpression= createParenthesizedExpression(newExpression, ast);
}
cast.setExpression(newExpression);
ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(expression, importRewrite);
cast.setType(importRewrite.addImport(explicitCast, ast, importRewriteContext, TypeLocation.CAST));
expression= newExpression= cast;
}
if (NecessaryParenthesesChecker.needsParentheses(expression, element.getParent(), element.getLocationInParent())) {
newExpression= createParenthesizedExpression(newExpression, ast);
}
rewriter.replace(element, newExpression, null);
}
}
} finally {
RefactoringFileBuffers.release(context.compilationUnit);
}
}
private void makeNamesUnique(ASTRewrite rewriter, CodeScopeBuilder.Scope scope) {
Collection<NameData> usedCalleeNames= fAnalyzer.getUsedNames();
for (NameData nd : usedCalleeNames) {
if (scope.isInUse(nd.getName())) {
String newName= scope.createName(nd.getName(), true);
List<SimpleName> references= nd.references();
for (SimpleName element : references) {
ASTNode newNode= rewriter.createStringPlaceholder(newName, ASTNode.SIMPLE_NAME);
rewriter.replace(element, newNode, null);
}
}
}
}
private void updateImplicitReceivers(ASTRewrite rewriter, CallContext context) {
if (context.receiver == null)
return;
List<Expression> implicitReceivers= fAnalyzer.getImplicitReceivers();
for (Expression expression : implicitReceivers) {
ASTNode node= expression;
ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(node, context.importer);
if (node instanceof MethodInvocation) {
final MethodInvocation inv= (MethodInvocation)node;
rewriter.set(inv, MethodInvocation.EXPRESSION_PROPERTY, createReceiver(rewriter, context, (IMethodBinding)inv.getName().resolveBinding(), importRewriteContext), null);
} else if (node instanceof ClassInstanceCreation) {
final ClassInstanceCreation inst= (ClassInstanceCreation)node;
rewriter.set(inst, ClassInstanceCreation.EXPRESSION_PROPERTY, createReceiver(rewriter, context, inst.resolveConstructorBinding(), importRewriteContext), null);
} else if (node instanceof ThisExpression) {
rewriter.replace(node, rewriter.createStringPlaceholder(context.receiver, ASTNode.METHOD_INVOCATION), null);
} else if (node instanceof FieldAccess) {
final FieldAccess access= (FieldAccess)node;
rewriter.set(access, FieldAccess.EXPRESSION_PROPERTY, createReceiver(rewriter, context, access.resolveFieldBinding(), importRewriteContext), null);
} else if (node instanceof SimpleName && ((SimpleName)node).resolveBinding() instanceof IVariableBinding) {
IVariableBinding vb= (IVariableBinding)((SimpleName)node).resolveBinding();
if (vb.isField()) {
Expression receiver= createReceiver(rewriter, context, vb, importRewriteContext);
if (receiver != null) {
if (!vb.isEnumConstant() || node.getLocationInParent() != SwitchCase.EXPRESSIONS2_PROPERTY) {
FieldAccess access= node.getAST().newFieldAccess();
ASTNode target= rewriter.createMoveTarget(node);
access.setName((SimpleName)target);
access.setExpression(receiver);
rewriter.replace(node, access, null);
}
}
}
}
}
}
private void updateTypeReferences(ASTRewrite rewriter, CallContext context) {
ImportRewrite importer= context.importer;
for (SimpleName simpleName : fAnalyzer.getTypesToImport()) {
Name element= simpleName;
ITypeBinding binding= ASTNodes.getTypeBinding(element);
if (binding != null && !binding.isLocal()) {
// We have collected names not types. So we have to import
// the declaration type if we reference a parameterized type
// since we have an entry for every name node (e.g. one for
// Vector and one for Integer in Vector<Integer>.
if (binding.isParameterizedType()) {
binding= binding.getTypeDeclaration();
}
String[] bindingNameComponents= Bindings.getNameComponents(binding);
try {
IType[] types= context.compilationUnit.getAllTypes();
for (IType type : types) {
String typeName= type.getFullyQualifiedName();
String[] typeNameComponents= typeName.split("\\.|\\$"); //$NON-NLS-1$
if (Arrays.equals(bindingNameComponents, typeNameComponents)) {
return;
}
}
String s= importer.addImport(binding);
if (!ASTNodes.asString(element).equals(s)) {
rewriter.replace(element, rewriter.createStringPlaceholder(s, ASTNode.SIMPLE_NAME), null);
}
} catch (JavaModelException e) {
// do nothing
}
}
}
}
private void updateStaticReferences(ASTRewrite rewriter, CallContext context) {
ImportRewrite importer= context.importer;
for (SimpleName simpleName : fAnalyzer.getStaticsToImport()) {
Name element= simpleName;
IBinding binding= element.resolveBinding();
if (binding != null) {
String s= importer.addStaticImport(binding);
if (!ASTNodes.asString(element).equals(s)) {
rewriter.replace(element, rewriter.createStringPlaceholder(s, ASTNode.SIMPLE_NAME), null);
}
}
}
}
private Expression createReceiver(ASTRewrite rewriter, CallContext context, IMethodBinding method, ImportRewriteContext importRewriteContext) {
String receiver= getReceiver(context, method.getModifiers(), importRewriteContext);
if (receiver == null)
return null;
return (Expression)rewriter.createStringPlaceholder(receiver, ASTNode.METHOD_INVOCATION);
}
private Expression createReceiver(ASTRewrite rewriter, CallContext context, IVariableBinding field, ImportRewriteContext importRewriteContext) {
String receiver= getReceiver(context, field.getModifiers(), importRewriteContext);
if (receiver == null)
return null;
return (Expression)rewriter.createStringPlaceholder(receiver, ASTNode.SIMPLE_NAME);
}
private String getReceiver(CallContext context, int modifiers, ImportRewriteContext importRewriteContext) {
String receiver= context.receiver;
ITypeBinding invocationType= ASTNodes.getEnclosingType(context.invocation);
ITypeBinding sourceType= fDeclaration.resolveBinding().getDeclaringClass();
if (invocationType != null && invocationType.getName().equals(receiver)) {
return null;
}
if (!context.receiverIsStatic && Modifier.isStatic(modifiers)) {
if ("this".equals(receiver) && invocationType != null && Bindings.equals(invocationType, sourceType)) { //$NON-NLS-1$
receiver= null;
} else {
receiver= context.importer.addImport(sourceType, importRewriteContext);
}
}
return receiver;
}
private void updateTypeVariables(ASTRewrite rewriter, CallContext context) {
ITypeBinding type= context.getReceiverType();
if (type == null)
return;
rewriteReferences(rewriter, type.getTypeArguments(), fAnalyzer.getTypeParameterReferences());
}
private void updateMethodTypeVariable(ASTRewrite rewriter, CallContext context) {
IMethodBinding method= Invocations.resolveBinding(context.invocation);
if (method == null)
return;
rewriteReferences(rewriter, method.getTypeArguments(), fAnalyzer.getMethodTypeParameterReferences());
}
private void rewriteReferences(ASTRewrite rewriter, ITypeBinding[] typeArguments, List<NameData> typeParameterReferences) {
if (typeArguments.length == 0)
return;
Assert.isTrue(typeArguments.length == typeParameterReferences.size());
for (int i= 0; i < typeArguments.length; i++) {
SourceAnalyzer.NameData refData= typeParameterReferences.get(i);
List<SimpleName> references= refData.references();
String newName= typeArguments[i].getName();
for (SimpleName name : references) {
rewriter.replace(name, rewriter.createStringPlaceholder(newName, ASTNode.SIMPLE_NAME), null);
}
}
}
private ASTNode getLastStatement() {
List<Statement> statements= fDeclaration.getBody().statements();
if (statements.isEmpty())
return null;
return statements.get(statements.size() - 1);
}
private List<IRegion> getReturnStatementRanges() {
fMarkerMode= RETURN_STATEMENT_MODE;
List<IRegion> result= new ArrayList<>(1);
List<Statement> statements= fDeclaration.getBody().statements();
int size= statements.size();
if (size <= 1)
return result;
result.add(createRange(statements, size - 2));
return result;
}
private List<IRegion> getStatementRanges() {
fMarkerMode= STATEMENT_MODE;
List<IRegion> result= new ArrayList<>(1);
List<Statement> statements= fDeclaration.getBody().statements();
int size= statements.size();
if (size == 0)
return result;
result.add(createRange(statements, size - 1));
return result;
}
private List<IRegion> getExpressionRanges() {
fMarkerMode= EXPRESSION_MODE;
List<IRegion> result= new ArrayList<>(2);
List<Statement> statements= fDeclaration.getBody().statements();
ReturnStatement rs= null;
int size= statements.size();
ASTNode node;
switch (size) {
case 0:
return result;
case 1:
node= statements.get(0);
if (node.getNodeType() == ASTNode.RETURN_STATEMENT) {
rs= (ReturnStatement)node;
} else {
result.add(createRange(node, node));
}
break;
default: {
node= statements.get(size - 1);
if (node.getNodeType() == ASTNode.RETURN_STATEMENT) {
result.add(createRange(statements, size - 2));
rs= (ReturnStatement)node;
} else {
result.add(createRange(statements, size - 1));
}
break;
}
}
if (rs != null) {
Expression exp= rs.getExpression();
result.add(createRange(exp, exp));
}
return result;
}
private IRegion createRange(List<Statement> statements, int end) {
ASTNode first= statements.get(0);
ASTNode last= statements.get(end);
return createRange(first, last);
}
private IRegion createRange(ASTNode first, ASTNode last) {
ASTNode root= first.getRoot();
if (root instanceof CompilationUnit) {
CompilationUnit unit= (CompilationUnit)root;
int start= unit.getExtendedStartPosition(first);
int length = unit.getExtendedStartPosition(last) - start + unit.getExtendedLength(last);
IRegion range= new Region(start, length);
return range;
} else {
int start= first.getStartPosition();
int length = last.getStartPosition() - start + last.getLength();
IRegion range= new Region(start, length);
return range;
}
}
private String[] getBlocks(RangeMarker[] markers) throws BadLocationException {
String[] result= new String[markers.length];
final ICompilationUnit cu= fTypeRoot instanceof ICompilationUnit ? (ICompilationUnit) fTypeRoot : null;
final IJavaProject project= fTypeRoot.getJavaProject();
for (int i= 0; i < markers.length; i++) {
RangeMarker marker= markers[i];
String content= fDocument.get(marker.getOffset(), marker.getLength());
String lines[]= Strings.convertIntoLines(content);
if (cu != null) {
Strings.trimIndentation(lines, cu, false);
} else {
Strings.trimIndentation(lines, project, false);
}
if (fMarkerMode == STATEMENT_MODE && lines.length == 2 && isSingleControlStatementWithoutBlock()) {
lines[1]= cu != null ? CodeFormatterUtil.createIndentString(1, cu) + lines[1] : CodeFormatterUtil.createIndentString(1, project) + lines[1];
}
result[i]= Strings.concatenate(lines, TextUtilities.getDefaultLineDelimiter(fDocument));
}
return result;
}
private boolean isSingleControlStatementWithoutBlock() {
List<Statement> statements= fDeclaration.getBody().statements();
int size= statements.size();
if (size != 1)
return false;
Statement statement= statements.get(size - 1);
int nodeType= statement.getNodeType();
if (nodeType == ASTNode.IF_STATEMENT) {
IfStatement ifStatement= (IfStatement) statement;
return !(ifStatement.getThenStatement() instanceof Block)
&& !(ifStatement.getElseStatement() instanceof Block);
} else if (nodeType == ASTNode.FOR_STATEMENT) {
return !(((ForStatement)statement).getBody() instanceof Block);
} else if (nodeType == ASTNode.WHILE_STATEMENT) {
return !(((WhileStatement)statement).getBody() instanceof Block);
}
return false;
}
}