com.google.googlejavaformat.java.JavaInputAstVisitor.java Source code

Java tutorial

Introduction

Here is the source code for com.google.googlejavaformat.java.JavaInputAstVisitor.java

Source

/*
 * Copyright 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.googlejavaformat.java;

import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.googlejavaformat.CloseOp;
import com.google.googlejavaformat.Doc;
import com.google.googlejavaformat.Doc.FillMode;
import com.google.googlejavaformat.Indent;
import com.google.googlejavaformat.Input;
import com.google.googlejavaformat.Op;
import com.google.googlejavaformat.OpenOp;
import com.google.googlejavaformat.OpsBuilder;
import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
import com.google.googlejavaformat.Output.BreakTag;

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.AnnotatableType;
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.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
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.ConstructorInvocation;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.CreationReference;
import org.eclipse.jdt.core.dom.Dimension;
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.Expression;
import org.eclipse.jdt.core.dom.ExpressionMethodReference;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.IntersectionType;
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.MemberValuePair;
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.NameQualifiedType;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
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.StringLiteral;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SuperMethodReference;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.TypeMethodReference;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.core.dom.WildcardType;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * An extension of {@link OpsBuilder}, implementing a visit pattern for Eclipse AST nodes to build a
 * sequence of {@link Op}s.
 */
@SuppressWarnings({ "unchecked", "rawtypes" }) // jdt uses rawtypes extensively
public final class JavaInputAstVisitor extends ASTVisitor {
    /** Direction for Annotations (usually VERTICAL). */
    enum Direction {
        VERTICAL, HORIZONTAL;

        boolean isVertical() {
            return this == VERTICAL;
        }
    }

    /** Whether to break or not. */
    enum BreakOrNot {
        YES, NO;

        boolean isYes() {
            return this == YES;
        }
    }

    /** Whether to collapse empty blocks. */
    enum CollapseEmptyOrNot {
        YES, NO;

        static CollapseEmptyOrNot valueOf(boolean b) {
            return b ? YES : NO;
        }

        boolean isYes() {
            return this == YES;
        }
    }

    /** Whether to allow leading blank lines in blocks. */
    enum AllowLeadingBlankLine {
        YES, NO;

        static AllowLeadingBlankLine valueOf(boolean b) {
            return b ? YES : NO;
        }
    }

    /** Whether to allow trailing blank lines in blocks. */
    enum AllowTrailingBlankLine {
        YES, NO;

        static AllowTrailingBlankLine valueOf(boolean b) {
            return b ? YES : NO;
        }
    }

    /** Whether to include braces. */
    enum BracesOrNot {
        YES, NO;

        boolean isYes() {
            return this == YES;
        }
    }

    /** Whether or not to include dimensions. */
    enum DimensionsOrNot {
        YES, NO;

        boolean isYes() {
            return this == YES;
        }
    }

    /** Whether or not the declaration is Varargs. */
    enum VarArgsOrNot {
        YES, NO;

        static VarArgsOrNot valueOf(boolean b) {
            return b ? YES : NO;
        }

        boolean isYes() {
            return this == YES;
        }
    }

    /** Whether the formal parameter declaration is a receiver. */
    enum ReceiverParameter {
        YES, NO;

        boolean isYes() {
            return this == YES;
        }
    }

    /** Whether these declarations are the first in the block. */
    enum FirstDeclarationsOrNot {
        YES, NO;

        boolean isYes() {
            return this == YES;
        }
    }

    /** Position in a list of declarations. */
    enum DeclarationPosition {
        FIRST, INTERIOR, LAST;

        static EnumSet<DeclarationPosition> getPositionInParent(ASTNode node) {
            EnumSet<DeclarationPosition> position = EnumSet.noneOf(DeclarationPosition.class);
            StructuralPropertyDescriptor locationInParent = node.getLocationInParent();
            if (locationInParent instanceof ChildListPropertyDescriptor) {
                List<ASTNode> propertyList = (List<ASTNode>) node.getParent()
                        .getStructuralProperty(locationInParent);
                int idx = propertyList.indexOf(node);
                if (idx == 0) {
                    position.add(DeclarationPosition.FIRST);
                }
                if (idx == propertyList.size() - 1) {
                    position.add(DeclarationPosition.LAST);
                }
                if (position.isEmpty()) {
                    position.add(DeclarationPosition.INTERIOR);
                }
            }
            return position;
        }
    }

    private final OpsBuilder builder;

    private static final Indent.Const ZERO = Indent.Const.ZERO;
    private final int indentMultiplier;
    private final Indent.Const minusTwo;
    private final Indent.Const minusFour;
    private final Indent.Const plusTwo;
    private final Indent.Const plusFour;
    private final Indent.Const plusEight;

