com.bsiag.eclipse.jdt.java.formatter.LineBreaksPreparator.java Source code

Java tutorial

Introduction

Here is the source code for com.bsiag.eclipse.jdt.java.formatter.LineBreaksPreparator.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2015 Mateusz Matela and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     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
 *******************************************************************************/
package com.bsiag.eclipse.jdt.java.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.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.TokenNameCOMMENT_JAVADOC;
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.TokenNamepackage;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewhile;

import java.util.List;

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.CatchClause;
import org.eclipse.jdt.core.dom.CompilationUnit;
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.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.NormalAnnotation;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
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.SwitchStatement;
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.formatter.DefaultCodeFormatterConstants;

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()) {
            int index = this.tm.firstIndexIn(imports.get(0), -1);
            if (index > 0)
                this.tm.get(index).putLineBreaksBefore(this.options.blank_lines_before_imports + 1);
        }

        List<AnnotationTypeDeclaration> types = node.types();
        if (!types.isEmpty()) {
            if (!imports.isEmpty())
                this.tm.firstTokenIn(types.get(0), -1)
                        .putLineBreaksBefore(this.options.blank_lines_after_imports + 1);
            for (int i = 1; i < types.size(); i++) {
                this.tm.firstTokenIn(types.get(i), -1)
                        .putLineBreaksBefore(this.options.blank_lines_between_type_declarations + 1);
            }
        }
        return true;
    }

    @Override
    public boolean visit(PackageDeclaration node) {
        int blanks = this.options.blank_lines_before_package;
        if (blanks > 0) {
            List<Annotation> annotations = node.annotations();
            int firstTokenIndex = annotations.isEmpty() ? this.tm.firstIndexBefore(node.getName(), TokenNamepackage)
                    : this.tm.firstIndexIn(annotations.get(0), -1);
            this.tm.get(firstTokenIndex).putLineBreaksBefore(blanks + 1);
        }
        this.tm.lastTokenIn(node, TokenNameSEMICOLON)
                .putLineBreaksAfter(this.options.blank_lines_after_package + 1);
        this.declarationModifierVisited = false;
        return true;
    }

    @Override
    public boolean visit(ImportDeclaration node) {
        breakLineBefore(node);
        return true;
    }

    public boolean visit(TypeDeclaration node) {
        handleBodyDeclarations(node.bodyDeclarations());

        if (node.getName().getStartPosition() == -1)
            return true; // this is a fake type created by parsing in class body mode

        breakLineBefore(node);

        handleBracedCode(node, node.getName(), this.options.brace_position_for_type_declaration,
                this.options.indent_body_declarations_compare_to_type_header,
                this.options.insert_new_line_in_empty_type_declaration);

        this.declarationModifierVisited = false;
        return true;
    }

    private void handleBodyDeclarations(List<BodyDeclaration> bodyDeclarations) {
        BodyDeclaration previous = null;
        for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
            if (previous == null) {
                this.tm.firstTokenIn(bodyDeclaration, -1)
                        .putLineBreaksBefore(this.options.blank_lines_before_first_class_body_declaration + 1);
            } else {
                int blankLines = 0;
                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
                        || bodyDeclaration instanceof AnnotationTypeMemberDeclaration) {
                    blankLines = this.options.blank_lines_before_method;
                }

                if (!sameChunk(previous, bodyDeclaration))
                    blankLines = Math.max(blankLines, this.options.blank_lines_before_new_chunk);

                this.tm.firstTokenIn(bodyDeclaration, -1).putLineBreaksBefore(blankLines + 1);
            }
            previous = bodyDeclaration;
        }
    }

    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;
        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,
                this.options.insert_new_line_in_empty_enum_declaration);
        handleBodyDeclarations(node.bodyDeclarations());

        List<EnumConstantDeclaration> enumConstants = node.enumConstants();
        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,
                this.options.insert_new_line_in_empty_annotation_declaration);

        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,
                    this.options.insert_new_line_in_empty_enum_constant);
        } else {
            handleBracedCode(node, null, this.options.brace_position_for_anonymous_type_declaration,
                    this.options.indent_body_declarations_compare_to_type_header,
                    this.options.insert_new_line_in_empty_anonymous_type_declaration);
        }
        handleBodyDeclarations(node.bodyDeclarations());
        return true;
    }

    @Override
    public boolean visit(MethodDeclaration node) {
        this.declarationModifierVisited = false;

        if (node.getBody() == null)
            return true;

        if (node.isConstructor()) {
            handleBracedCode(node.getBody(), null, this.options.brace_position_for_constructor_declaration,
                    this.options.indent_statements_compare_to_body,
                    this.options.insert_new_line_in_empty_method_body);
        } else {
            handleBracedCode(node.getBody(), null, this.options.brace_position_for_method_declaration,
                    this.options.indent_statements_compare_to_body,
                    this.options.insert_new_line_in_empty_method_body);
            Token openBrace = this.tm.firstTokenIn(node.getBody(), TokenNameLBRACE);
            if (openBrace.getLineBreaksAfter() > 0) // if not, these are empty braces
                openBrace.putLineBreaksAfter(this.options.blank_lines_at_beginning_of_method_body + 1);
        }
        return true;
    }

    @Override
    public boolean visit(Block node) {
        if (this.options.keep_guardian_clause_on_one_line && this.tm.isGuardClause(node))
            return true;

        List<Statement> statements = node.statements();
        for (Statement statement : statements) {
            if (this.options.put_empty_statement_on_new_line || !(statement instanceof EmptyStatement))
                breakLineBefore(statement);
        }
        if (node.getParent().getLength() == 0)
            return true; // this is a fake block created by parsing in statements mode

        ASTNode parent = node.getParent();
        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.insert_new_line_in_empty_block);

        return true;
    }

    @Override
    public boolean visit(SwitchStatement node) {
        handleBracedCode(node, node.getExpression(), this.options.brace_position_for_switch,
                this.options.indent_switchstatements_compare_to_switch, true);

        List<Statement> statements = node.statements();
        if (this.options.indent_switchstatements_compare_to_cases) {
            int nonBreakStatementEnd = -1;
            for (Statement statement : statements) {
                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 Block)) {
                    indent(statement);
                }
                nonBreakStatementEnd = (statement instanceof BreakStatement || statement instanceof ReturnStatement)
                        ? -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(node, TokenNameRBRACE).unindent();
            }
        }
        if (this.options.indent_breaks_compare_to_cases) {
            for (Statement statement : statements) {
                if (statement instanceof BreakStatement)
                    indent(statement);
            }
        }

        for (Statement statement : statements) {
            if (statement instanceof Block)
                continue; // will add break in visit(Block) if necessary
            if (this.options.put_empty_statement_on_new_line || !(statement instanceof EmptyStatement))
                breakLineBefore(statement);
        }

        return true;
    }

    @Override
    public boolean visit(DoStatement node) {
        Statement body = node.getBody();
        handleLoopBody(body);
        if (this.options.insert_new_line_before_while_in_do_statement
                || (!(body instanceof Block) && !(body instanceof EmptyStatement))) {
            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);
        boolean isEmpty = handleEmptyLinesIndentation(openBraceIndex);

        int 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(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 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) {
        handleLoopBody(node.getBody());
        return true;
    }

    @Override
    public boolean visit(ForStatement node) {
        handleLoopBody(node.getBody());
        return true;
    }

    @Override
    public boolean visit(EnhancedForStatement node) {
        handleLoopBody(node.getBody());
        return true;
    }

    private void handleLoopBody(Statement body) {
        if (!(body instanceof Block)
                && !(body instanceof EmptyStatement && !this.options.put_empty_statement_on_new_line)) {
            breakLineBefore(body);
            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 = (elseNode instanceof Block)
                    || (this.options.keep_else_statement_on_same_line)
                    || (this.options.compact_else_if && (elseNode instanceof IfStatement));
            if (!keepElseOnSameLine) {
                breakLineBefore(elseNode);
                indent(elseNode);
            }
        }

        boolean keepThenOnSameLine = this.options.keep_then_statement_on_same_line
                || (this.options.keep_simple_if_on_one_line && elseNode == null);
        if (!keepThenOnSameLine && !(thenNode instanceof Block)) {
            breakLineBefore(thenNode);
            indent(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;
    }

    private void breakLineBefore(ASTNode node) {
        this.tm.firstTokenIn(node, -1).breakBefore();
    }

    private void handleBracedCode(ASTNode node, ASTNode nodeBeforeOpenBrace, String bracePosition,
            boolean indentBody, boolean newLineInEmpty) {
        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);

        boolean isEmpty = true;
        for (int i = openBraceIndex + 1; i < closeBraceIndex; i++) {
            if (!this.tm.get(i).isComment()) {
                isEmpty = false;
                break;
            }
        }

        handleEmptyLinesIndentation(openBraceIndex);

        if (!isEmpty || newLineInEmpty) {
            openBraceToken.breakAfter();
            closeBraceToken.breakBefore();
        }
        if (indentBody) {
            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 boolean handleEmptyLinesIndentation(int openBraceIndex) {
        Token open = this.tm.get(openBraceIndex);
        Token next = this.tm.get(openBraceIndex + 1);
        boolean isEmpty = next.tokenType == TokenNameRBRACE;
        if (!isEmpty || this.tm.countLineBreaksBetween(open, next) < 2 || !this.options.indent_empty_lines)
            return isEmpty;

        // find a line break and make a token out of it
        for (int i = open.originalEnd + 1; i < next.originalStart; i++) {
            char c = this.tm.charAt(i);
            char c2 = this.tm.charAt(i + 1);
            int lineBreakStart = (c == '\r' || c == '\n') ? i : -1;
            int lineBreakEnd = ((c2 == '\r' || c2 == '\n') && c2 != c) ? i + 1 : lineBreakStart;
            if (lineBreakStart >= 0) {
                Token emptyLineToken = new Token(lineBreakStart, lineBreakEnd, Token.TokenNameEMPTY_LINE);
                emptyLineToken.breakBefore();
                emptyLineToken.breakAfter();
                emptyLineToken.setToEscape(true); // force text builder to use toString()
                this.tm.insert(openBraceIndex + 1, emptyLineToken);
                return true;
            }
        }
        return true;
    }

    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() {
        // the visits only noted where indents increase and decrease,
        // now prepare actual indent values
        int currentIndent = this.options.initial_indentation_level;
        for (Token token : this.tm) {
            currentIndent += token.getIndent();
            token.setIndent(currentIndent * this.options.indentation_size);
        }
    }
}