blob: 57e11f25f129bb30d2bc24a1aa419f234c9299b3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2019 Mateusz Matela 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:
* Mateusz Matela <mateusz.matela@gmail.com> - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519
* Mateusz Matela <mateusz.matela@gmail.com> - [formatter] IndexOutOfBoundsException in TokenManager - https://bugs.eclipse.org/462945
* Mateusz Matela <mateusz.matela@gmail.com> - [formatter] follow up bug for comments - https://bugs.eclipse.org/458208
* IBM Corporation - DOM AST changes for JEP 354
*******************************************************************************/
package org.eclipse.jdt.internal.formatter;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameAT;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOLON;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMA;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_JAVADOC;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLBRACE;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameRBRACE;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameSEMICOLON;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameTextBlock;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamebase;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameelse;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamefinally;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewhen;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewhile;
import java.util.ArrayList;
import java.util.List;
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.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CallinMappingDeclaration;
import org.eclipse.jdt.core.dom.CalloutMappingDeclaration;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EmptyStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.GuardPredicateDeclaration;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.ModuleDeclaration;
import org.eclipse.jdt.core.dom.ModuleDirective;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.RoleTypeDeclaration;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchExpression;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.TextBlock;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.core.dom.WithinStatement;
import org.eclipse.jdt.core.dom.YieldStatement;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jdt.core.dom.AbstractMethodMappingDeclaration;
import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions.Alignment;
import org.eclipse.jdt.internal.formatter.Token.WrapMode;
import org.eclipse.jdt.internal.formatter.Token.WrapPolicy;
import org.eclipse.jface.text.IRegion;
public class LineBreaksPreparator extends ASTVisitor {
final private TokenManager tm;
final private DefaultCodeFormatterOptions options;
private boolean declarationModifierVisited;
public LineBreaksPreparator(TokenManager tokenManager, DefaultCodeFormatterOptions options) {
this.tm = tokenManager;
this.options = options;
}
@Override
public boolean preVisit2(ASTNode node) {
boolean isMalformed = (node.getFlags() & ASTNode.MALFORMED) != 0;
return !isMalformed;
}
@Override
public boolean visit(CompilationUnit node) {
List<ImportDeclaration> imports = node.imports();
if (!imports.isEmpty() && this.tm.firstIndexIn(imports.get(0), -1) > 0)
putBlankLinesBefore(imports.get(0), this.options.blank_lines_before_imports);
for (int i = 1; i < imports.size(); i++) {
int from = this.tm.lastIndexIn(imports.get(i - 1), -1);
int to = this.tm.firstIndexIn(imports.get(i), -1);
for (int j = from; j < to; j++) {
Token token1 = this.tm.get(j);
Token token2 = this.tm.get(j + 1);
if (this.tm.countLineBreaksBetween(token1, token2) > 1)
putBlankLinesAfter(token1, this.options.blank_lines_between_import_groups);
}
}
List<AnnotationTypeDeclaration> types = node.types();
if (!types.isEmpty()) {
if (!imports.isEmpty())
putBlankLinesBefore(types.get(0), this.options.blank_lines_after_imports);
for (int i = 1; i < types.size(); i++)
putBlankLinesBefore(types.get(i), this.options.blank_lines_between_type_declarations);
}
return true;
}
@Override
public boolean visit(PackageDeclaration node) {
if (node.getJavadoc() == null) {
putBlankLinesBefore(node, this.options.blank_lines_before_package);
} else {
putBlankLinesAfter(this.tm.lastTokenIn(node.getJavadoc(), -1), this.options.blank_lines_before_package);
}
putBlankLinesAfter(this.tm.lastTokenIn(node, -1), this.options.blank_lines_after_package);
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(ImportDeclaration node) {
breakLineBefore(node);
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
//{ObjectTeams:
GuardPredicateDeclaration predicate = node.getGuardPredicate();
if (predicate != null) {
this.tm.firstTokenIn(node.getGuardPredicate(), predicate.isBase() ? TokenNamebase : TokenNamewhen).breakBefore();
indent(node.getGuardPredicate());
indent(node.getGuardPredicate());
this.tm.firstTokenAfter(node.getGuardPredicate(), TokenNameLBRACE).breakBefore();
}
// SH}
handleBodyDeclarations(node.bodyDeclarations());
if (this.tm.isFake(node))
return true;
breakLineBefore(node);
handleBracedCode(node, node.getName(), this.options.brace_position_for_type_declaration,
this.options.indent_body_declarations_compare_to_type_header);
this.declarationModifierVisited = false;
return true;
}
private void handleBodyDeclarations(List<BodyDeclaration> bodyDeclarations) {
BodyDeclaration previous = null;
for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
int blankLines = 0;
if (previous == null) {
blankLines = this.options.blank_lines_before_first_class_body_declaration;
} else if (!sameChunk(previous, bodyDeclaration)) {
blankLines = this.options.blank_lines_before_new_chunk;
} else if (bodyDeclaration instanceof FieldDeclaration) {
blankLines = this.options.blank_lines_before_field;
} else if (bodyDeclaration instanceof AbstractTypeDeclaration) {
blankLines = this.options.blank_lines_before_member_type;
} else if (bodyDeclaration instanceof MethodDeclaration) {
blankLines = ((MethodDeclaration) bodyDeclaration).getBody() == null
&& ((MethodDeclaration) previous).getBody() == null
? this.options.blank_lines_before_abstract_method
: this.options.blank_lines_before_method;
} else if (bodyDeclaration instanceof AnnotationTypeMemberDeclaration) {
blankLines = this.options.blank_lines_before_method;
//{ObjectTeams: method mappings like methods:
} else if (bodyDeclaration instanceof AbstractMethodMappingDeclaration) {
blankLines = this.options.blank_lines_before_method;
// SH}
}
putBlankLinesBefore(bodyDeclaration, blankLines);
previous = bodyDeclaration;
}
if (previous != null) {
ASTNode parent = previous.getParent();
if (!(parent instanceof TypeDeclaration && this.tm.isFake((TypeDeclaration) parent))) {
Token lastToken = this.tm.lastTokenIn(parent, -1);
putBlankLinesBefore(lastToken, this.options.blank_lines_after_last_class_body_declaration);
}
}
}
private boolean sameChunk(BodyDeclaration bd1, BodyDeclaration bd2) {
if (bd1.getClass().equals(bd2.getClass()))
return true;
if (bd1 instanceof AbstractTypeDeclaration && bd2 instanceof AbstractTypeDeclaration)
return true;
if ((bd1 instanceof FieldDeclaration || bd1 instanceof Initializer)
&& (bd2 instanceof FieldDeclaration || bd2 instanceof Initializer))
return true; // special case: initializers are often related to fields, don't separate
return false;
}
@Override
public boolean visit(EnumDeclaration node) {
handleBracedCode(node, node.getName(), this.options.brace_position_for_enum_declaration,
this.options.indent_body_declarations_compare_to_enum_declaration_header);
List<BodyDeclaration> declarations = node.bodyDeclarations();
List<EnumConstantDeclaration> enumConstants = node.enumConstants();
if (!declarations.isEmpty()) {
if (!enumConstants.isEmpty()) {
declarations = new ArrayList<>(declarations);
declarations.add(0, enumConstants.get(0));
}
handleBodyDeclarations(declarations);
}
for (int i = 0; i < enumConstants.size(); i++) {
EnumConstantDeclaration declaration = enumConstants.get(i);
if (declaration.getJavadoc() != null)
this.tm.firstTokenIn(declaration, TokenNameCOMMENT_JAVADOC).breakBefore();
if (declaration.getAnonymousClassDeclaration() != null && i < enumConstants.size() - 1)
this.tm.firstTokenAfter(declaration, TokenNameCOMMA).breakAfter();
}
// put breaks after semicolons
int index = enumConstants.isEmpty() ? this.tm.firstIndexAfter(node.getName(), TokenNameLBRACE) + 1
: this.tm.firstIndexAfter(enumConstants.get(enumConstants.size() - 1), -1);
for (;; index++) {
Token token = this.tm.get(index);
if (token.isComment())
continue;
if (token.tokenType == TokenNameSEMICOLON)
token.breakAfter();
else
break;
}
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
handleBracedCode(node, node.getName(), this.options.brace_position_for_annotation_type_declaration,
this.options.indent_body_declarations_compare_to_annotation_declaration_header);
handleBodyDeclarations(node.bodyDeclarations());
if (node.getModifiers() == 0)
this.tm.firstTokenBefore(node.getName(), TokenNameAT).breakBefore();
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
if (node.getParent() instanceof EnumConstantDeclaration) {
handleBracedCode(node, null, this.options.brace_position_for_enum_constant,
this.options.indent_body_declarations_compare_to_enum_constant_header);
} else {
handleBracedCode(node, null, this.options.brace_position_for_anonymous_type_declaration,
this.options.indent_body_declarations_compare_to_type_header);
}
handleBodyDeclarations(node.bodyDeclarations());
return true;
}
@Override
public boolean visit(RecordDeclaration node) {
handleBracedCode(node, node.getName(), this.options.brace_position_for_record_declaration,
this.options.indent_body_declarations_compare_to_record_header);
handleBodyDeclarations(node.bodyDeclarations());
return true;
}
@Override
public boolean visit(MethodDeclaration node) {
this.declarationModifierVisited = false;
if (node.getBody() == null)
return true;
String bracePosition = node.isCompactConstructor() ? this.options.brace_position_for_record_constructor
: node.isConstructor() ? this.options.brace_position_for_constructor_declaration
: this.options.brace_position_for_method_declaration;
handleBracedCode(node.getBody(), null, bracePosition, this.options.indent_statements_compare_to_body,
this.options.blank_lines_at_beginning_of_method_body, this.options.blank_lines_at_end_of_method_body);
return true;
}
@Override
public boolean visit(Block node) {
List<Statement> statements = node.statements();
for (Statement statement : statements) {
if (this.options.put_empty_statement_on_new_line || !(statement instanceof EmptyStatement))
breakLineBefore(statement);
}
ASTNode parent = node.getParent();
if (parent.getLength() == 0)
return true; // this is a fake block created by parsing in statements mode
if (parent instanceof MethodDeclaration)
return true; // braces have been handled in #visit(MethodDeclaration)
String bracePosition = this.options.brace_position_for_block;
if (parent instanceof SwitchStatement) {
List<Statement> siblings = ((SwitchStatement) parent).statements();
int blockPosition = siblings.indexOf(node);
boolean isFirstInCase = blockPosition > 0 && (siblings.get(blockPosition - 1) instanceof SwitchCase);
if (isFirstInCase)
bracePosition = this.options.brace_position_for_block_in_case;
} else if (parent instanceof LambdaExpression) {
bracePosition = this.options.brace_position_for_lambda_body;
}
handleBracedCode(node, null, bracePosition, this.options.indent_statements_compare_to_block,
this.options.blank_lines_at_beginning_of_code_block, this.options.blank_lines_at_end_of_code_block);
if (parent instanceof Block) {
blankLinesAroundBlock(node, ((Block) parent).statements());
} else if (parent instanceof Statement && parent.getParent() instanceof Block) {
blankLinesAroundBlock(parent, ((Block) parent.getParent()).statements());
}
return true;
}
private void blankLinesAroundBlock(ASTNode blockStatement, List<ASTNode> siblings) {
putBlankLinesBefore(blockStatement, this.options.blank_lines_before_code_block);
if (!this.options.put_empty_statement_on_new_line) {
int blockIndex = siblings.indexOf(blockStatement);
if (blockIndex + 1 < siblings.size() && siblings.get(blockIndex + 1) instanceof EmptyStatement)
return;
}
putBlankLinesAfter(this.tm.lastTokenIn(blockStatement, -1), this.options.blank_lines_after_code_block);
}
@Override
public boolean visit(SwitchStatement node) {
handleBracedCode(node, node.getExpression(), this.options.brace_position_for_switch,
this.options.indent_switchstatements_compare_to_switch,
this.options.blank_lines_at_beginning_of_code_block, this.options.blank_lines_at_end_of_code_block);
List<Statement> statements = node.statements();
doSwitchStatementsIndentation(node, statements);
doSwitchStatementsLineBreaks(statements);
if (node.getParent() instanceof Block)
blankLinesAroundBlock(node, ((Block) node.getParent()).statements());
return true;
}
@Override
public boolean visit(SwitchExpression node) {
handleBracedCode(node, node.getExpression(), this.options.brace_position_for_switch,
this.options.indent_switchstatements_compare_to_switch,
this.options.blank_lines_at_beginning_of_code_block, this.options.blank_lines_at_end_of_code_block);
List<Statement> statements = node.statements();
doSwitchStatementsIndentation(node, statements);
doSwitchStatementsLineBreaks(statements);
return true;
}
private void doSwitchStatementsIndentation(ASTNode switchNode, List<Statement> statements) {
if (this.options.indent_switchstatements_compare_to_cases) {
int nonBreakStatementEnd = -1;
for (Statement statement : statements) {
boolean isBreaking = isSwitchBreakingStatement(statement);
if (isBreaking && !(statement instanceof Block))
adjustEmptyLineAfter(this.tm.lastIndexIn(statement, -1), -1);
if (statement instanceof SwitchCase) {
if (nonBreakStatementEnd >= 0) {
// indent only comments between previous and current statement
this.tm.get(nonBreakStatementEnd + 1).indent();
this.tm.firstTokenIn(statement, -1).unindent();
}
} else if (!(statement instanceof BreakStatement || statement instanceof YieldStatement
|| statement instanceof Block)) {
indent(statement);
}
nonBreakStatementEnd = isBreaking ? -1 : this.tm.lastIndexIn(statement, -1);
}
if (nonBreakStatementEnd >= 0) {
// indent comments between last statement and closing brace
this.tm.get(nonBreakStatementEnd + 1).indent();
this.tm.lastTokenIn(switchNode, TokenNameRBRACE).unindent();
}
}
if (this.options.indent_breaks_compare_to_cases) {
for (Statement statement : statements) {
if (statement instanceof BreakStatement || statement instanceof YieldStatement)
indent(statement);
}
}
}
private void doSwitchStatementsLineBreaks(List<Statement> statements) {
boolean arrowMode = statements.stream()
.anyMatch(s -> s instanceof SwitchCase && s.getAST().apiLevel() >= AST.JLS14 &&((SwitchCase) s).isSwitchLabeledRule());
Statement previous = null;
for (Statement statement : statements) {
boolean skip = statement instanceof Block // will add break in visit(Block) if necessary
|| (arrowMode && !(statement instanceof SwitchCase))
|| (statement instanceof EmptyStatement && !this.options.put_empty_statement_on_new_line);
if (!skip) {
boolean newGroup = !arrowMode && statement instanceof SwitchCase && isSwitchBreakingStatement(previous);
int blankLines = newGroup ? this.options.blank_lines_between_statement_groups_in_switch : 0;
putBlankLinesBefore(statement, blankLines);
}
previous = statement;
}
}
private boolean isSwitchBreakingStatement(Statement statement) {
return statement instanceof BreakStatement || statement instanceof ReturnStatement
|| statement instanceof ContinueStatement || statement instanceof ThrowStatement
|| statement instanceof YieldStatement || statement instanceof Block;
}
@Override
public boolean visit(DoStatement node) {
Statement body = node.getBody();
boolean sameLine = this.options.keep_simple_do_while_body_on_same_line;
if (!sameLine)
handleLoopBody(body);
if (this.options.insert_new_line_before_while_in_do_statement
|| (!(body instanceof Block) && !(body instanceof EmptyStatement) && !sameLine)) {
Token whileToken = this.tm.firstTokenBefore(node.getExpression(), TokenNamewhile);
whileToken.breakBefore();
}
return true;
}
@Override
public boolean visit(LabeledStatement node) {
if (this.options.insert_new_line_after_label)
this.tm.firstTokenIn(node, TokenNameCOLON).breakAfter();
return true;
}
@Override
public boolean visit(ArrayInitializer node) {
int openBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE);
int closeBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE);
boolean isEmpty = openBraceIndex + 1 == closeBraceIndex;
if (isEmpty) {
adjustEmptyLineAfter(openBraceIndex, this.options.continuation_indentation_for_array_initializer);
closeBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE);
}
Token openBraceToken = this.tm.get(openBraceIndex);
Token closeBraceToken = this.tm.get(closeBraceIndex);
if (!(node.getParent() instanceof ArrayInitializer)) {
Token afterOpenBraceToken = this.tm.get(openBraceIndex + 1);
for (int i = 0; i < this.options.continuation_indentation_for_array_initializer; i++) {
afterOpenBraceToken.indent();
closeBraceToken.unindent();
}
}
if (!isEmpty || !this.options.keep_empty_array_initializer_on_one_line)
handleBracePosition(openBraceToken, closeBraceIndex, this.options.brace_position_for_array_initializer);
if (!isEmpty) {
if (this.options.insert_new_line_after_opening_brace_in_array_initializer)
openBraceToken.breakAfter();
if (this.options.insert_new_line_before_closing_brace_in_array_initializer)
closeBraceToken.breakBefore();
}
return true;
}
@Override
public boolean visit(NormalAnnotation node) {
handleAnnotation(node);
return true;
}
@Override
public boolean visit(SingleMemberAnnotation node) {
handleAnnotation(node);
return true;
}
@Override
public boolean visit(MarkerAnnotation node) {
handleAnnotation(node);
return true;
}
@Override
public boolean visit(VariableDeclarationStatement node) {
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(SingleVariableDeclaration node) {
this.declarationModifierVisited = false;
if (node.getParent() instanceof MethodDeclaration) {
// special case: annotations on parameters without modifiers should not be treated as type annotations
this.declarationModifierVisited = (node.getModifiers() == 0);
}
return true;
}
@Override
public boolean visit(VariableDeclarationExpression node) {
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(FieldDeclaration node) {
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(AnnotationTypeMemberDeclaration node) {
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(EnumConstantDeclaration node) {
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(Modifier node) {
this.declarationModifierVisited = true;
return true;
}
private void handleAnnotation(Annotation node) {
ASTNode parentNode = node.getParent();
boolean breakAfter = false;
boolean isTypeAnnotation = this.declarationModifierVisited;
if (isTypeAnnotation) {
breakAfter = this.options.insert_new_line_after_type_annotation;
} else if (parentNode instanceof PackageDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_package;
} else if (parentNode instanceof AbstractTypeDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_type;
} else if (parentNode instanceof EnumConstantDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_enum_constant;
} else if (parentNode instanceof FieldDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_field;
} else if (parentNode instanceof MethodDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_method;
} else if (parentNode instanceof AnnotationTypeMemberDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_method
&& ((AnnotationTypeMemberDeclaration) parentNode).getDefault() != node;
} else if (parentNode instanceof VariableDeclarationStatement
|| parentNode instanceof VariableDeclarationExpression) {
breakAfter = this.options.insert_new_line_after_annotation_on_local_variable;
} else if (parentNode instanceof SingleVariableDeclaration) {
breakAfter = this.options.insert_new_line_after_annotation_on_parameter;
if ((parentNode.getParent()) instanceof EnhancedForStatement)
breakAfter = this.options.insert_new_line_after_annotation_on_local_variable;
}
if (breakAfter)
this.tm.lastTokenIn(node, -1).breakAfter();
}
@Override
public boolean visit(WhileStatement node) {
if (!this.options.keep_simple_while_body_on_same_line)
handleLoopBody(node.getBody());
return true;
}
@Override
public boolean visit(ForStatement node) {
if (!this.options.keep_simple_for_body_on_same_line)
handleLoopBody(node.getBody());
return true;
}
@Override
public boolean visit(EnhancedForStatement node) {
if (!this.options.keep_simple_for_body_on_same_line)
handleLoopBody(node.getBody());
return true;
}
private void handleLoopBody(Statement body) {
if (body instanceof Block)
return;
if (body instanceof EmptyStatement && !this.options.put_empty_statement_on_new_line
&& !(body.getParent() instanceof IfStatement))
return;
breakLineBefore(body);
adjustEmptyLineAfter(this.tm.lastIndexIn(body, -1), -1);
indent(body);
}
@Override
public boolean visit(IfStatement node) {
Statement elseNode = node.getElseStatement();
Statement thenNode = node.getThenStatement();
if (elseNode != null) {
if (this.options.insert_new_line_before_else_in_if_statement || !(thenNode instanceof Block))
this.tm.firstTokenBefore(elseNode, TokenNameelse).breakBefore();
boolean keepElseOnSameLine = (this.options.keep_else_statement_on_same_line)
|| (this.options.compact_else_if && (elseNode instanceof IfStatement));
if (!keepElseOnSameLine)
handleLoopBody(elseNode);
}
boolean keepThenOnSameLine = this.options.keep_then_statement_on_same_line
|| (this.options.keep_simple_if_on_one_line && elseNode == null);
if (!keepThenOnSameLine)
handleLoopBody(thenNode);
return true;
}
@Override
public boolean visit(TryStatement node) {
if (node.getFinally() != null && this.options.insert_new_line_before_finally_in_try_statement) {
this.tm.firstTokenBefore(node.getFinally(), TokenNamefinally).breakBefore();
}
return true;
}
@Override
public boolean visit(CatchClause node) {
if (this.options.insert_new_line_before_catch_in_try_statement)
breakLineBefore(node);
return true;
}
//{ObjectTeams: more visits:
@Override
public boolean visit(RoleTypeDeclaration node) {
return visit((TypeDeclaration)node);
}
@Override
public boolean visit(CalloutMappingDeclaration node) {
if (node.hasParameterMapping()) {
for (Object pMap : node.getParameterMappings())
breakLineBefore((ASTNode) pMap);
String bracePosition = this.options.brace_position_for_method_declaration;
handleBracedCode(node, null, bracePosition, this.options.indent_statements_compare_to_block);
}
return true;
}
@Override
public boolean visit(CallinMappingDeclaration node) {
GuardPredicateDeclaration predicate = node.getGuardPredicate();
if (predicate != null) {
this.tm.firstTokenIn(node.getGuardPredicate(), predicate.isBase() ? TokenNamebase : TokenNamewhen).breakBefore();
indent(node.getGuardPredicate());
}
if (node.hasParameterMapping()) {
for (Object pMap : node.getParameterMappings())
breakLineBefore((ASTNode) pMap);
String bracePosition = this.options.brace_position_for_method_declaration;
handleBracedCode(node, null, bracePosition, this.options.indent_statements_compare_to_block);
}
return true;
}
@Override
public boolean visit(WithinStatement node) { // like while
handleLoopBody(node.getBody());
return true;
}
// SH}
@Override
public boolean visit(ModuleDeclaration node) {
// using settings for type declaration and fields for now, add new settings if necessary
breakLineBefore(node);
List<ModuleDirective> statements = node.moduleStatements();
handleBracedCode(node, node.getName(), this.options.brace_position_for_type_declaration,
this.options.indent_body_declarations_compare_to_type_header,
statements.isEmpty() ? 0 : this.options.blank_lines_before_first_class_body_declaration,
statements.isEmpty() ? 0 : this.options.blank_lines_after_last_class_body_declaration);
ModuleDirective previous = null;
for (ModuleDirective statement : statements) {
if (previous != null) {
boolean cameChunk = previous.getClass().equals(statement.getClass());
putBlankLinesBefore(statement,
cameChunk ? this.options.blank_lines_before_field : this.options.blank_lines_before_new_chunk);
}
previous = statement;
}
this.declarationModifierVisited = false;
return true;
}
@Override
public boolean visit(TextBlock node) {
int indentOption = this.options.text_block_indentation;
if (indentOption == Alignment.M_INDENT_PRESERVE)
return true;
Token block = this.tm.firstTokenIn(node, TokenNameTextBlock);
ArrayList<Token> lines = new ArrayList<>();
lines.add(new Token(block.originalStart, block.originalStart + 2, 0)); // first line; """
int incidentalWhitespace = Integer.MAX_VALUE;
int blankLines = -1; // will go to 0 on line break after first line
int i = block.originalStart + 3;
while (i <= block.originalEnd) {
int lineStart = i;
int firstNonBlank = -1;
int lastNonBlank = -1;
while (i <= block.originalEnd) {
char c = this.tm.charAt(i++);
if (c == '\r' || c == '\n') {
char c2 = this.tm.charAt(i);
if ((c2 == '\r' || c2 == '\n') && c2 != c)
i++;
break;
}
if (c != ' ' && c != '\t') {
if (firstNonBlank == -1)
firstNonBlank = i - 1;
lastNonBlank = i - 1;
}
}
if (firstNonBlank != -1) {
Token line = new Token(lineStart, lastNonBlank, 0);
line.putLineBreaksBefore(blankLines + 1);
blankLines = 0;
lines.add(line);
incidentalWhitespace = Math.min(incidentalWhitespace, firstNonBlank - lineStart);
} else {
blankLines++;
}
}
WrapPolicy wrapPolicy = new WrapPolicy(WrapMode.DISABLED, 0, -1, 0, 0, 1, false, false);
for (i = 1; i < lines.size(); i++) {
Token t = lines.get(i);
Token line = new Token(t, t.originalStart + incidentalWhitespace, t.originalEnd, TokenNameTextBlock);
line.setWrapPolicy(wrapPolicy);
lines.set(i, line);
}
block.setInternalStructure(lines);
return true;
}
private void breakLineBefore(ASTNode node) {
this.tm.firstTokenIn(node, -1).breakBefore();
}
private void putBlankLinesBefore(ASTNode node, int linesCount) {
int index = this.tm.firstIndexIn(node, -1);
while (index > 0 && this.tm.get(index - 1).tokenType == TokenNameCOMMENT_JAVADOC)
index--;
putBlankLinesBefore(this.tm.get(index), linesCount);
}
private void putBlankLinesBefore(Token token, int linesCount) {
if (linesCount >= 0) {
token.putLineBreaksBefore(linesCount + 1);
} else {
token.putLineBreaksBefore(~linesCount + 1);
token.setPreserveLineBreaksBefore(false);
}
}
private void putBlankLinesAfter(Token token, int linesCount) {
if (linesCount >= 0) {
token.putLineBreaksAfter(linesCount + 1);
} else {
token.putLineBreaksAfter(~linesCount + 1);
token.setPreserveLineBreaksAfter(false);
}
}
private void handleBracedCode(ASTNode node, ASTNode nodeBeforeOpenBrace, String bracePosition, boolean indentBody) {
handleBracedCode(node, nodeBeforeOpenBrace, bracePosition, indentBody, 0, 0);
}
private void handleBracedCode(ASTNode node, ASTNode nodeBeforeOpenBrace, String bracePosition, boolean indentBody,
int blankLinesAfterOpeningBrace, int blankLinesBeforeClosingBrace) {
int openBraceIndex = nodeBeforeOpenBrace == null
? this.tm.firstIndexIn(node, TokenNameLBRACE)
: this.tm.firstIndexAfter(nodeBeforeOpenBrace, TokenNameLBRACE);
int closeBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE);
Token openBraceToken = this.tm.get(openBraceIndex);
Token closeBraceToken = this.tm.get(closeBraceIndex);
handleBracePosition(openBraceToken, closeBraceIndex, bracePosition);
putBlankLinesAfter(openBraceToken, blankLinesAfterOpeningBrace);
putBlankLinesBefore(closeBraceToken, blankLinesBeforeClosingBrace);
if (indentBody) {
adjustEmptyLineAfter(openBraceIndex, 1);
this.tm.get(openBraceIndex + 1).indent();
closeBraceToken.unindent();
}
}
private void handleBracePosition(Token openBraceToken, int closeBraceIndex, String bracePosition) {
if (bracePosition.equals(DefaultCodeFormatterConstants.NEXT_LINE)) {
openBraceToken.breakBefore();
} else if (bracePosition.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED)) {
openBraceToken.breakBefore();
openBraceToken.indent();
if (closeBraceIndex + 1 < this.tm.size())
this.tm.get(closeBraceIndex + 1).unindent();
} else if (bracePosition.equals(DefaultCodeFormatterConstants.NEXT_LINE_ON_WRAP)) {
openBraceToken.setNextLineOnWrap();
}
}
private void adjustEmptyLineAfter(int tokenIndex, int indentationAdjustment) {
if (tokenIndex + 1 >= this.tm.size())
return;
Token token = this.tm.get(tokenIndex);
Token next = this.tm.get(tokenIndex + 1);
if (this.tm.countLineBreaksBetween(token, next) < 2 || !this.options.indent_empty_lines)
return;
next.setEmptyLineIndentAdjustment(indentationAdjustment * this.options.indentation_size);
}
private void indent(ASTNode node) {
int startIndex = this.tm.firstIndexIn(node, -1);
while (startIndex > 0 && this.tm.get(startIndex - 1).isComment())
startIndex--;
this.tm.get(startIndex).indent();
int lastIndex = this.tm.lastIndexIn(node, -1);
if (lastIndex + 1 < this.tm.size())
this.tm.get(lastIndex + 1).unindent();
}
public void finishUp(List<IRegion> regions) {
// the visits only noted where indents increase and decrease,
// now prepare actual indent values, preserving indents outside formatting regions
int currentIndent = this.options.initial_indentation_level * this.options.indentation_size;
Token previous = null;
for (Token token : this.tm) {
if (isFixedLineStart(token, previous, regions)) {
currentIndent = this.tm.findSourcePositionInLine(token.originalStart);
} else {
currentIndent = Math.max(currentIndent + token.getIndent() * this.options.indentation_size, 0);
}
token.setIndent(currentIndent);
previous = token;
}
}
private boolean isFixedLineStart(Token token, Token previous, List<IRegion> regions) {
if (previous == null && this.options.initial_indentation_level >0)
return false; // must be handling ast rewrite
if (previous != null && this.tm.countLineBreaksBetween(previous, token) == 0)
return false;
if (token.getLineBreaksBefore() == 0 && (previous == null || previous.getLineBreaksAfter() == 0))
return false;
int lineStart = token.originalStart;
char c;
while (lineStart > 0 && (c = this.tm.charAt(lineStart - 1)) != '\r' && c != '\n')
lineStart--;
for (IRegion r : regions) {
if (token.originalStart >= r.getOffset() && lineStart <= r.getOffset() + r.getLength())
return false;
}
return true;
}
}