    private static final ImmutableList<Op> breakList(Optional<BreakTag> breakTag) {
        return ImmutableList.<Op>of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag));
    }

    private static final ImmutableList<Op> breakFillList(Optional<BreakTag> breakTag) {
        return ImmutableList.of(OpenOp.make(ZERO, 0), Doc.Break.make(Doc.FillMode.INDEPENDENT, " ", ZERO, breakTag),
                CloseOp.make());
    }

    private static final ImmutableList<Op> forceBreakList(Optional<BreakTag> breakTag) {
        return ImmutableList.<Op>of(Doc.Break.make(FillMode.FORCED, "", Indent.Const.ZERO, breakTag));
    }

    private static final ImmutableList<Op> EMPTY_LIST = ImmutableList.of();
    private static final Map<String, Integer> PRECEDENCE = new HashMap<>();
    private static final int MAX_LINES_FOR_ARGUMENTS = 1;
    private static final int MAX_LINES_FOR_ARRAY_INITIALIZERS = 1;
    private static final int MAX_LINES_FOR_ANNOTATION_ELEMENT_VALUE_PAIRS = 1;
    private static final int MAX_LINES_FOR_CHAINED_ACCESSES = 1;

    /**
     * Allow multi-line filling (of array initializers, argument lists, and boolean
     * expressions) for items with length less than or equal to this threshold.
     */
    private static final int MAX_ITEM_LENGTH_FOR_FILLING = 10;

    /**
     * A maxLinesFilled value for OpenOp.make that indicates one-per-line mode
     * should never be used.
     */
    private static final int ALWAYS_FILL = 0;

    static {
        PRECEDENCE.put("*", 10);
        PRECEDENCE.put("/", 10);
        PRECEDENCE.put("%", 10);
        PRECEDENCE.put("+", 9);
        PRECEDENCE.put("-", 9);
        PRECEDENCE.put("<<", 8);
        PRECEDENCE.put(">>", 8);
        PRECEDENCE.put(">>>", 8);
        PRECEDENCE.put("<", 7);
        PRECEDENCE.put(">", 7);
        PRECEDENCE.put("<=", 7);
        PRECEDENCE.put(">=", 7);
        PRECEDENCE.put("==", 6);
        PRECEDENCE.put("!=", 6);
        PRECEDENCE.put("&", 5);
        PRECEDENCE.put("^", 4);
        PRECEDENCE.put("|", 3);
        PRECEDENCE.put("&&", 2);
        PRECEDENCE.put("||", 1);
    }

    private static final int MAX_FILLED_INFIX_LINES = 1;
    private static final int MAX_LINES_FOR_FORMAL_LIST = 1;

    private static final int MAX_EMPTY_DECLS = 10;

    /**
     * The {@code Visitor} constructor.
     * @param builder the {@link OpsBuilder}
     */
    public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) {
        this.builder = builder;
        this.indentMultiplier = indentMultiplier;
        minusTwo = Indent.Const.make(-2, indentMultiplier);
        minusFour = Indent.Const.make(-4, indentMultiplier);
        plusTwo = Indent.Const.make(+2, indentMultiplier);
        plusFour = Indent.Const.make(+4, indentMultiplier);
        plusEight = Indent.Const.make(+8, indentMultiplier);
    }

    /** A record of whether we have visited into an expression. */
    private final Deque<Boolean> inExpression = new ArrayDeque<>(Arrays.asList(false));

    private boolean inExpression() {
        return inExpression.peekLast();
    }

    /** Pre-visits {@link ASTNode}s. */
    @Override
    public void preVisit(ASTNode node) {
        inExpression.addLast(node instanceof Expression || inExpression.peekLast());
    }

    /** Post-visits {@link ASTNode}s. */
    @Override
    public void postVisit(ASTNode node) {
        inExpression.removeLast();
    }

    /** Visitor method for a {@link CompilationUnit}. */
    @Override
    public boolean visit(CompilationUnit node) {
        boolean first = true;
        if (node.getPackage() != null) {
            markForPartialFormat();
            visit(node.getPackage());
            builder.forcedBreak();
            first = false;
        }
        if (!node.imports().isEmpty()) {
            if (!first) {
                builder.blankLineWanted(BlankLineWanted.YES);
            }
            for (ImportDeclaration importDeclaration : (List<ImportDeclaration>) node.imports()) {
                markForPartialFormat();
                builder.blankLineWanted(BlankLineWanted.PRESERVE);
                visit(importDeclaration);
                builder.forcedBreak();
            }
            first = false;
        }
        for (AbstractTypeDeclaration type : (List<AbstractTypeDeclaration>) node.types()) {
            if (!first) {
                builder.blankLineWanted(BlankLineWanted.YES);
            }
            dropEmptyDeclarations();
            markForPartialFormat();
            type.accept(this);
            builder.forcedBreak();
            first = false;
        }
        dropEmptyDeclarations();
        // set a partial format marker at EOF to make sure we can format the entire file
        markForPartialFormat();
        return false;
    }

    /** Skips over extra semi-colon at the top-level, or in a class member declaration lists. */
    private void dropEmptyDeclarations() {
        for (int times = 0; times < MAX_EMPTY_DECLS; times++) {
            builder.guessToken(";");
        }
    }

    /** Visitor method for {@link AnnotationTypeDeclaration}s. */
    @Override
    public boolean visit(AnnotationTypeDeclaration node) {
        sync(node);
        builder.open(ZERO);
        visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent());
        builder.open(ZERO);
        token("@");
        token("interface");
        builder.breakOp(" ");
        visit(node.getName());
        builder.close();
        builder.close();
        if (node.bodyDeclarations() == null) {
            builder.open(plusFour);
            token(";");
            builder.close();
        } else {
            addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
        }
        builder.guessToken(";");
        return false;
    }

    /** Visitor method for {@link AnnotationTypeMemberDeclaration}s. */
    @Override
    public boolean visit(AnnotationTypeMemberDeclaration node) {
        sync(node);
        declareOne(node, Direction.VERTICAL, node.modifiers(), node.getType(), VarArgsOrNot.NO,
                ImmutableList.<Annotation>of(), node.getName(), "()", ImmutableList.<Dimension>of(), "default",
                Optional.fromNullable(node.getDefault()), Optional.of(";"), ReceiverParameter.NO);
        return false;
    }

    /** Visitor method for {@link AnonymousClassDeclaration}s. */
    @Override
    public boolean visit(AnonymousClassDeclaration node) {
        sync(node);
        addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
        return false;
    }

    // TODO(jdd): Get rid of instanceof?

    /** Visitor method for {@link ArrayAccess}es. */
    @Override
    public boolean visit(ArrayAccess node) {
        sync(node);
        builder.open(plusFour);
        // Collapse chains of ArrayAccess nodes.
        ArrayDeque<Expression> stack = new ArrayDeque<>();
        Expression array;
        while (true) {
            stack.addLast(node.getIndex());
            array = node.getArray();
            if (!(array instanceof ArrayAccess)) {
                break;
            }
            node = (ArrayAccess) array;
        }
        array.accept(this);
        do {
            token("[");
            builder.breakToFill();
            stack.removeLast().accept(this);
            token("]");
        } while (!stack.isEmpty());
        builder.close();
        return false;
    }

    /** Visitor method for {@link ArrayCreation}s. */
    @Override
    public boolean visit(ArrayCreation node) {
        sync(node);
        builder.open(plusFour);
        token("new");
        builder.space();
        visitArrayType(node.getType(), DimensionsOrNot.NO);
        int dimensions = node.getType().getDimensions();
        builder.open(ZERO);
        for (int i = 0; i < dimensions; i++) {
            builder.breakOp();
            token("[");
            if (i < node.dimensions().size()) {
                ((Expression) node.dimensions().get(i)).accept(this);
            }
            token("]");
        }
        builder.close();
        builder.close();
        if (node.getInitializer() != null) {
            builder.space();
            visit(node.getInitializer());
        }
        return false;
    }

    /** Visitor method for {@link ArrayInitializer}s. */
    @Override
    public boolean visit(ArrayInitializer node) {
        sync(node);
        if (node.expressions().isEmpty()) {
            tokenBreakTrailingComment("{", plusTwo);
            builder.blankLineWanted(BlankLineWanted.NO);
            token("}", plusTwo);
        } else {
            // Special-case the formatting of array initializers inside annotations
            // to more eagerly use a one-per-line layout.
            boolean inMemberValuePair = node.getParent().getNodeType() == ASTNode.MEMBER_VALUE_PAIR
                    || node.getParent().getNodeType() == ASTNode.SINGLE_MEMBER_ANNOTATION;
            boolean shortItems = hasOnlyShortItems(node.expressions());
            boolean allowFilledElementsOnOwnLine = shortItems || !inMemberValuePair;

            builder.open(plusTwo);
            tokenBreakTrailingComment("{", plusTwo);
            builder.blankLineWanted(BlankLineWanted.NO);
            boolean hasTrailingComma = hasTrailingToken(builder.getInput(), node.expressions(), ",");
            builder.breakOp(hasTrailingComma ? FillMode.FORCED : FillMode.UNIFIED, "", ZERO);
            if (allowFilledElementsOnOwnLine) {
                builder.open(ZERO, shortItems ? ALWAYS_FILL : MAX_LINES_FOR_ARRAY_INITIALIZERS);
            }
            boolean first = true;
            for (Expression expression : (List<Expression>) node.expressions()) {
                if (!first) {
                    token(",");
                    if (allowFilledElementsOnOwnLine) {
                        builder.breakToFill(" ");
                    } else {
                        builder.breakOp(" ");
                    }
                }
                expression.accept(this);
                first = false;
            }
            builder.guessToken(",");
            if (allowFilledElementsOnOwnLine) {
                builder.close();
            }
            builder.breakOp(minusTwo);
            builder.blankLineWanted(BlankLineWanted.NO);
            builder.close();
            token("}", plusTwo);
        }
        return false;
    }

    /**
     * Returns {@code defaultThreshold} if bin-packing can be used for the given
     * expression list, and {code NEVER_FILL} otherwise.
     */
    private int maxLinesFilledForItems(List<Expression> expressions, int defaultThreshold) {
        return hasOnlyShortItems(expressions) ? ALWAYS_FILL : defaultThreshold;
    }

    private boolean hasOnlyShortItems(List<Expression> expressions) {
        for (Expression expression : expressions) {
            if (builder.actualSize(expression.getStartPosition(),
                    expression.getLength()) >= MAX_ITEM_LENGTH_FOR_FILLING) {
                return false;
            }
        }
        return true;
    }

    /** Visitor method for {@link ArrayType}s. */
    @Override
    public boolean visit(ArrayType node) {
        sync(node);
        visitArrayType(node, DimensionsOrNot.YES);
        return false;
    }

    /** Visitor method for {@link AssertStatement}s. */
    @Override
    public boolean visit(AssertStatement node) {
        sync(node);
        builder.open(ZERO);
        token("assert");
        builder.space();
        builder.open(node.getMessage() == null ? ZERO : plusFour);
        node.getExpression().accept(this);
        if (node.getMessage() != null) {
            builder.breakOp(" ");
            token(":");
            builder.space();
            node.getMessage().accept(this);
        }
        builder.close();
        builder.close();
        token(";");
        return false;
    }

    /** Visitor method for {@link Assignment}s. */
    @Override
    public boolean visit(Assignment node) {
        sync(node);
        builder.open(plusFour);
        node.getLeftHandSide().accept(this);
        builder.space();
        builder.op(node.getOperator().toString());
        builder.breakOp(" ");
        node.getRightHandSide().accept(this);
        builder.close();
        return false;
    }

    /** Visitor method for {@link Block}s. */
    @Override
    public boolean visit(Block node) {
        visitBlock(node, CollapseEmptyOrNot.YES, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO);
        return false;
    }

    /** Visitor method for {@link BooleanLiteral}s. */
    @Override
    public boolean visit(BooleanLiteral node) {
        sync(node);
        token(node.toString());
        return false;
    }

    /** Visitor method for {@link BreakStatement}s. */
    @Override
    public boolean visit(BreakStatement node) {
        sync(node);
        builder.open(plusFour);
        token("break");
        if (node.getLabel() != null) {
            builder.breakOp(" ");
            visit(node.getLabel());
        }
        builder.close();
        token(";");
        return false;
    }

    /** Visitor method for {@link CastExpression}s. */
    @Override
    public boolean visit(CastExpression node) {
        sync(node);
        builder.open(plusFour);
        token("(");
        node.getType().accept(this);
        token(")");
        builder.breakOp(" ");
        node.getExpression().accept(this);
        builder.close();
        return false;
    }

    /** Visitor method for {@link CharacterLiteral}s. */
    @Override
    public boolean visit(CharacterLiteral node) {
        sync(node);
        token(node.getEscapedValue());
        return false;
    }

    /** Visitor method for {@link ClassInstanceCreation}s. */
    @Override
    public boolean visit(ClassInstanceCreation node) {
        sync(node);
        builder.open(ZERO);
        if (node.getExpression() != null) {
            node.getExpression().accept(this);
            builder.breakOp();
            token(".");
        }
        token("new");
        builder.space();
        addTypeArguments(node.typeArguments(), plusFour);
        node.getType().accept(this);
        addArguments(node.arguments(), plusFour);
        builder.close();
        if (node.getAnonymousClassDeclaration() != null) {
            visit(node.getAnonymousClassDeclaration());
        }
        return false;
    }

    /** Visitor method for {@link ConditionalExpression}s. */
    @Override
    public boolean visit(ConditionalExpression node) {
        sync(node);
        builder.open(plusFour);
        node.getExpression().accept(this);
        builder.breakOp(" ");
        token("?");
        builder.space();
        node.getThenExpression().accept(this);
        builder.breakOp(" ");
        token(":");
        builder.space();
        node.getElseExpression().accept(this);
        builder.close();
        return false;
    }

    /** Visitor method for {@link ConstructorInvocation}s. */
    @Override
    public boolean visit(ConstructorInvocation node) {
        sync(node);
        addTypeArguments(node.typeArguments(), plusFour);
        token("this");
        addArguments(node.arguments(), plusFour);
        token(";");
        return false;
    }

    /** Visitor method for {@link ContinueStatement}s. */
    @Override
    public boolean visit(ContinueStatement node) {
        sync(node);
        builder.open(plusFour);
        token("continue");
        if (node.getLabel() != null) {
            builder.breakOp(" ");
            visit(node.getLabel());
        }
        token(";");
        builder.close();
        return false;
    }

    /** Visitor method for {@link CreationReference}s. */
    @Override
    public boolean visit(CreationReference node) {
        sync(node);
        builder.open(plusFour);
        node.getType().accept(this);
        builder.breakOp();
        builder.op("::");
        addTypeArguments(node.typeArguments(), plusFour);
        token("new");
        builder.close();
        return false;
    }

    /** Visitor method for {@link Dimension}s. */
    @Override
    public boolean visit(Dimension node) {
        sync(node);
        if (!node.annotations().isEmpty()) {
            builder.open(ZERO);
            visitAnnotations(node.annotations(), BreakOrNot.NO, BreakOrNot.NO);
            builder.breakToFill(" ");
            builder.close();
        }
        token("[");
        token("]");
        return false;
    }

    /** Visitor method for {@link DoStatement}s. */
    @Override
    public boolean visit(DoStatement node) {
        sync(node);
        token("do");
        visitStatement(node.getBody(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES,
                AllowTrailingBlankLine.YES);
        if (node.getBody().getNodeType() == ASTNode.BLOCK) {
            builder.space();
        } else {
            builder.breakOp(" ");
        }
        token("while");
        builder.space();
        token("(");
        node.getExpression().accept(this);
        token(")");
        token(";");
        return false;
    }

    /** Visitor method for {@link EmptyStatement}s. */
    @Override
    public boolean visit(EmptyStatement node) {
        sync(node);
        builder.guessToken(";");
        return false;
    }

    /** Visitor method for {@link EnhancedForStatement}s. */
    @Override
    public boolean visit(EnhancedForStatement node) {
        sync(node);
        builder.open(ZERO);
        token("for");
        builder.space();
        token("(");
        builder.open(ZERO);
        visitToDeclare(Direction.HORIZONTAL, node.getParameter(), Optional.of(node.getExpression()), ":");
        builder.close();
        token(")");
        builder.close();
        visitStatement(node.getBody(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES,
                AllowTrailingBlankLine.NO);
        return false;
    }

    /** Visitor method for {@link EnumConstantDeclaration}s. */
    @Override
    public boolean visit(EnumConstantDeclaration node) {
        sync(node);
        List<Op> breaks = visitModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent());
        if (!breaks.isEmpty()) {
            builder.open(ZERO);
            builder.addAll(breaks);
            builder.close();
        }
        visit(node.getName());
        if (node.arguments().isEmpty()) {
            builder.guessToken("(");
            builder.guessToken(")");
        } else {
            addArguments(node.arguments(), plusFour);
        }
        if (node.getAnonymousClassDeclaration() != null) {
            visit(node.getAnonymousClassDeclaration());
        }
        return false;
    }

    /** Visitor method for {@link EnumDeclaration}s. */
    @Override
    public boolean visit(EnumDeclaration node) {
        sync(node);
        builder.open(ZERO);
        visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent());
        builder.open(plusFour);
        token("enum");
        builder.breakOp(" ");
        visit(node.getName());
        builder.close();
        builder.close();
        if (!node.superInterfaceTypes().isEmpty()) {
            builder.open(plusFour);
            builder.breakOp(" ");
            builder.open(plusFour);
            token("implements");
            builder.breakOp(" ");
            builder.open(ZERO);
            boolean first = true;
            for (Type superInterfaceType : (List<Type>) node.superInterfaceTypes()) {
                if (!first) {
                    token(",");
                    builder.breakToFill(" ");
                }
                superInterfaceType.accept(this);
                first = false;
            }
            builder.close();
            builder.close();
            builder.close();
        }
        builder.space();
        tokenBreakTrailingComment("{", plusTwo);
        if (node.enumConstants().isEmpty() && node.bodyDeclarations().isEmpty()) {
            builder.open(ZERO);
            builder.blankLineWanted(BlankLineWanted.NO);
            token("}");
            builder.close();
        } else {
            builder.open(plusTwo);
            builder.blankLineWanted(BlankLineWanted.NO);
            builder.forcedBreak();
            builder.open(ZERO);
            boolean first = true;
            for (EnumConstantDeclaration enumConstant : (List<EnumConstantDeclaration>) node.enumConstants()) {
                if (!first) {
                    token(",");
                    builder.forcedBreak();
                }
                visit(enumConstant);
                first = false;
            }
            if (builder.peekToken().or("").equals(",")) {
                token(",");
                builder.forcedBreak(); // The ";" goes on its own line.
            }
            builder.close();
            builder.close();
            builder.open(ZERO);
            if (node.bodyDeclarations().isEmpty()) {
                builder.guessToken(";");
            } else {
                token(";");
                builder.forcedBreak();
                addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.NO, FirstDeclarationsOrNot.NO);
            }
            builder.forcedBreak();
            builder.blankLineWanted(BlankLineWanted.NO);
            token("}", plusTwo);
            builder.close();
        }
        builder.guessToken(";");
        return false;
    }

    /** Visitor method for {@link ExpressionMethodReference}s. */
    @Override
    public boolean visit(ExpressionMethodReference node) {
        sync(node);
        builder.open(plusFour);
        node.getExpression().accept(this);
        builder.breakOp();
        builder.op("::");
        if (!node.typeArguments().isEmpty()) {
            addTypeArguments(node.typeArguments(), plusFour);
        }
        visit(node.getName());
        builder.close();
        return false;
    }

    /** Visitor method for {@link ExpressionStatement}s. */
    @Override
    public boolean visit(ExpressionStatement node) {
        sync(node);
        node.getExpression().accept(this);
        token(";");
        return false;
    }

    /** Visitor method for {@link FieldAccess}es. */
    @Override
    public boolean visit(FieldAccess node) {
        sync(node);
        visitDot(node);
        return false;
    }

    /** Visitor method for {@link FieldDeclaration}s. */
    @Override
    public boolean visit(FieldDeclaration node) {
        sync(node);
        markForPartialFormat();
        addDeclaration(node, node.modifiers(), node.getType(), node.fragments(),
                fieldAnnotationDirection(node.modifiers()));
        return false;
    }

    /** Visitor method for {@link ForStatement}s. */
    @Override
    public boolean visit(ForStatement node) {
        sync(node);
        token("for");
        builder.space();
        token("(");
        builder.open(plusFour);
        builder.open(node.initializers().size() <= 1 ? ZERO : plusFour);
        boolean first = true;
        for (Expression initializer : (List<Expression>) node.initializers()) {
            if (!first) {
                token(",");
                builder.breakToFill(" ");
            }
            initializer.accept(this);
            first = false;
        }
        builder.close();
        token(";");
        builder.breakOp(" ");
        if (node.getExpression() != null) {
            node.getExpression().accept(this);
        }
        token(";");
        builder.breakOp(" ");
        if (!node.updaters().isEmpty()) {
            builder.open(node.updaters().size() <= 1 ? ZERO : plusFour);
            boolean firstUpdater = true;
            for (Expression updater : (List<Expression>) node.updaters()) {
                if (!firstUpdater) {
                    token(",");
                    builder.breakToFill(" ");
                }
                updater.accept(this);
                firstUpdater = false;
            }
            builder.close();
        }
        builder.close();
        token(")");
        visitStatement(node.getBody(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES,
                AllowTrailingBlankLine.NO);
        return false;
    }

    /** Visitor method for {@link IfStatement}s. */
    @Override
    public boolean visit(IfStatement node) {
        sync(node);
        // Collapse chains of else-ifs.
        List<Expression> expressions = new ArrayList<>();
        List<Statement> statements = new ArrayList<>();
        while (true) {
            expressions.add(node.getExpression());
            statements.add(node.getThenStatement());
            if (node.getElseStatement() != null && node.getElseStatement().getNodeType() == ASTNode.IF_STATEMENT) {
                node = (IfStatement) node.getElseStatement();
            } else {
                break;
            }
        }
        builder.open(ZERO);
        boolean first = true;
        boolean followingBlock = false;
        int expressionsN = expressions.size();
        for (int i = 0; i < expressionsN; i++) {
            if (!first) {
                if (followingBlock) {
                    builder.space();
                } else {
                    builder.forcedBreak();
                }
                token("else");
                builder.space();
            }
            token("if");
            builder.space();
            token("(");
            expressions.get(i).accept(this);
            token(")");
            // An empty block can collapse to "{}" if there are no if/else or else clauses
            boolean onlyClause = expressionsN == 1 && node.getElseStatement() == null;
            // Trailing blank lines are permitted if this isn't the last clause
            boolean trailingClauses = i < expressionsN - 1 || node.getElseStatement() != null;
            visitStatement(statements.get(i), CollapseEmptyOrNot.valueOf(onlyClause), AllowLeadingBlankLine.YES,
                    AllowTrailingBlankLine.valueOf(trailingClauses));
            followingBlock = statements.get(i).getNodeType() == ASTNode.BLOCK;
            first = false;
        }
        if (node.getElseStatement() != null) {
            if (followingBlock) {
                builder.space();
            } else {
                builder.forcedBreak();
            }
            token("else");
            visitStatement(node.getElseStatement(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES,
                    AllowTrailingBlankLine.NO);
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link ImportDeclaration}s. */
    @Override
    public boolean visit(ImportDeclaration node) {
        sync(node);
        token("import");
        builder.space();
        if (node.isStatic()) {
            token("static");
            builder.space();
        }
        visitName(node.getName(), BreakOrNot.NO);
        if (node.isOnDemand()) {
            token(".");
            token("*");
        }
        token(";");
        return false;
    }

    /** Visitor method for {@link InfixExpression}s. */
    @Override
    public boolean visit(InfixExpression node) {
        sync(node);
        /*
         * Collect together all operators with same precedence to clean up indentation. Eclipse's
         * extended operands help a little (to collect together the same operator), but they're applied
         * inconsistently, and don't apply to other operators of the same precedence.
         */
        List<Expression> operands = new ArrayList<>();
        List<String> operators = new ArrayList<>();
        walkInfix(PRECEDENCE.get(node.getOperator().toString()), node, operands, operators);
        builder.open(plusFour, maxLinesFilledForItems(operands, MAX_FILLED_INFIX_LINES));
        operands.get(0).accept(this);
        int operatorsN = operators.size();
        for (int i = 0; i < operatorsN; i++) {
            builder.breakToFill(" ");
            builder.op(operators.get(i));
            builder.space();
            operands.get(i + 1).accept(this);
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link Initializer}s. */
    @Override
    public boolean visit(Initializer node) {
        sync(node);
        visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent());
        node.getBody().accept(this);
        builder.guessToken(";");
        return false;
    }

    /** Visitor method for {@link InstanceofExpression}s. */
    @Override
    public boolean visit(InstanceofExpression node) {
        sync(node);
        builder.open(plusFour);
        node.getLeftOperand().accept(this);
        builder.breakOp(" ");
        builder.open(ZERO);
        token("instanceof");
        builder.breakOp(" ");
        node.getRightOperand().accept(this);
        builder.close();
        builder.close();
        return false;
    }

    /** Visitor method for {@link IntersectionType}s. */
    @Override
    public boolean visit(IntersectionType node) {
        sync(node);
        builder.open(plusFour);
        List<Type> types = new ArrayList<>();
        walkIntersectionTypes(types, node);
        boolean first = true;
        for (Type type : types) {
            if (!first) {
                builder.breakToFill(" ");
                token("&");
                builder.space();
            }
            type.accept(this);
            first = false;
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link LabeledStatement}s. */
    @Override
    public boolean visit(LabeledStatement node) {
        sync(node);
        builder.open(ZERO);
        visit(node.getLabel());
        token(":");
        builder.forcedBreak();
        builder.close();
        node.getBody().accept(this);
        return false;
    }

    /** Visitor method for {@link LambdaExpression}s. */
    @Override
    public boolean visit(LambdaExpression node) {
        sync(node);
        boolean statementBody = node.getBody().getNodeType() == ASTNode.BLOCK;
        builder.open(statementBody ? ZERO : plusFour);
        builder.open(plusFour);
        if (node.hasParentheses()) {
            token("(");
        }
        boolean first = true;
        for (ASTNode parameter : (List<ASTNode>) node.parameters()) {
            if (!first) {
                token(",");
                builder.breakOp(" ");
            }
            parameter.accept(this);
            ;
            first = false;
        }
        if (node.hasParentheses()) {
            token(")");
        }
        builder.close();
        builder.space();
        builder.op("->");
        if (statementBody) {
            builder.space();
        } else {
            builder.breakOp(" ");
        }
        node.getBody().accept(this);
        builder.close();
        return false;
    }

    /** Visitor method for {@link MarkerAnnotation}s. */
    @Override
    public boolean visit(MarkerAnnotation node) {
        sync(node);
        builder.open(ZERO);
        token("@");
        node.getTypeName().accept(this);
        builder.close();
        return false;
    }

    /** Visitor method for {@link MemberValuePair}s. */
    @Override
    public boolean visit(MemberValuePair node) {
        boolean isArrayInitializer = node.getValue().getNodeType() == ASTNode.ARRAY_INITIALIZER;
        sync(node);
        builder.open(isArrayInitializer ? ZERO : plusFour);
        visit(node.getName());
        builder.space();
        token("=");
        if (isArrayInitializer) {
            builder.space();
        } else {
            builder.breakOp(" ");
        }
        node.getValue().accept(this);
        builder.close();
        return false;
    }

    /** Visitor method for {@link MethodDeclaration}s. */
    @Override
    public boolean visit(MethodDeclaration node) {
        sync(node);
        visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent());

        builder.open(plusFour);
        {
            BreakTag breakBeforeName = genSym();
            BreakTag breakBeforeType = genSym();
            builder.open(ZERO);
            {
                boolean first = true;
                if (!node.typeParameters().isEmpty()) {
                    visitTypeParameters(node.typeParameters(), ZERO, BreakOrNot.NO);
                    first = false;
                }

                boolean openedNameAndTypeScope = false;
                // constructor-like declarations that don't match the name of the enclosing class are 
                // parsed as method declarations with a null return type
                if (!node.isConstructor() && node.getReturnType2() != null) {
                    if (!first) {
                        builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(breakBeforeType));
                    } else {
                        first = false;
                    }
                    if (!openedNameAndTypeScope) {
                        builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO));
                        openedNameAndTypeScope = true;
                    }
                    node.getReturnType2().accept(this);
                }
                if (!first) {
                    builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(breakBeforeName));
                } else {
                    first = false;
                }
                if (!openedNameAndTypeScope) {
                    builder.open(ZERO);
                    openedNameAndTypeScope = true;
                }
                visit(node.getName());
                token("(");
                // end of name and type scope
                builder.close();
            }
            builder.close();

            builder.open(Indent.If.make(breakBeforeName, plusFour, ZERO));
            builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO));
            builder.open(ZERO);
            {
                if (!node.parameters().isEmpty() || node.getReceiverType() != null) {
                    // Break before args.
                    builder.breakToFill("");
                    visitFormals(node, Optional.fromNullable(node.getReceiverType()), node.getReceiverQualifier(),
                            node.parameters());
                }
                token(")");
                extraDimensions(plusFour, node.extraDimensions());
                if (!node.thrownExceptionTypes().isEmpty()) {
                    builder.breakToFill(" ");
                    builder.open(plusFour);
                    {
                        visitThrowsClause(node.thrownExceptionTypes());
                    }
                    builder.close();
                }
            }
            builder.close();
            builder.close();
            builder.close();
        }
        builder.close();

        if (node.getBody() == null) {
            token(";");
        } else {
            builder.space();
            visitBlock(node.getBody(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES,
                    AllowTrailingBlankLine.NO);
        }
        builder.guessToken(";");

        return false;
    }

    /** Visitor method for {@link MethodInvocation}s. */
    @Override
    public boolean visit(MethodInvocation node) {
        sync(node);
        visitDot(node);
        return false;
    }

    /** Visitor method for {@link Modifier}s. */
    @Override
    public boolean visit(Modifier node) {
        sync(node);
        token(node.toString());
        return false;
    }

    // TODO(jdd): Collapse chains of "." operators here too.

    /** Visitor method for {@link NameQualifiedType}s. */
    @Override
    public boolean visit(NameQualifiedType node) {
        sync(node);
        beforeAnnotatableType(node);
        builder.open(plusFour);
        node.getQualifier().accept(this);
        builder.breakOp();
        token(".");
        visit(node.getName());
        builder.close();
        return false;
    }

    /** Visitor method for {@link NormalAnnotation}s. */
    @Override
    public boolean visit(NormalAnnotation node) {
        sync(node);
        builder.open(ZERO);
        token("@");
        node.getTypeName().accept(this);
        builder.open(plusTwo, MAX_LINES_FOR_ANNOTATION_ELEMENT_VALUE_PAIRS);
        token("(");
        builder.breakOp();
        boolean first = true;

        // Format the member value pairs one-per-line if any of them are
        // initialized with arrays.
        boolean hasArrayInitializer = false;
        for (MemberValuePair value : (List<MemberValuePair>) node.values()) {
            if (value.getValue().getNodeType() == ASTNode.ARRAY_INITIALIZER) {
                hasArrayInitializer = true;
                break;
            }
        }

        for (MemberValuePair value : (List<MemberValuePair>) node.values()) {
            if (!first) {
                token(",");
                if (hasArrayInitializer) {
                    builder.forcedBreak();
                } else {
                    builder.breakOp(" ");
                }
            }
            value.accept(this);
            first = false;
        }
        builder.breakOp(FillMode.UNIFIED, "", minusTwo, Optional.<BreakTag>absent());
        token(")");
        builder.close();
        builder.close();
        return false;
    }

    /** Visitor method for {@link NullLiteral}s. */
    @Override
    public boolean visit(NullLiteral node) {
        sync(node);
        token(node.toString());
        return false;
    }

    /** Visitor method for {@link NumberLiteral}s. */
    @Override
    public boolean visit(NumberLiteral node) {
        sync(node);
        String value = node.getToken();
        if (value.startsWith("-")) {
            // jdt normally parses negative numeric literals as a unary minus on an
            // unsigned literal, but in the case of Long.MIN_VALUE and
            // Integer.MIN_VALUE it creates a signed literal without a unary minus
            // expression.
            //
            // Unfortunately in both cases the input token stream will still have
            // the '-' token followed by an unsigned numeric literal token. We
            // hack around this by checking for a leading '-' in the text of the
            // given numeric literal, and emitting two separate tokens if it is
            // present to match the input stream.
            token("-");
            value = value.substring(1);
        }
        token(value);
        return false;
    }

    /** Visitor method for {@link PackageDeclaration}s. */
    @Override
    public boolean visit(PackageDeclaration node) {
        sync(node);
        visitAndBreakModifiers(node.annotations(), Direction.VERTICAL, Optional.<BreakTag>absent());
        builder.open(plusFour);
        token("package");
        builder.space();
        visitName(node.getName(), BreakOrNot.NO);
        builder.close();
        token(";");
        return false;
    }

    /** Visitor method for {@link ParameterizedType}s. */
    @Override
    public boolean visit(ParameterizedType node) {
        sync(node);
        if (node.typeArguments().isEmpty()) {
            node.getType().accept(this);
            token("<");
            token(">");
        } else {
            builder.open(plusFour);
            node.getType().accept(this);
            token("<");
            builder.breakOp();
            builder.open(ZERO);
            boolean first = true;
            for (Type typeArgument : (List<Type>) node.typeArguments()) {
                if (!first) {
                    token(",");
                    builder.breakToFill(" ");
                }
                typeArgument.accept(this);
                first = false;
            }
            builder.close();
            builder.close();
            token(">");
        }
        return false;
    }

    /** Visitor method for {@link ParenthesizedExpression}s. */
    @Override
    public boolean visit(ParenthesizedExpression node) {
        sync(node);
        token("(");
        node.getExpression().accept(this);
        token(")");
        return false;
    }

    /** Visitor method for {@link PostfixExpression}s. */
    @Override
    public boolean visit(PostfixExpression node) {
        sync(node);
        node.getOperand().accept(this);
        builder.op(node.getOperator().toString());
        return false;
    }

    /** Visitor method for {@link PrefixExpression}s. */
    @Override
    public boolean visit(PrefixExpression node) {
        sync(node);
        String op = node.getOperator().toString();
        builder.op(op);
        // Keep prefixes unambiguous.
        Expression operand = node.getOperand();
        if ((op.equals("+") || op.equals("-")) && operand.getNodeType() == ASTNode.PREFIX_EXPRESSION
                && ((PrefixExpression) operand).getOperator().toString().startsWith(op)) {
            builder.space();
        }
        operand.accept(this);
        return false;
    }

    /** Visitor method for {@link PrimitiveType}s. */
    @Override
    public boolean visit(PrimitiveType node) {
        sync(node);
        beforeAnnotatableType(node);
        token(node.toString());
        return false;
    }

    /** Visitor method for {@link QualifiedName}s. */
    @Override
    public boolean visit(QualifiedName node) {
        visitQualifiedName(node, BreakOrNot.YES);
        return false;
    }

    // TODO(jdd): Can we share?

    /** Visitor method for {@link QualifiedType}s. */
    @Override
    public boolean visit(QualifiedType node) {
        sync(node);
        builder.open(plusFour);
        // Collapse chains of "." operators.
        ArrayDeque<SimpleName> stack = new ArrayDeque<>();
        Type qualifier;
        while (true) {
            stack.add(node.getName());
            qualifier = node.getQualifier();
            if (qualifier.getNodeType() != ASTNode.QUALIFIED_TYPE) {
                break;
            }
            node = (QualifiedType) qualifier;
        }
        qualifier.accept(this);
        do {
            builder.breakOp();
            token(".");
            visit(stack.removeLast());
        } while (!stack.isEmpty());
        builder.close();
        return false;
    }

    /** Visitor method for {@link ReturnStatement}s. */
    @Override
    public boolean visit(ReturnStatement node) {
        sync(node);
        token("return");
        if (node.getExpression() != null) {
            builder.space();
            node.getExpression().accept(this);
        }
        token(";");
        return false;
    }

    /** Visitor method for {@link SimpleName}s. */
    @Override
    public boolean visit(SimpleName node) {
        sync(node);
        token(node.getIdentifier());
        return false;
    }

    /** Visitor method for {@link SimpleType}s. */
    @Override
    public boolean visit(SimpleType node) {
        sync(node);
        beforeAnnotatableType(node);
        node.getName().accept(this);
        return false;
    }

    /** Visitor method for {@link SingleMemberAnnotation}s. */
    @Override
    public boolean visit(SingleMemberAnnotation node) {
        sync(node);
        Expression value = node.getValue();
        boolean isArrayInitializer = value.getNodeType() == ASTNode.ARRAY_INITIALIZER;
        builder.open(isArrayInitializer ? ZERO : plusFour);
        token("@");
        node.getTypeName().accept(this);
        token("(");
        if (!isArrayInitializer) {
            builder.breakOp();
        }
        value.accept(this);
        builder.close();
        token(")");
        return false;
    }

    /** Visitor method for {@link SingleVariableDeclaration}s. */
    @Override
    public boolean visit(SingleVariableDeclaration node) {
        visitToDeclare(Direction.HORIZONTAL, node, Optional.fromNullable(node.getInitializer()), "=");
        return false;
    }

    /** Visitor method for {@link StringLiteral}s. */
    @Override
    public boolean visit(StringLiteral node) {
        sync(node);
        token(node.getEscapedValue());
        return false;
    }

    /** Visitor method for {@link SuperConstructorInvocation}s. */
    @Override
    public boolean visit(SuperConstructorInvocation node) {
        sync(node);
        if (node.getExpression() != null) {
            node.getExpression().accept(this);
            token(".");
        }
        addTypeArguments(node.typeArguments(), plusFour);
        token("super");
        addArguments(node.arguments(), plusFour);
        token(";");
        return false;
    }

    /** Visitor method for {@link SuperFieldAccess}es. */
    @Override
    public boolean visit(SuperFieldAccess node) {
        sync(node);
        builder.open(plusFour);
        if (node.getQualifier() != null) {
            node.getQualifier().accept(this);
            builder.breakOp();
            token(".");
        }
        token("super");
        builder.breakOp();
        token(".");
        visit(node.getName());
        builder.close();
        return false;
    }

    /** Visitor method for {@link SuperMethodInvocation}s. */
    @Override
    public boolean visit(SuperMethodInvocation node) {
        sync(node);
        builder.open(ZERO);
        if (node.getQualifier() != null) {
            node.getQualifier().accept(this);
            builder.breakOp();
            token(".");
        }
        token("super");
        builder.breakOp();
        token(".");
        builder.close();
        addTypeArguments(node.typeArguments(), plusFour);
        visit(node.getName());
        addArguments(node.arguments(), plusFour);
        return false;
    }

    /** Visitor method for {@link SuperMethodReference}s. */
    @Override
    public boolean visit(SuperMethodReference node) {
        sync(node);
        builder.open(plusFour);
        if (node.getQualifier() != null) {
            builder.open(plusFour);
            node.getQualifier().accept(this);
            builder.breakOp();
            token(".");
            builder.close();
        }
        token("super");
        builder.breakOp();
        builder.op("::");
        if (!node.typeArguments().isEmpty()) {
            addTypeArguments(node.typeArguments(), plusFour);
        }
        visit(node.getName());
        builder.close();
        return false;
    }

    /** Visitor method for {@link SwitchCase}s. */
    @Override
    public boolean visit(SwitchCase node) {
        sync(node);
        markForPartialFormat();
        if (node.isDefault()) {
            token("default", plusTwo);
            token(":");
        } else {
            token("case", plusTwo);
            builder.space();
            node.getExpression().accept(this);
            token(":");
        }
        return false;
    }

    /** Visitor method for {@link SwitchStatement}s. */
    @Override
    public boolean visit(SwitchStatement node) {
        sync(node);
        token("switch");
        builder.space();
        token("(");
        node.getExpression().accept(this);
        token(")");
        builder.space();
        tokenBreakTrailingComment("{", plusTwo);
        builder.blankLineWanted(BlankLineWanted.NO);
        builder.open(plusFour);
        boolean first = true;
        boolean lastWasSwitchCase = false;
        for (ASTNode statement : (List<ASTNode>) node.statements()) {
            if (!first && !lastWasSwitchCase) {
                builder.blankLineWanted(BlankLineWanted.PRESERVE);
            }
            if (statement.getNodeType() == ASTNode.SWITCH_CASE) {
                builder.open(minusTwo);
                builder.forcedBreak();
                visit((SwitchCase) statement);
                builder.close();
                lastWasSwitchCase = true;
            } else {
                builder.forcedBreak();
                statement.accept(this);
                lastWasSwitchCase = false;
            }
            first = false;
        }
        builder.close();
        builder.forcedBreak();
        builder.blankLineWanted(BlankLineWanted.NO);
        token("}", plusFour);
        return false;
    }

    /** Visitor method for {@link SynchronizedStatement}s. */
    @Override
    public boolean visit(SynchronizedStatement node) {
        sync(node);
        token("synchronized");
        builder.space();
        token("(");
        builder.open(plusFour);
        builder.breakOp();
        node.getExpression().accept(this);
        builder.close();
        token(")");
        builder.space();
        node.getBody().accept(this);
        return false;
    }

    /** Visitor method for {@link ThisExpression}s. */
    @Override
    public boolean visit(ThisExpression node) {
        sync(node);
        if (node.getQualifier() != null) {
            builder.open(plusFour);
            node.getQualifier().accept(this);
            builder.breakOp();
            token(".");
            builder.close();
        }
        token("this");
        return false;
    }

    /** Visitor method for {@link ThrowStatement}s. */
    @Override
    public boolean visit(ThrowStatement node) {
        sync(node);
        token("throw");
        builder.space();
        node.getExpression().accept(this);
        token(";");
        return false;
    }

    /** Visitor method for {@link TryStatement}s. */
    @Override
    public boolean visit(TryStatement node) {
        sync(node);
        builder.open(ZERO);
        token("try");
        builder.space();
        if (!node.resources().isEmpty()) {
            token("(");
            builder.open(plusFour);
            boolean first = true;
            for (VariableDeclarationExpression resource : (List<VariableDeclarationExpression>) node.resources()) {
                if (!first) {
                    token(";");
                    builder.forcedBreak();
                }
                visit(resource);
                first = false;
            }
            // TODO(cushon): emit a space after the optional trailing semi-colon
            builder.guessToken(";");
            token(")");
            builder.close();
            builder.space();
        }
        // An empty try-with-resources body can collapse to "{}" if there are no trailing catch or
        // finally blocks.
        boolean trailingClauses = !node.catchClauses().isEmpty() || node.getFinally() != null;
        visitBlock(node.getBody(), CollapseEmptyOrNot.valueOf(!trailingClauses), AllowLeadingBlankLine.YES,
                AllowTrailingBlankLine.valueOf(trailingClauses));
        for (int i = 0; i < node.catchClauses().size(); i++) {
            CatchClause catchClause = (CatchClause) node.catchClauses().get(i);
            trailingClauses = i < node.catchClauses().size() - 1 || node.getFinally() != null;
            visitCatchClause(catchClause, AllowTrailingBlankLine.valueOf(trailingClauses));
        }
        if (node.getFinally() != null) {
            builder.space();
            token("finally");
            builder.space();
            visitBlock(node.getFinally(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES,
                    AllowTrailingBlankLine.NO);
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link TypeDeclaration}s. */
    @Override
    public boolean visit(TypeDeclaration node) {
        sync(node);
        List<Op> breaks = visitModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent());
        boolean hasSuperclassType = node.getSuperclassType() != null;
        boolean hasSuperInterfaceTypes = !node.superInterfaceTypes().isEmpty();
        builder.open(ZERO);
        builder.addAll(breaks);
        token(node.isInterface() ? "interface" : "class");
        builder.space();
        visit(node.getName());
        if (!node.typeParameters().isEmpty()) {
            visitTypeParameters(node.typeParameters(),
                    hasSuperclassType || hasSuperInterfaceTypes ? plusFour : ZERO, BreakOrNot.YES);
        }
        if (hasSuperclassType || hasSuperInterfaceTypes) {
            builder.open(plusFour);
            if (hasSuperclassType) {
                builder.breakToFill(" ");
                token("extends");
                // TODO(b/20761216): using a non-breaking space here could cause >100 char lines
                builder.space();
                node.getSuperclassType().accept(this);
            }
            if (hasSuperInterfaceTypes) {
                builder.breakToFill(" ");
                builder.open(node.superInterfaceTypes().size() > 1 ? plusFour : ZERO);
                token(node.isInterface() ? "extends" : "implements");
                builder.space();
                boolean first = true;
                for (Type superInterfaceType : (List<Type>) node.superInterfaceTypes()) {
                    if (!first) {
                        token(",");
                        builder.breakToFill(" ");
                    }
                    superInterfaceType.accept(this);
                    first = false;
                }
                builder.close();
            }
            builder.close();
        }
        builder.close();
        if (node.bodyDeclarations() == null) {
            token(";");
        } else {
            addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.YES, FirstDeclarationsOrNot.YES);
            builder.guessToken(";");
        }
        return false;
    }

    /** Visitor method for {@link TypeDeclarationStatement}s. */
    @Override
    public boolean visit(TypeDeclarationStatement node) {
        sync(node);
        node.getDeclaration().accept(this);
        return false;
    }

    /** Visitor method for {@link TypeLiteral}s. */
    @Override
    public boolean visit(TypeLiteral node) {
        sync(node);
        builder.open(plusFour);
        node.getType().accept(this);
        builder.breakOp();
        token(".");
        token("class");
        builder.close();
        return false;
    }

    /** Visitor method for {@link TypeMethodReference}s. */
    @Override
    public boolean visit(TypeMethodReference node) {
        sync(node);
        builder.open(plusFour);
        node.getType().accept(this);
        builder.breakOp();
        builder.op("::");
        if (!node.typeArguments().isEmpty()) {
            addTypeArguments(node.typeArguments(), plusFour);
        }
        visit(node.getName());
        builder.close();
        return false;
    }

    /** Visitor method for {@link TypeParameter}s. */
    @Override
    public boolean visit(TypeParameter node) {
        sync(node);
        builder.open(ZERO);
        visitAndBreakModifiers(node.modifiers(), Direction.HORIZONTAL, Optional.<BreakTag>absent());
        visit(node.getName());
        if (!node.typeBounds().isEmpty()) {
            builder.space();
            token("extends");
            builder.open(plusFour);
            builder.breakOp(" ");
            builder.open(plusFour);
            boolean first = true;
            for (Type typeBound : (List<Type>) node.typeBounds()) {
                if (!first) {
                    builder.breakToFill(" ");
                    token("&");
                    builder.space();
                }
                typeBound.accept(this);
                first = false;
            }
            builder.close();
            builder.close();
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link UnionType}s. */
    @Override
    public boolean visit(UnionType node) {
        sync(node);
        builder.open(plusFour);
        List<Type> types = new ArrayList<>();
        walkUnionTypes(types, node);
        boolean first = true;
        for (Type type : types) {
            if (!first) {
                builder.breakOp(" ");
                token("|");
                builder.space();
            }
            type.accept(this);
            first = false;
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link VariableDeclarationExpression}s. */
    @Override
    public boolean visit(VariableDeclarationExpression node) {
        sync(node);
        builder.open(plusFour);
        // TODO(jdd): Why no use common method?
        for (IExtendedModifier modifier : (List<IExtendedModifier>) node.modifiers()) {
            ((ASTNode) modifier).accept(this);
            builder.breakToFill(" ");
        }
        node.getType().accept(this);
        if (node.fragments().size() == 1) {
            builder.breakToFill(" ");
            visit((VariableDeclarationFragment) node.fragments().get(0));
        } else {
            // TODO(jdd): Are the indentations consistent here?
            builder.breakToFill(" ");
            builder.open(plusFour);
            boolean first = true;
            for (VariableDeclarationFragment fragment : (List<VariableDeclarationFragment>) node.fragments()) {
                if (!first) {
                    token(",");
                    builder.breakToFill(" ");
                }
                visit(fragment);
                first = false;
            }
            builder.close();
        }
        builder.close();
        return false;
    }

    /** Visitor method for {@link VariableDeclarationFragment}s. */
    @Override
    public boolean visit(VariableDeclarationFragment node) {
        sync(node);
        // TODO(jdd): Why no open-close?
        visit(node.getName());
        extraDimensions(plusFour, node.extraDimensions());
        if (node.getInitializer() != null) {
            builder.space();
            token("=");
            builder.breakToFill(" ");
            // TODO(jdd): Why this way.
            builder.open(ZERO);
            node.getInitializer().accept(this);
            builder.close();
        }
        return false;
    }

    // TODO(jdd): Worry about upper and lower bounds.

    /** Visitor method for {@link VariableDeclarationStatement}s. */
    @Override
    public boolean visit(VariableDeclarationStatement node) {
        sync(node);
        addDeclaration(node, node.modifiers(), node.getType(), node.fragments(),
                canLocalHaveHorizontalAnnotations(node.modifiers()));
        return false;
    }

    /** Visitor method for {@link WhileStatement}s. */
    @Override
    public boolean visit(WhileStatement node) {
        sync(node);
        token("while");
        builder.space();
        token("(");
        node.getExpression().accept(this);
        token(")");
        visitStatement(node.getBody(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES,
                AllowTrailingBlankLine.NO);
        return false;
    }

    /** Visitor method for {@link WildcardType}s. */
    @Override
    public boolean visit(WildcardType node) {
        sync(node);
        beforeAnnotatableType(node);
        builder.open(ZERO);
        token("?");
        if (node.getBound() != null) {
            builder.open(plusFour);
            builder.space();
            token(node.isUpperBound() ? "extends" : "super");
            builder.breakOp(" ");
            node.getBound().accept(this);
            builder.close();
        }
        builder.close();
        return false;
    }

    // Helper methods.

    /** Before Visitor methods for {@link Type}. */
    private void beforeAnnotatableType(AnnotatableType node) {
        if (!node.annotations().isEmpty()) {
            builder.open(ZERO);
            for (Annotation annotation : (List<Annotation>) node.annotations()) {
                annotation.accept(this);
                builder.breakOp(" ");
            }
            builder.close();
        }
    }

    /** Helper method for {@link Annotation}s and declareOne. */
    void visitAnnotations(List<Annotation> annotations, BreakOrNot breakBefore, BreakOrNot breakAfter) {
        if (!annotations.isEmpty()) {
            if (breakBefore.isYes()) {
                builder.breakToFill(" ");
            }
            boolean first = true;
            for (Annotation annotation : annotations) {
                if (!first) {
                    builder.breakToFill(" ");
                }
                annotation.accept(this);
                first = false;
            }
            if (breakAfter.isYes()) {
                builder.breakToFill(" ");
            }
        }
    }

    /**
     * Helper method for {@link Block}s, {@link CatchClause}s, {@link Statement}s,
     * {@link TryStatement}s, and {@link WhileStatement}s.
     */
    private void visitBlock(Block node, CollapseEmptyOrNot collapseEmptyOrNot,
            AllowLeadingBlankLine allowLeadingBlankLine, AllowTrailingBlankLine allowTrailingBlankLine) {
        sync(node);
        if (collapseEmptyOrNot.isYes() && node.statements().isEmpty()) {
            tokenBreakTrailingComment("{", plusTwo);
            builder.blankLineWanted(BlankLineWanted.NO);
            token("}", plusTwo);
        } else {
            builder.open(ZERO);
            builder.open(plusTwo);
            tokenBreakTrailingComment("{", plusTwo);
            if (allowLeadingBlankLine == AllowLeadingBlankLine.NO) {
                builder.blankLineWanted(BlankLineWanted.NO);
            } else {
                builder.blankLineWanted(BlankLineWanted.PRESERVE);
            }
            boolean first = true;
            for (Statement statement : (List<Statement>) node.statements()) {
                builder.forcedBreak();
                if (!first) {
                    builder.blankLineWanted(BlankLineWanted.PRESERVE);
                }
                first = false;
                markForPartialFormat();
                statement.accept(this);
            }
            builder.close();
            builder.forcedBreak();
            builder.close();
            if (allowTrailingBlankLine == AllowTrailingBlankLine.NO) {
                builder.blankLineWanted(BlankLineWanted.NO);
            } else {
                builder.blankLineWanted(BlankLineWanted.PRESERVE);
            }
            markForPartialFormat();
            token("}", plusTwo);
        }
    }

    /**
     * Helper method for {@link DoStatement}s, {@link EnhancedForStatement}s, {@link ForStatement}s,
     * {@link IfStatement}s, and WhileStatements.
     */
    private void visitStatement(Statement node, CollapseEmptyOrNot collapseEmptyOrNot,
            AllowLeadingBlankLine allowLeadingBlank, AllowTrailingBlankLine allowTrailingBlank) {
        sync(node);
        switch (node.getNodeType()) {
        case ASTNode.BLOCK:
            builder.space();
            visitBlock((Block) node, collapseEmptyOrNot, allowLeadingBlank, allowTrailingBlank);
            break;
        default:
            // TODO(jdd): Fix.
            builder.open(plusTwo);
            builder.breakOp(" ");
            node.accept(this);
            builder.close();
        }
    }

    /** Helper method for {@link ArrayCreation}s and {@link ArrayType}s. */
    private void visitArrayType(ArrayType node, DimensionsOrNot includeDimensions) {
        if (includeDimensions.isYes() && !node.dimensions().isEmpty()) {
            builder.open(plusFour);
        }
        node.getElementType().accept(this);
        if (includeDimensions.isYes()) {
            for (Dimension dimension : (List<Dimension>) node.dimensions()) {
                builder.breakToFill(dimension.annotations().isEmpty() ? "" : " ");
                visit(dimension);
            }
        }
        if (includeDimensions.isYes() && !node.dimensions().isEmpty()) {
            builder.close();
        }
    }

    /**
     * Helper methods for {@link AnnotationTypeDeclaration}s,
     * {@link AnnotationTypeMemberDeclaration}s, {@link EnumDeclaration}s, {@link Initializer}s,
     * {@link MethodDeclaration}s, {@link PackageDeclaration}s, and {@link TypeParameter}s. Output
     * combined modifiers and annotations and the trailing break.
     * @param modifiers a list of {@link IExtendedModifier}s, which can include annotations
     * @param annotationDirection direction of annotations
     * @param declarationAnnotationBreak a tag for the {code Break} after any declaration annotations.
     */
    void visitAndBreakModifiers(List<IExtendedModifier> modifiers, Direction annotationDirection,
            Optional<BreakTag> declarationAnnotationBreak) {
        builder.addAll(visitModifiers(modifiers, annotationDirection, declarationAnnotationBreak));
    }

    /**
     * Helper method for {@link EnumConstantDeclaration}s, {@link TypeDeclaration}s, and
     * {@code visitAndBreakModifiers}. Output combined modifiers and annotations and returns the
     * trailing break.
     * @param modifiers a list of {@link IExtendedModifier}s, which can include annotations
     * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL}
     * @param declarationAnnotationBreak a tag for the {code Break} after any declaration annotations.
     * @return the list of {@link Doc.Break}s following the modifiers and annotations
     */
    private List<Op> visitModifiers(List<IExtendedModifier> modifiers, Direction annotationsDirection,
            Optional<BreakTag> declarationAnnotationBreak) {
        if (modifiers.isEmpty()) {
            return EMPTY_LIST;
        }
        builder.open(ZERO);
        boolean first = true;
        boolean lastWasAnnotation = false;
        int idx = 0;
        for (; idx < modifiers.size(); idx++) {
            IExtendedModifier modifier = modifiers.get(idx);
            if (modifier.isModifier()) {
                break;
            }
            if (!first) {
                builder.addAll(annotationsDirection.isVertical() ? forceBreakList(declarationAnnotationBreak)
                        : breakList(declarationAnnotationBreak));
            }
            ((ASTNode) modifier).accept(this);
            first = false;
            lastWasAnnotation = true;
        }
        builder.close();
        ImmutableList<Op> trailingBreak = annotationsDirection.isVertical()
                ? forceBreakList(declarationAnnotationBreak)
                : breakList(declarationAnnotationBreak);
        if (idx >= modifiers.size()) {
            return trailingBreak;
        }
        if (lastWasAnnotation) {
            builder.addAll(trailingBreak);
        }

        builder.open(ZERO);
        first = true;
        for (; idx < modifiers.size(); idx++) {
            IExtendedModifier modifier = modifiers.get(idx);
            if (!first) {
                builder.addAll(breakFillList(Optional.<BreakTag>absent()));
            }
            ((ASTNode) modifier).accept(this);
            first = false;
            lastWasAnnotation = modifier.isAnnotation();
        }
        builder.close();
        return breakFillList(Optional.<BreakTag>absent());
    }

    /** Helper method for {@link CatchClause}s. */
    private void visitCatchClause(CatchClause node, AllowTrailingBlankLine allowTrailingBlankLine) {
        sync(node);
        builder.space();
        token("catch");
        builder.space();
        token("(");
        builder.open(plusFour);
        builder.breakOp();
        builder.open(ZERO);
        visit(node.getException());
        builder.close();
        builder.close();
        token(")");
        builder.space();
        visitBlock(node.getBody(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES, allowTrailingBlankLine);
    }

    /**
     * Helper method for {@link InfixExpression}s. Visit this {@link Expression} node, and its
     * children, as long as they are {@link InfixExpression} nodes of the same precedence. Accumulate
     * the operands and operators.
     * @param precedence the precedence of the operators to collect
     * @param operands the output list of {@code n + 1} operands
     * @param operators the output list of {@code n} operators
     */
    private static void walkInfix(int precedence, Expression expression, List<Expression> operands,
            List<String> operators) {
        if (expression.getNodeType() == ASTNode.INFIX_EXPRESSION) {
            InfixExpression infixExpression = (InfixExpression) expression;
            String myOperator = infixExpression.getOperator().toString();
            if (PRECEDENCE.get(myOperator) == precedence) {
                walkInfix(precedence, infixExpression.getLeftOperand(), operands, operators);
                operators.add(myOperator);
                walkInfix(precedence, infixExpression.getRightOperand(), operands, operators);
                if (infixExpression.hasExtendedOperands()) {
                    for (Expression extendedOperand : (List<Expression>) infixExpression.extendedOperands()) {
                        operators.add(myOperator);
                        walkInfix(precedence, extendedOperand, operands, operators);
                    }
                }
            } else {
                operands.add(expression);
            }
        } else {
            operands.add(expression);
        }
    }

    // TODO(jdd): Merge with union types.

    /** Helper method for {@link IntersectionType}s. */
    private static void walkIntersectionTypes(List<Type> types, IntersectionType node) {
        for (ASTNode type : (List<ASTNode>) node.types()) {
            if (type.getNodeType() == ASTNode.INTERSECTION_TYPE) {
                walkIntersectionTypes(types, (IntersectionType) type);
            } else {
                types.add((Type) type);
            }
        }
    }

    /** Helper method for {@link MethodDeclaration}s. */
    private void visitFormals(ASTNode node, Optional<Type> receiverType, SimpleName receiverQualifier,
            List<SingleVariableDeclaration> parameters) {
        if (receiverType.isPresent() || !parameters.isEmpty()) {
            builder.open(ZERO, MAX_LINES_FOR_FORMAL_LIST);
            boolean first = true;
            if (receiverType.isPresent()) {
                // TODO(jdd): Use builders.
                declareOne(node, Direction.HORIZONTAL, ImmutableList.<IExtendedModifier>of(), receiverType.get(),
                        VarArgsOrNot.NO, ImmutableList.<Annotation>of(), receiverQualifier, "",
                        ImmutableList.<Dimension>of(), "", Optional.<Expression>absent(), Optional.<String>absent(),
                        ReceiverParameter.YES);
                first = false;
            }
            for (SingleVariableDeclaration parameter : parameters) {
                if (!first) {
                    token(",");
                    builder.breakToFill(" ");
                }
                // TODO(jdd): Check for "=".
                visitToDeclare(Direction.HORIZONTAL, parameter, Optional.<Expression>absent(), "=");
                first = false;
            }
            builder.close();
        }
    }

    /** Helper method for {@link MethodDeclaration}s. */
    private void visitThrowsClause(List<Type> thrownExceptionTypes) {
        token("throws");
        builder.breakToFill(" ");
        boolean first = true;
        for (Type thrownExceptionType : thrownExceptionTypes) {
            if (!first) {
                token(",");
                builder.breakToFill(" ");
            }
            thrownExceptionType.accept(this);
            first = false;
        }
    }

    /** Helper method for {@link ImportDeclaration}s, {@link Name}s, and {@link QualifiedName}s. */
    private void visitName(Name node, BreakOrNot breaks) {
        sync(node);
        if (node.isSimpleName()) {
            visit((SimpleName) node);
        } else {
            visitQualifiedName((QualifiedName) node, breaks);
        }
    }

    /**
     * Helper method for {@link EnhancedForStatement}s, {@link MethodDeclaration}s, and
     * {@link SingleVariableDeclaration}s.
     */
    private void visitToDeclare(Direction annotationsDirection, SingleVariableDeclaration node,
            Optional<Expression> initializer, String equals) {
        sync(node);
        declareOne(node, annotationsDirection, node.modifiers(), node.getType(),
                VarArgsOrNot.valueOf(node.isVarargs()), node.varargsAnnotations(), node.getName(), "",
                node.extraDimensions(), equals, initializer, Optional.<String>absent(), ReceiverParameter.NO);
    }

    /** Helper method for {@link MethodDeclaration}s and {@link TypeDeclaration}s. */
    private void visitTypeParameters(List<TypeParameter> nodes, Indent plusIndent, BreakOrNot breakAfterOpen) {
        if (!nodes.isEmpty()) {
            token("<");
            builder.open(plusIndent);
            builder.open(plusFour);
            if (breakAfterOpen.isYes()) {
                builder.breakOp();
            }
            boolean first = true;
            for (TypeParameter node : nodes) {
                if (!first) {
                    token(",");
                    builder.breakToFill(" ");
                }
                visit(node);
                first = false;
            }
            builder.close();
            builder.close();
            token(">");
        }
    }

    /** Helper method for {@link UnionType}s. */
    private static void walkUnionTypes(List<Type> types, UnionType node) {
        for (ASTNode type : (List<ASTNode>) node.types()) {
            if (type.getNodeType() == ASTNode.UNION_TYPE) {
                walkUnionTypes(types, (UnionType) type);
            } else {
                types.add((Type) type);
            }
        }
    }

    /** Collapse chains of {@code .} operators, across multiple {@link ASTNode} types. */

    /**
     * Output a "." node.
     * @param node0 the "." node
     */
    void visitDot(Expression node0) {
        Expression node = node0;

        // collect a flattened list of "."-separated items
        // e.g. ImmutableList.builder().add(1).build() -> [ImmutableList, builder(), add(1), build()]
        ArrayDeque<Expression> stack = new ArrayDeque<>();
        LOOP: do {
            stack.addFirst(node);
            switch (node.getNodeType()) {
            case ASTNode.FIELD_ACCESS:
                node = ((FieldAccess) node).getExpression();
                break;
            case ASTNode.METHOD_INVOCATION:
                node = ((MethodInvocation) node).getExpression();
                break;
            case ASTNode.QUALIFIED_NAME:
                node = ((QualifiedName) node).getQualifier();
                break;
            case ASTNode.SIMPLE_NAME:
                node = null;
                break LOOP;
            default:
                // If the dot chain starts with a primary expression
                // (e.g. a class instance creation, or a conditional expression)
                // then remove it from the list and deal with it first.
                stack.removeFirst();
                break LOOP;
            }
        } while (node != null);
        List<Expression> items = new ArrayList<>(stack);

        boolean needDot = false;

        // The dot chain started with a primary expression: output it normally, and indent
        // the rest of the chain +4.
        if (node != null) {
            // Exception: if it's an anonymous class declaration, we don't need to
            // break and indent after the trailing '}'.
            if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION
                    && ((ClassInstanceCreation) node).getAnonymousClassDeclaration() != null) {
                builder.open(ZERO);
                node.accept(this);
                token(".");
            } else {
                builder.open(plusFour);
                node.accept(this);
                builder.breakOp();
                needDot = true;
            }
        }

        // Check if the dot chain has a prefix that looks like a type name, so we can
        // treat the type name-shaped part as a single syntactic unit.
        int prefixIndex = TypeNameClassifier.typePrefixLength(simpleNames(stack));

        int invocationCount = 0;
        int firstInvocationIndex = -1;
        {
            for (int i = 0; i < items.size(); i++) {
                Expression expression = items.get(i);
                if (expression.getNodeType() == ASTNode.METHOD_INVOCATION) {
                    if (i > 0 || node != null) {
                        // we only want dereference invocations
                        invocationCount++;
                    }
                    if (firstInvocationIndex < 0) {
                        firstInvocationIndex = i;
                    }
                }
            }
        }

        // If there's only one invocation, treat leading field accesses as a single
        // unit. In the normal case we want to preserve the alignment of subsequent
        // method calls, and would emit e.g.:
        //
        // myField
        //     .foo()
        //     .bar();
        //
        // But if there's no 'bar()' to worry about the alignment of we prefer:
        //
        // myField.foo();
        //
        // to:
        //
        // myField
        //     .foo();
        //
        if (invocationCount == 1) {
            prefixIndex = firstInvocationIndex;
        }

        if (prefixIndex > 0) {
            visitDotWithPrefix(items, needDot, prefixIndex);
        } else {
            visitRegularDot(items, needDot);
        }

        if (node != null) {
            builder.close();
        }
    }

    /**
     * Output a "regular" chain of dereferences, possibly in builder-style. Break before every dot.
     *
     * @param items           in the chain
     * @param needDot         whether a leading dot is needed
     */
    private void visitRegularDot(List<Expression> items, boolean needDot) {
        boolean trailingDereferences = items.size() > 1;
        boolean needDot0 = needDot;
        if (!needDot0) {
            builder.open(plusFour, MAX_LINES_FOR_CHAINED_ACCESSES);
        }
        // don't break after the first element if it is every small, unless the
        // chain starts with another expression
        int minLength = indentMultiplier * 4;
        int length = needDot0 ? minLength : 0;
        for (Expression e : items) {
            if (needDot) {
                if (length > minLength) {
                    builder.breakOp(FillMode.UNIFIED, "", ZERO);
                }
                token(".");
                length++;
            }
            BreakTag tyargTag = genSym();
            dotExpressionUpToArgs(e, Optional.of(tyargTag));
            Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO);
            dotExpressionArgsAndParen(e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO);
            length += e.getLength();
            needDot = true;
        }
        if (!needDot0) {
            builder.close();
        }
    }

    /**
     * Output a chain of dereferences where some prefix should be treated as a single
     * syntactic unit, either because it looks like a type name or because there
     * is only a single method invocation in the chain.
     *
     * @param items           in the chain
     * @param needDot         whether a leading dot is needed
     * @param prefixIndex     the index of the last item in the prefix
     */
    private void visitDotWithPrefix(List<Expression> items, boolean needDot, int prefixIndex) {
        // Are there method invocations or field accesses after the prefix?
        boolean trailingDereferences = prefixIndex >= 0 && prefixIndex < items.size() - 1;

        builder.open(plusFour, MAX_LINES_FOR_CHAINED_ACCESSES);
        builder.open(trailingDereferences ? ZERO : ZERO);

        BreakTag nameTag = genSym();
        for (int i = 0; i < items.size(); i++) {
            Expression e = items.get(i);
            if (needDot) {
                FillMode fillMode;
                if (prefixIndex >= 0 && i <= prefixIndex) {
                    fillMode = FillMode.INDEPENDENT;
                } else {
                    fillMode = FillMode.UNIFIED;
                }

                builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag));
                token(".");
            }
            BreakTag tyargTag = genSym();
            dotExpressionUpToArgs(e, Optional.of(tyargTag));
            if (prefixIndex >= 0 && i == prefixIndex) {
                builder.close();
            }

            Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO);
            Indent argsIndent = Indent.If.make(nameTag, plusFour, trailingDereferences ? plusFour : ZERO);
            dotExpressionArgsAndParen(e, tyargIndent, argsIndent);

            needDot = true;
        }

        builder.close();
    }

    /** Returns the simple names of expressions in a "." chain. */
    private List<String> simpleNames(ArrayDeque<Expression> stack) {
        ImmutableList.Builder<String> simpleNames = ImmutableList.builder();
        OUTER: for (Expression expression : stack) {
            switch (expression.getNodeType()) {
            case ASTNode.FIELD_ACCESS:
                simpleNames.add(((FieldAccess) expression).getName().getIdentifier());
                break;
            case ASTNode.QUALIFIED_NAME:
                simpleNames.add(((QualifiedName) expression).getName().getIdentifier());
                break;
            case ASTNode.SIMPLE_NAME:
                simpleNames.add(((SimpleName) expression).getIdentifier());
                break;
            case ASTNode.METHOD_INVOCATION:
                simpleNames.add(((MethodInvocation) expression).getName().getIdentifier());
                break OUTER;
            default:
                break OUTER;
            }
        }
        return simpleNames.build();
    }

    private void dotExpressionUpToArgs(Expression expression, Optional<BreakTag> tyargTag) {
        switch (expression.getNodeType()) {
        case ASTNode.FIELD_ACCESS:
            FieldAccess fieldAccess = (FieldAccess) expression;
            visit(fieldAccess.getName());
            break;
        case ASTNode.METHOD_INVOCATION:
            MethodInvocation methodInvocation = (MethodInvocation) expression;
            if (!methodInvocation.typeArguments().isEmpty()) {
                builder.open(plusFour);
                addTypeArguments(methodInvocation.typeArguments(), ZERO);
                // TODO(jdd): Should indent the name -4.
                builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, tyargTag);
                builder.close();
            }
            visit(methodInvocation.getName());
            break;
        case ASTNode.QUALIFIED_NAME:
            visit(((QualifiedName) expression).getName());
            break;
        case ASTNode.SIMPLE_NAME:
            visit(((SimpleName) expression));
            break;
        default:
            expression.accept(this);
        }
    }

    private void dotExpressionArgsAndParen(Expression expression, Indent tyargIndent, Indent indent) {
        switch (expression.getNodeType()) {
        case ASTNode.METHOD_INVOCATION:
            builder.open(tyargIndent);
            MethodInvocation methodInvocation = (MethodInvocation) expression;
            addArguments(methodInvocation.arguments(), indent);
            builder.close();
            break;
        case ASTNode.FIELD_ACCESS:
        case ASTNode.SIMPLE_NAME:
        case ASTNode.QUALIFIED_NAME:
        default:
            break;
        }
    }

    /** Helper methods for method invocations. */

    void addTypeArguments(List<Type> typeArguments, Indent plusIndent) {
        if (!typeArguments.isEmpty()) {
            token("<");
            builder.open(plusIndent);
            boolean first = true;
            for (Type typeArgument : typeArguments) {
                if (!first) {
                    token(",");
                    builder.breakToFill(" ");
                }
                typeArgument.accept(this);
                first = false;
            }
            builder.close();
            token(">");
        }
    }

    /**
     * Add arguments to a method invocation, etc. The arguments indented {@code plusFour}, filled,
     * from the current indent. The arguments may be output two at a time if they seem to be arguments
     * to a map constructor, etc.
     * @param arguments the arguments
     * @param plusIndent the extra indent for the arguments
     */
    void addArguments(List<Expression> arguments, Indent plusIndent) {
        builder.open(plusIndent);
        token("(");
        if (!arguments.isEmpty()) {
            if (argumentsArePaired(arguments)) {
                builder.forcedBreak();
                builder.open(ZERO);
                boolean first = true;
                for (int i = 0; i < arguments.size() - 1; i += 2) {
                    Expression argument0 = arguments.get(i);
                    Expression argument1 = arguments.get(i + 1);
                    if (!first) {
                        token(",");
                        builder.forcedBreak();
                    }
                    builder.open(plusFour);
                    argument0.accept(this);
                    token(",");
                    builder.breakOp(" ");
                    argument1.accept(this);
                    builder.close();
                    first = false;
                }
                builder.close();
            } else {
                builder.breakOp();
                builder.open(ZERO, maxLinesFilledForItems(arguments, MAX_LINES_FOR_ARGUMENTS));
                boolean first = true;
                for (Expression argument : arguments) {
                    if (!first) {
                        token(",");
                        builder.breakToFill(" ");
                    }
                    argument.accept(this);
                    first = false;
                }
                builder.close();
            }
        }
        token(")");
        builder.close();
    }

    private boolean argumentsArePaired(List<Expression> arguments) {
        int n = arguments.size();
        if (n % 2 != 0 || n < 4) {
            return false;
        }
        List<Expression> firsts = new ArrayList<>();
        List<Expression> seconds = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            (i % 2 == 0 ? firsts : seconds).add(arguments.get(i));
        }
        Integer firstColumn0 = actualColumn(firsts.get(0));
        if (firstColumn0 == null) {
            return false;
        }
        for (int i = 1; i < n / 2; i++) {
            Integer firstColumnI = actualColumn(firsts.get(i));
            if (!firstColumn0.equals(firstColumnI)) {
                return false;
            }
        }
        for (int i = 0; i < n / 2; i++) {
            Integer secondColumnI = actualColumn(seconds.get(i));
            if (secondColumnI == null) {
                return false;
            }
            if (!(firstColumn0 < secondColumnI)) {
                return false;
            }
        }
        return expressionsAreParallel(firsts, n / 2) && expressionsAreParallel(seconds, n / 4 + 1);
    }

    private Integer actualColumn(Expression expression) {
        Map<Integer, Integer> positionToColumnMap = builder.getInput().getPositionToColumnMap();
        return positionToColumnMap.get(builder.actualStartColumn(expression.getStartPosition()));
    }

    private static boolean expressionsAreParallel(List<Expression> expressions, int atLeastM) {
        Multimap<Integer, Expression> map = HashMultimap.create();
        for (Expression expression : expressions) {
            map.put(expression.getNodeType(), expression);
        }
        for (Integer nodeType : map.keys()) {
            if (map.get(nodeType).size() >= atLeastM) {
                return true;
            }
        }
        return false;
    }

    /** Visitor method for {@link QualifiedName}s. */
    private void visitQualifiedName(QualifiedName node0, BreakOrNot breaks) {
        QualifiedName node = node0;
        sync(node);

        // defer to visitDot for builder-style wrapping if breaks are enabled
        if (breaks.isYes()) {
            visitDot(node0);
            return;
        }

        // Collapse chains of "." operators.
        ArrayDeque<SimpleName> stack = new ArrayDeque<>();
        Name qualifier;
        while (true) {
            stack.addFirst(node.getName());
            qualifier = node.getQualifier();
            if (qualifier == null || qualifier.getNodeType() != ASTNode.QUALIFIED_NAME) {
                break;
            }
            node = (QualifiedName) qualifier;
        }
        if (qualifier != null) {
            visitName(qualifier, breaks);
            token(".");
        }
        boolean needDot = false;
        for (SimpleName name : stack) {
            if (needDot) {
                token(".");
            }
            visit(name);
            needDot = true;
        }
    }

    // General helper functions.

    // TODO(jdd): Mention annotation declarations.

    /**
     * Declare one variable or variable-like thing.
     * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL}
     * @param modifiers the {@link IExtendedModifier}s, including annotations
     * @param type the {@link Type}
     * @param isVarargs is the type varargs?
     * @param varargsAnnotations annotations on the varargs
     * @param name the name
     * @param op if non-empty, tokens to follow the name
     * @param extraDimensions the extra dimensions
     * @param equals "=" or equivalent
     * @param initializer the (optional) initializer
     * @param trailing the (optional) trailing token, e.g. ';'
     * @param receiverParameter whether this is a receiver parameter
     */
    void declareOne(ASTNode node, Direction annotationsDirection, List<IExtendedModifier> modifiers, Type type,
            VarArgsOrNot isVarargs, List<Annotation> varargsAnnotations, SimpleName name, String op,
            List<Dimension> extraDimensions, String equals, Optional<Expression> initializer,
            Optional<String> trailing, ReceiverParameter receiverParameter) {

        BreakTag typeBreak = genSym();
        BreakTag verticalAnnotationBreak = genSym();

        EnumSet<DeclarationPosition> position = DeclarationPosition.getPositionInParent(node);

        // If the node is a field declaration, try to output any declaration
        // annotations in-line. If the entire declaration doesn't fit on a single
        // line, fall back to one-per-line.
        boolean isField = node.getNodeType() == ASTNode.FIELD_DECLARATION;

        if (isField) {
            if (!position.contains(DeclarationPosition.FIRST)) {
                builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak));
            } else {
                builder.blankLineWanted(BlankLineWanted.PRESERVE);
            }
        }

        builder.open(ZERO);
        {
            visitAndBreakModifiers(modifiers, annotationsDirection, Optional.of(verticalAnnotationBreak));
            builder.open(plusFour);
            {
                builder.open(ZERO);
                {
                    builder.open(ZERO);
                    {
                        type.accept(this);
                        if (isVarargs.isYes()) {
                            visitAnnotations(varargsAnnotations, BreakOrNot.YES, BreakOrNot.YES);
                            builder.op("...");
                        }
                    }
                    builder.close();

                    builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(typeBreak));

                    // conditionally ident the name and initializer +4 if the type spans
                    // multiple lines
                    builder.open(Indent.If.make(typeBreak, plusFour, ZERO));
                    if (receiverParameter.isYes()) {
                        if (name != null) {
                            visit(name);
                            token(".");
                        }
                        token("this");
                    } else {
                        visit(name);
                    }
                    builder.op(op);
                    extraDimensions(initializer.isPresent() ? plusFour : ZERO, extraDimensions);
                }
                builder.close();
            }
            builder.close();

            if (initializer.isPresent()) {
                builder.space();
                token(equals);
                if (initializer.get().getNodeType() == ASTNode.ARRAY_INITIALIZER) {
                    builder.open(minusFour);
                    {
                        builder.space();
                        initializer.get().accept(this);
                    }
                    builder.close();
                } else {
                    builder.open(Indent.If.make(typeBreak, plusFour, ZERO));
                    {
                        builder.breakToFill(" ");
                        initializer.get().accept(this);
                    }
                    builder.close();
                }
            }
            // end of conditional name and initializer indent
            builder.close();

            if (trailing.isPresent()) {
                builder.guessToken(trailing.get());
            }
        }
        builder.close();

        if (isField) {
            if (!position.contains(DeclarationPosition.LAST)) {
                builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak));
            } else {
                builder.blankLineWanted(BlankLineWanted.NO);
            }
        }
    }

    /**
     * Declare multiple variables or variable-like things.
     * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL}
     * @param modifiers the {@link IExtendedModifier}s, including annotations
     * @param type the {@link Type}s
     * @param fragments the {@link VariableDeclarationFragment}s
     */
    private void declareMany(Direction annotationsDirection, List<IExtendedModifier> modifiers, Type type,
            List<VariableDeclarationFragment> fragments) {
        builder.open(ZERO);
        visitAndBreakModifiers(modifiers, annotationsDirection, Optional.<BreakTag>absent());
        builder.open(plusFour);
        type.accept(this);
        // TODO(jdd): Open another time?
        boolean first = true;
        for (VariableDeclarationFragment fragment : fragments) {
            if (!first) {
                token(",");
            }
            builder.breakOp(" ");
            builder.open(ZERO);
            visit(fragment.getName());
            Expression initializer = fragment.getInitializer();
            extraDimensions(initializer != null ? plusEight : plusFour, fragment.extraDimensions());
            if (initializer != null) {
                builder.space();
                token("=");
                if (initializer.getNodeType() == ASTNode.ARRAY_INITIALIZER) {
                    // TODO(jdd): Check on this.
                    builder.close();
                    builder.open(ZERO);
                    builder.space();
                    initializer.accept(this);
                } else {
                    builder.open(plusFour);
                    builder.breakOp(" ");
                    initializer.accept(this);
                    builder.close();
                }
            }
            builder.close();
            first = false;
        }
        builder.close();
        token(";");
        builder.close();
    }

    /**
     * Add a declaration.
     * @param modifiers the {@link IExtendedModifier}s, including annotations
     * @param type the {@link Type}s
     * @param fragments the {@link VariableDeclarationFragment}s
     * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL}
     */
    void addDeclaration(ASTNode node, List<IExtendedModifier> modifiers, Type type,
            List<VariableDeclarationFragment> fragments, Direction annotationsDirection) {
        if (fragments.size() == 1) {
            VariableDeclarationFragment fragment = fragments.get(0);
            declareOne(node, annotationsDirection, modifiers, type, VarArgsOrNot.NO, ImmutableList.<Annotation>of(),
                    fragment.getName(), "", fragment.extraDimensions(), "=",
                    Optional.fromNullable(fragment.getInitializer()), Optional.of(";"), ReceiverParameter.NO);
        } else {
            declareMany(annotationsDirection, modifiers, type, fragments);
        }
    }

    // TODO(jdd): State precondition (and check callers).
    /**
     * Emit extra dimensions (if any).
     * @param plusIndent the extra indentation for the extra dimensions
     * @param extraDimensions the extra {@link Dimension}s
     */
    void extraDimensions(Indent plusIndent, List<Dimension> extraDimensions) {
        builder.open(plusIndent);
        for (Dimension extraDimension : extraDimensions) {
            builder.breakToFill(extraDimension.annotations().isEmpty() ? "" : " ");
            visit(extraDimension);
        }
        builder.close();
    }

    // TODO(jdd): Static checks?
    /**
     * Add a list of {@link BodyDeclaration}s
     * @param bodyDeclarations the {@link BodyDeclaration}s
     * @param braces whether to include braces in the output
     * @param first0 is the first {@link BodyDeclaration} the first to be output?
     */
    void addBodyDeclarations(List<BodyDeclaration> bodyDeclarations, BracesOrNot braces,
            FirstDeclarationsOrNot first0) {
        if (bodyDeclarations.isEmpty()) {
            if (braces.isYes()) {
                builder.space();
                tokenBreakTrailingComment("{", plusTwo);
                builder.blankLineWanted(BlankLineWanted.NO);
                builder.open(ZERO);
                token("}", plusTwo);
                builder.close();
            }
        } else {
            if (braces.isYes()) {
                builder.space();
                tokenBreakTrailingComment("{", plusTwo);
                builder.open(ZERO);
            }
            builder.open(plusTwo);
            boolean first = first0.isYes();
            boolean lastOneGotBlankLineBefore = false;
            for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
                dropEmptyDeclarations();
                builder.forcedBreak();
                boolean thisOneGetsBlankLineBefore = bodyDeclaration.getNodeType() != ASTNode.FIELD_DECLARATION
                        || hasJavaDoc(bodyDeclaration);
                if (first) {
                    builder.blankLineWanted(BlankLineWanted.PRESERVE);
                } else if (!first && (thisOneGetsBlankLineBefore || lastOneGotBlankLineBefore)) {
                    builder.blankLineWanted(BlankLineWanted.YES);
                }
                markForPartialFormat();
                bodyDeclaration.accept(this);
                first = false;
                lastOneGotBlankLineBefore = thisOneGetsBlankLineBefore;
            }
            builder.close();
            builder.forcedBreak();
            markForPartialFormat();
            if (braces.isYes()) {
                dropEmptyDeclarations();
                builder.blankLineWanted(BlankLineWanted.NO);
                token("}", plusTwo);
                builder.close();
            }
        }
    }

    // Use Eclipse token ID instead of position?
    /** Does this {@link BodyDeclaration} have JavaDoc preceding it? */
    private boolean hasJavaDoc(BodyDeclaration bodyDeclaration) {
        int position = bodyDeclaration.getStartPosition();
        Map.Entry<Integer, ? extends Input.Token> entry = builder.getInput().getPositionTokenMap()
                .ceilingEntry(position);
        if (entry != null) {
            for (Input.Tok tok : entry.getValue().getToksBefore()) {
                if (tok.getText().startsWith("/**")) {
                    return true;
                }
            }
        }
        return false;
    }

    private static Optional<? extends Input.Token> getNextToken(Input input, int position) {
        Map.Entry<Integer, ? extends Input.Token> ceilingEntry = input.getPositionTokenMap().ceilingEntry(position);
        return ceilingEntry == null ? Optional.<JavaInput.Token>absent() : Optional.of(ceilingEntry.getValue());
    }

    /**
     * Does this list of {@link ASTNode}s ends with the specified token?
     * @param input the {@link Input}
     * @param nodes list of {@link ASTNode}s
     * @return whether the list has an extra trailing comma
     */
    private static boolean hasTrailingToken(Input input, List<ASTNode> nodes, String token) {
        if (nodes.isEmpty()) {
            return false;
        }
        ASTNode lastNode = nodes.get(nodes.size() - 1);
        Optional<? extends Input.Token> nextToken = getNextToken(input,
                lastNode.getStartPosition() + lastNode.getLength());
        return nextToken.isPresent() && nextToken.get().getTok().getText().equals(token);
    }

    // TODO(jdd): Use constants for limits?

    /**
     * Can a local with a set of modifiers be declared with horizontal annotations? This is currently
     * true if there is at most one marker annotation, and no others.
     * @param modifiers the list of {@link IExtendedModifier}s
     * @return whether the local can be declared with horizontal annotations
     */
    private static Direction canLocalHaveHorizontalAnnotations(List<IExtendedModifier> modifiers) {
        int normalAnnotations = 0;
        int markerAnnotations = 0;
        int singleMemberAnnotations = 0;
        for (IExtendedModifier modifier : modifiers) {
            switch (((ASTNode) modifier).getNodeType()) {
            case ASTNode.NORMAL_ANNOTATION:
                ++normalAnnotations;
                break;
            case ASTNode.MARKER_ANNOTATION:
                ++markerAnnotations;
                break;
            case ASTNode.SINGLE_MEMBER_ANNOTATION:
                ++singleMemberAnnotations;
                break;
            default:
                break;
            }
        }
        return normalAnnotations == 0 && markerAnnotations <= 1 && singleMemberAnnotations == 0
                ? Direction.HORIZONTAL
                : Direction.VERTICAL;
    }

    /**
     * Should a field with a set of modifiers be declared with horizontal annotations?
     * This is currently true if all annotations are marker annotations.
     *
     * @param modifiers the list of {@link IExtendedModifier}s
     * @return whether the local can be declared with horizontal annotations
     */
    private static Direction fieldAnnotationDirection(List<IExtendedModifier> modifiers) {
        for (IExtendedModifier modifier : modifiers) {
            if (modifier.isAnnotation() && ((ASTNode) modifier).getNodeType() != ASTNode.MARKER_ANNOTATION) {
                return Direction.VERTICAL;
            }
        }
        return Direction.HORIZONTAL;
    }

    // TODO(jdd): Do more?
    /**
     * Emit a {@link Doc.Token}.
     * @param token the {@link String} to wrap in a {@link Doc.Token}
     */
    final void token(String token) {
        builder.token(token, Doc.Token.RealOrImaginary.REAL, ZERO, Optional.<Indent>absent());
    }

    /**
     * Emit a {@link Doc.Token}.
     * @param token the {@link String} to wrap in a {@link Doc.Token}
     * @param plusIndentCommentsBefore extra indent for comments before this token
     */
    final void token(String token, Indent plusIndentCommentsBefore) {
        builder.token(token, Doc.Token.RealOrImaginary.REAL, plusIndentCommentsBefore, Optional.<Indent>absent());
    }

    /**
     * Emit a {@link Doc.Token}, and breaks and indents trailing javadoc or block comments.
     */
    final void tokenBreakTrailingComment(String token, Indent breakAndIndentTrailingComment) {
        builder.token(token, Doc.Token.RealOrImaginary.REAL, ZERO,
                Optional.<Indent>of(breakAndIndentTrailingComment));
    }

    private void markForPartialFormat() {
        if (!inExpression()) {
            builder.markForPartialFormat();
        }
    }

    /**
     * Sync to position in the input. If we've skipped outputting any tokens that were present in the
     * input tokens, output them here and complain.
     * @param node the ASTNode holding the input position
     */
    final void sync(ASTNode node) {
        builder.sync(node.getStartPosition());
    }

    final BreakTag genSym() {
        return new BreakTag();
    }

    @Override
    public final String toString() {
        return MoreObjects.toStringHelper(this).add("builder", builder).toString();
    }
}