Java tutorial
/* * 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 static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.googlejavaformat.Doc.FillMode.INDEPENDENT; import static com.google.googlejavaformat.Doc.FillMode.UNIFIED; import static com.google.googlejavaformat.Indent.If.make; import static com.google.googlejavaformat.OpsBuilder.BlankLineWanted.PRESERVE; import static com.google.googlejavaformat.OpsBuilder.BlankLineWanted.YES; import static com.google.googlejavaformat.java.Trees.getEndPosition; import static com.google.googlejavaformat.java.Trees.getLength; import static com.google.googlejavaformat.java.Trees.getMethodName; import static com.google.googlejavaformat.java.Trees.getSourceForNode; import static com.google.googlejavaformat.java.Trees.getStartPosition; import static com.google.googlejavaformat.java.Trees.operatorName; import static com.google.googlejavaformat.java.Trees.precedence; import static com.google.googlejavaformat.java.Trees.skipParen; import static java.util.stream.Collectors.toList; import static org.openjdk.source.tree.Tree.Kind.ANNOTATION; import static org.openjdk.source.tree.Tree.Kind.ARRAY_ACCESS; import static org.openjdk.source.tree.Tree.Kind.ASSIGNMENT; import static org.openjdk.source.tree.Tree.Kind.BLOCK; import static org.openjdk.source.tree.Tree.Kind.EXTENDS_WILDCARD; import static org.openjdk.source.tree.Tree.Kind.IF; import static org.openjdk.source.tree.Tree.Kind.METHOD_INVOCATION; import static org.openjdk.source.tree.Tree.Kind.NEW_ARRAY; import static org.openjdk.source.tree.Tree.Kind.NEW_CLASS; import static org.openjdk.source.tree.Tree.Kind.STRING_LITERAL; import static org.openjdk.source.tree.Tree.Kind.UNION_TYPE; import static org.openjdk.source.tree.Tree.Kind.VARIABLE; import com.google.common.base.MoreObjects; import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.base.Verify; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Multiset; import com.google.common.collect.PeekingIterator; import com.google.common.collect.Streams; import com.google.googlejavaformat.CloseOp; import com.google.googlejavaformat.Doc; import com.google.googlejavaformat.Doc.FillMode; import com.google.googlejavaformat.FormattingError; 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 com.google.googlejavaformat.java.DimensionHelpers.SortedDims; import com.google.googlejavaformat.java.DimensionHelpers.TypeWithDims; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Stream; import javax.annotation.Nullable; import org.openjdk.javax.lang.model.element.Name; import org.openjdk.source.tree.AnnotatedTypeTree; import org.openjdk.source.tree.AnnotationTree; import org.openjdk.source.tree.ArrayAccessTree; import org.openjdk.source.tree.ArrayTypeTree; import org.openjdk.source.tree.AssertTree; import org.openjdk.source.tree.AssignmentTree; import org.openjdk.source.tree.BinaryTree; import org.openjdk.source.tree.BlockTree; import org.openjdk.source.tree.BreakTree; import org.openjdk.source.tree.CaseTree; import org.openjdk.source.tree.CatchTree; import org.openjdk.source.tree.ClassTree; import org.openjdk.source.tree.CompilationUnitTree; import org.openjdk.source.tree.CompoundAssignmentTree; import org.openjdk.source.tree.ConditionalExpressionTree; import org.openjdk.source.tree.ContinueTree; import org.openjdk.source.tree.DirectiveTree; import org.openjdk.source.tree.DoWhileLoopTree; import org.openjdk.source.tree.EmptyStatementTree; import org.openjdk.source.tree.EnhancedForLoopTree; import org.openjdk.source.tree.ExportsTree; import org.openjdk.source.tree.ExpressionStatementTree; import org.openjdk.source.tree.ExpressionTree; import org.openjdk.source.tree.ForLoopTree; import org.openjdk.source.tree.IdentifierTree; import org.openjdk.source.tree.IfTree; import org.openjdk.source.tree.ImportTree; import org.openjdk.source.tree.InstanceOfTree; import org.openjdk.source.tree.IntersectionTypeTree; import org.openjdk.source.tree.LabeledStatementTree; import org.openjdk.source.tree.LambdaExpressionTree; import org.openjdk.source.tree.LiteralTree; import org.openjdk.source.tree.MemberReferenceTree; import org.openjdk.source.tree.MemberSelectTree; import org.openjdk.source.tree.MethodInvocationTree; import org.openjdk.source.tree.MethodTree; import org.openjdk.source.tree.ModifiersTree; import org.openjdk.source.tree.ModuleTree; import org.openjdk.source.tree.NewArrayTree; import org.openjdk.source.tree.NewClassTree; import org.openjdk.source.tree.OpensTree; import org.openjdk.source.tree.ParameterizedTypeTree; import org.openjdk.source.tree.ParenthesizedTree; import org.openjdk.source.tree.PrimitiveTypeTree; import org.openjdk.source.tree.ProvidesTree; import org.openjdk.source.tree.RequiresTree; import org.openjdk.source.tree.ReturnTree; import org.openjdk.source.tree.StatementTree; import org.openjdk.source.tree.SwitchTree; import org.openjdk.source.tree.SynchronizedTree; import org.openjdk.source.tree.ThrowTree; import org.openjdk.source.tree.Tree; import org.openjdk.source.tree.TryTree; import org.openjdk.source.tree.TypeCastTree; import org.openjdk.source.tree.TypeParameterTree; import org.openjdk.source.tree.UnaryTree; import org.openjdk.source.tree.UnionTypeTree; import org.openjdk.source.tree.UsesTree; import org.openjdk.source.tree.VariableTree; import org.openjdk.source.tree.WhileLoopTree; import org.openjdk.source.tree.WildcardTree; import org.openjdk.source.util.TreePath; import org.openjdk.source.util.TreePathScanner; import org.openjdk.tools.javac.code.Flags; import org.openjdk.tools.javac.tree.JCTree; import org.openjdk.tools.javac.tree.TreeScanner; /** * An AST visitor that builds a stream of {@link Op}s to format from the given {@link * CompilationUnitTree}. */ public final class JavaInputAstVisitor extends TreePathScanner<Void, Void> { /** 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; } static VarArgsOrNot fromVariable(VariableTree node) { return valueOf((((JCTree.JCVariableDecl) node).mods.flags & Flags.VARARGS) == Flags.VARARGS); } } /** 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; } } 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 static final ImmutableList<Op> breakList(Optional<BreakTag> breakTag) { return ImmutableList.of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag)); } private static final ImmutableList<Op> breakFillList(Optional<BreakTag> breakTag) { return ImmutableList.of(OpenOp.make(ZERO), Doc.Break.make(Doc.FillMode.INDEPENDENT, " ", ZERO, breakTag), CloseOp.make()); } private static final ImmutableList<Op> forceBreakList(Optional<BreakTag> breakTag) { return ImmutableList.of(Doc.Break.make(FillMode.FORCED, "", Indent.Const.ZERO, breakTag)); } private static final ImmutableList<Op> EMPTY_LIST = ImmutableList.of(); /** * 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; /** * 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); } /** A record of whether we have visited into an expression. */ private final Deque<Boolean> inExpression = new ArrayDeque<>(ImmutableList.of(false)); private boolean inExpression() { return inExpression.peekLast(); } @Override public Void scan(Tree tree, Void unused) { inExpression.addLast(tree instanceof ExpressionTree || inExpression.peekLast()); int previous = builder.depth(); try { super.scan(tree, null); } catch (FormattingError e) { throw e; } catch (Throwable t) { throw new FormattingError(builder.diagnostic(Throwables.getStackTraceAsString(t))); } finally { inExpression.removeLast(); } builder.checkClosed(previous); return null; } @Override public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { boolean first = true; if (node.getPackageName() != null) { markForPartialFormat(); visitPackage(node.getPackageName(), node.getPackageAnnotations()); builder.forcedBreak(); first = false; } dropEmptyDeclarations(); if (!node.getImports().isEmpty()) { if (!first) { builder.blankLineWanted(BlankLineWanted.YES); } for (ImportTree importDeclaration : node.getImports()) { markForPartialFormat(); builder.blankLineWanted(PRESERVE); scan(importDeclaration, null); builder.forcedBreak(); } first = false; } dropEmptyDeclarations(); for (Tree type : node.getTypeDecls()) { if (type.getKind() == Tree.Kind.IMPORT) { // javac treats extra semicolons in the import list as type declarations // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed continue; } if (!first) { builder.blankLineWanted(BlankLineWanted.YES); } markForPartialFormat(); scan(type, null); builder.forcedBreak(); first = false; dropEmptyDeclarations(); } // set a partial format marker at EOF to make sure we can format the entire file markForPartialFormat(); return null; } /** Skips over extra semi-colons at the top-level, or in a class member declaration lists. */ private void dropEmptyDeclarations() { if (builder.peekToken().equals(Optional.of(";"))) { while (builder.peekToken().equals(Optional.of(";"))) { builder.forcedBreak(); markForPartialFormat(); token(";"); } } } @Override public Void visitClass(ClassTree tree, Void unused) { switch (tree.getKind()) { case ANNOTATION_TYPE: visitAnnotationType(tree); break; case CLASS: case INTERFACE: visitClassDeclaration(tree); break; case ENUM: visitEnumDeclaration(tree); break; default: throw new AssertionError(tree.getKind()); } return null; } public void visitAnnotationType(ClassTree node) { sync(node); builder.open(ZERO); visitAndBreakModifiers(node.getModifiers(), Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty()); builder.open(ZERO); token("@"); token("interface"); builder.breakOp(" "); visit(node.getSimpleName()); builder.close(); builder.close(); if (node.getMembers() == null) { builder.open(plusFour); token(";"); builder.close(); } else { addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); } builder.guessToken(";"); } @Override public Void visitArrayAccess(ArrayAccessTree node, Void unused) { sync(node); visitDot(node); return null; } @Override public Void visitNewArray(NewArrayTree node, Void unused) { if (node.getType() != null) { builder.open(plusFour); token("new"); builder.space(); TypeWithDims extractedDims = DimensionHelpers.extractDims(node.getType(), SortedDims.YES); Tree base = extractedDims.node; Deque<ExpressionTree> dimExpressions = new ArrayDeque<>(node.getDimensions()); Deque<List<AnnotationTree>> annotations = new ArrayDeque<>(); annotations.add(ImmutableList.copyOf(node.getAnnotations())); annotations.addAll((List<List<AnnotationTree>>) node.getDimAnnotations()); annotations.addAll(extractedDims.dims); scan(base, null); builder.open(ZERO); maybeAddDims(dimExpressions, annotations); builder.close(); builder.close(); } if (node.getInitializers() != null) { if (node.getType() != null) { builder.space(); } visitArrayInitializer(node.getInitializers()); } return null; } public boolean visitArrayInitializer(List<? extends ExpressionTree> expressions) { int cols; if (expressions.isEmpty()) { tokenBreakTrailingComment("{", plusTwo); if (builder.peekToken().equals(Optional.of(","))) { token(","); } token("}", plusTwo); } else if ((cols = argumentsAreTabular(expressions)) != -1) { builder.open(plusTwo); token("{"); builder.forcedBreak(); boolean first = true; for (Iterable<? extends ExpressionTree> row : Iterables.partition(expressions, cols)) { if (!first) { builder.forcedBreak(); } builder.open(row.iterator().next().getKind() == NEW_ARRAY || cols == 1 ? ZERO : plusFour); boolean firstInRow = true; for (ExpressionTree item : row) { if (!firstInRow) { token(","); builder.breakToFill(" "); } scan(item, null); firstInRow = false; } builder.guessToken(","); builder.close(); first = false; } builder.breakOp(minusTwo); builder.close(); token("}", plusTwo); } else { // Special-case the formatting of array initializers inside annotations // to more eagerly use a one-per-line layout. boolean inMemberValuePair = false; // walk up past the enclosing NewArrayTree (and maybe an enclosing AssignmentTree) TreePath path = getCurrentPath(); for (int i = 0; i < 2; i++) { if (path == null) { break; } if (path.getLeaf().getKind() == ANNOTATION) { inMemberValuePair = true; break; } path = path.getParentPath(); } boolean shortItems = hasOnlyShortItems(expressions); boolean allowFilledElementsOnOwnLine = shortItems || !inMemberValuePair; builder.open(plusTwo); tokenBreakTrailingComment("{", plusTwo); boolean hasTrailingComma = hasTrailingToken(builder.getInput(), expressions, ","); builder.breakOp(hasTrailingComma ? FillMode.FORCED : FillMode.UNIFIED, "", ZERO); if (allowFilledElementsOnOwnLine) { builder.open(ZERO); } boolean first = true; FillMode fillMode = shortItems ? FillMode.INDEPENDENT : FillMode.UNIFIED; for (ExpressionTree expression : expressions) { if (!first) { token(","); builder.breakOp(fillMode, " ", ZERO); } scan(expression, null); first = false; } builder.guessToken(","); if (allowFilledElementsOnOwnLine) { builder.close(); } builder.breakOp(minusTwo); builder.close(); token("}", plusTwo); } return false; } private boolean hasOnlyShortItems(List<? extends ExpressionTree> expressions) { for (ExpressionTree expression : expressions) { int startPosition = getStartPosition(expression); if (builder.actualSize(startPosition, getEndPosition(expression, getCurrentPath()) - startPosition) >= MAX_ITEM_LENGTH_FOR_FILLING) { return false; } } return true; } @Override public Void visitArrayType(ArrayTypeTree node, Void unused) { sync(node); visitAnnotatedArrayType(node); return null; } private void visitAnnotatedArrayType(Tree node) { TypeWithDims extractedDims = DimensionHelpers.extractDims(node, SortedDims.YES); builder.open(plusFour); scan(extractedDims.node, null); Deque<List<AnnotationTree>> dims = new ArrayDeque<>(extractedDims.dims); maybeAddDims(dims); Verify.verify(dims.isEmpty()); builder.close(); } @Override public Void visitAssert(AssertTree node, Void unused) { sync(node); builder.open(ZERO); token("assert"); builder.space(); builder.open(node.getDetail() == null ? ZERO : plusFour); scan(node.getCondition(), null); if (node.getDetail() != null) { builder.breakOp(" "); token(":"); builder.space(); scan(node.getDetail(), null); } builder.close(); builder.close(); token(";"); return null; } @Override public Void visitAssignment(AssignmentTree node, Void unused) { sync(node); builder.open(plusFour); scan(node.getVariable(), null); builder.space(); splitToken(operatorName(node)); builder.breakOp(" "); scan(node.getExpression(), null); builder.close(); return null; } @Override public Void visitBlock(BlockTree node, Void unused) { visitBlock(node, CollapseEmptyOrNot.NO, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); return null; } @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, Void unused) { sync(node); builder.open(plusFour); scan(node.getVariable(), null); builder.space(); splitToken(operatorName(node)); builder.breakOp(" "); scan(node.getExpression(), null); builder.close(); return null; } @Override public Void visitBreak(BreakTree node, Void unused) { sync(node); builder.open(plusFour); token("break"); if (node.getLabel() != null) { builder.breakOp(" "); visit(node.getLabel()); } builder.close(); token(";"); return null; } @Override public Void visitTypeCast(TypeCastTree node, Void unused) { sync(node); builder.open(plusFour); token("("); scan(node.getType(), null); token(")"); builder.breakOp(" "); scan(node.getExpression(), null); builder.close(); return null; } @Override public Void visitNewClass(NewClassTree node, Void unused) { sync(node); builder.open(ZERO); if (node.getEnclosingExpression() != null) { scan(node.getEnclosingExpression(), null); builder.breakOp(); token("."); } token("new"); builder.space(); addTypeArguments(node.getTypeArguments(), plusFour); if (node.getClassBody() != null) { builder.addAll( visitModifiers(node.getClassBody().getModifiers(), Direction.HORIZONTAL, Optional.empty())); } scan(node.getIdentifier(), null); addArguments(node.getArguments(), plusFour); builder.close(); if (node.getClassBody() != null) { addBodyDeclarations(node.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); } return null; } @Override public Void visitConditionalExpression(ConditionalExpressionTree node, Void unused) { sync(node); builder.open(plusFour); scan(node.getCondition(), null); builder.breakOp(" "); token("?"); builder.space(); scan(node.getTrueExpression(), null); builder.breakOp(" "); token(":"); builder.space(); scan(node.getFalseExpression(), null); builder.close(); return null; } @Override public Void visitContinue(ContinueTree node, Void unused) { sync(node); builder.open(plusFour); token("continue"); if (node.getLabel() != null) { builder.breakOp(" "); visit(node.getLabel()); } token(";"); builder.close(); return null; } @Override public Void visitDoWhileLoop(DoWhileLoopTree node, Void unused) { sync(node); token("do"); visitStatement(node.getStatement(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES, AllowTrailingBlankLine.YES); if (node.getStatement().getKind() == BLOCK) { builder.space(); } else { builder.breakOp(" "); } token("while"); builder.space(); token("("); scan(skipParen(node.getCondition()), null); token(")"); token(";"); return null; } @Override public Void visitEmptyStatement(EmptyStatementTree node, Void unused) { sync(node); dropEmptyDeclarations(); return null; } @Override public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void unused) { sync(node); builder.open(ZERO); token("for"); builder.space(); token("("); builder.open(ZERO); visitToDeclare(DeclarationKind.NONE, Direction.HORIZONTAL, node.getVariable(), Optional.of(node.getExpression()), ":", /* trailing= */ Optional.empty()); builder.close(); token(")"); builder.close(); visitStatement(node.getStatement(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES, AllowTrailingBlankLine.NO); return null; } private void visitEnumConstantDeclaration(VariableTree enumConstant) { for (AnnotationTree annotation : enumConstant.getModifiers().getAnnotations()) { scan(annotation, null); builder.forcedBreak(); } visit(enumConstant.getName()); NewClassTree init = ((NewClassTree) enumConstant.getInitializer()); if (init.getArguments().isEmpty()) { builder.guessToken("("); builder.guessToken(")"); } else { addArguments(init.getArguments(), plusFour); } if (init.getClassBody() != null) { addBodyDeclarations(init.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); } } public boolean visitEnumDeclaration(ClassTree node) { sync(node); builder.open(ZERO); visitAndBreakModifiers(node.getModifiers(), Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty()); builder.open(plusFour); token("enum"); builder.breakOp(" "); visit(node.getSimpleName()); builder.close(); builder.close(); if (!node.getImplementsClause().isEmpty()) { builder.open(plusFour); builder.breakOp(" "); builder.open(plusFour); token("implements"); builder.breakOp(" "); builder.open(ZERO); boolean first = true; for (Tree superInterfaceType : node.getImplementsClause()) { if (!first) { token(","); builder.breakToFill(" "); } scan(superInterfaceType, null); first = false; } builder.close(); builder.close(); builder.close(); } builder.space(); tokenBreakTrailingComment("{", plusTwo); ArrayList<VariableTree> enumConstants = new ArrayList<>(); ArrayList<Tree> members = new ArrayList<>(); for (Tree member : node.getMembers()) { if (member instanceof JCTree.JCVariableDecl) { JCTree.JCVariableDecl variableDecl = (JCTree.JCVariableDecl) member; if ((variableDecl.mods.flags & Flags.ENUM) == Flags.ENUM) { enumConstants.add(variableDecl); continue; } } members.add(member); } if (enumConstants.isEmpty() && members.isEmpty()) { if (builder.peekToken().equals(Optional.of(";"))) { builder.open(plusTwo); builder.forcedBreak(); token(";"); builder.forcedBreak(); dropEmptyDeclarations(); builder.close(); builder.open(ZERO); builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.NO); token("}", plusTwo); builder.close(); } else { 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 (VariableTree enumConstant : enumConstants) { if (!first) { token(","); builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.PRESERVE); } markForPartialFormat(); visitEnumConstantDeclaration(enumConstant); first = false; } if (builder.peekToken().orElse("").equals(",")) { token(","); builder.forcedBreak(); // The ";" goes on its own line. } builder.close(); builder.close(); if (builder.peekToken().equals(Optional.of(";"))) { builder.open(plusTwo); token(";"); builder.forcedBreak(); dropEmptyDeclarations(); builder.close(); } builder.open(ZERO); addBodyDeclarations(members, BracesOrNot.NO, FirstDeclarationsOrNot.NO); builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.NO); token("}", plusTwo); builder.close(); } builder.guessToken(";"); return false; } @Override public Void visitMemberReference(MemberReferenceTree node, Void unused) { sync(node); builder.open(plusFour); scan(node.getQualifierExpression(), null); builder.breakOp(); builder.op("::"); addTypeArguments(node.getTypeArguments(), plusFour); switch (node.getMode()) { case INVOKE: visit(node.getName()); break; case NEW: token("new"); break; default: throw new AssertionError(node.getMode()); } builder.close(); return null; } @Override public Void visitExpressionStatement(ExpressionStatementTree node, Void unused) { sync(node); scan(node.getExpression(), null); token(";"); return null; } @Override public Void visitVariable(VariableTree node, Void unused) { sync(node); visitVariables(ImmutableList.of(node), DeclarationKind.NONE, fieldAnnotationDirection(node.getModifiers())); return null; } void visitVariables(List<VariableTree> fragments, DeclarationKind declarationKind, Direction annotationDirection) { if (fragments.size() == 1) { VariableTree fragment = fragments.get(0); declareOne(declarationKind, annotationDirection, Optional.of(fragment.getModifiers()), fragment.getType(), /* name= */ fragment.getName(), "", "=", Optional.ofNullable(fragment.getInitializer()), Optional.of(";"), /* receiverExpression= */ Optional.empty(), Optional.ofNullable(variableFragmentDims(true, 0, fragment.getType()))); } else { declareMany(fragments, annotationDirection); } } private TypeWithDims variableFragmentDims(boolean first, int leadingDims, Tree type) { if (type == null) { return null; } if (first) { return DimensionHelpers.extractDims(type, SortedDims.YES); } TypeWithDims dims = DimensionHelpers.extractDims(type, SortedDims.NO); return new TypeWithDims(null, leadingDims > 0 ? dims.dims.subList(0, dims.dims.size() - leadingDims) : dims.dims); } @Override public Void visitForLoop(ForLoopTree node, Void unused) { sync(node); token("for"); builder.space(); token("("); builder.open(plusFour); builder.open(node.getInitializer().size() > 1 && node.getInitializer().get(0).getKind() == Tree.Kind.EXPRESSION_STATEMENT ? plusFour : ZERO); if (!node.getInitializer().isEmpty()) { if (node.getInitializer().get(0).getKind() == VARIABLE) { PeekingIterator<StatementTree> it = Iterators.peekingIterator(node.getInitializer().iterator()); visitVariables(variableFragments(it, it.next()), DeclarationKind.NONE, Direction.HORIZONTAL); } else { boolean first = true; builder.open(ZERO); for (StatementTree t : node.getInitializer()) { if (!first) { token(","); builder.breakOp(" "); } scan(((ExpressionStatementTree) t).getExpression(), null); first = false; } token(";"); builder.close(); } } else { token(";"); } builder.close(); builder.breakOp(" "); if (node.getCondition() != null) { scan(node.getCondition(), null); } token(";"); if (!node.getUpdate().isEmpty()) { builder.breakOp(" "); builder.open(node.getUpdate().size() <= 1 ? ZERO : plusFour); boolean firstUpdater = true; for (ExpressionStatementTree updater : node.getUpdate()) { if (!firstUpdater) { token(","); builder.breakToFill(" "); } scan(updater.getExpression(), null); firstUpdater = false; } builder.guessToken(";"); builder.close(); } else { builder.space(); } builder.close(); token(")"); visitStatement(node.getStatement(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES, AllowTrailingBlankLine.NO); return null; } @Override public Void visitIf(IfTree node, Void unused) { sync(node); // Collapse chains of else-ifs. List<ExpressionTree> expressions = new ArrayList<>(); List<StatementTree> statements = new ArrayList<>(); while (true) { expressions.add(node.getCondition()); statements.add(node.getThenStatement()); if (node.getElseStatement() != null && node.getElseStatement().getKind() == IF) { node = (IfTree) 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("("); scan(skipParen(expressions.get(i)), null); 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).getKind() == 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 null; } @Override public Void visitImport(ImportTree node, Void unused) { sync(node); token("import"); builder.space(); if (node.isStatic()) { token("static"); builder.space(); } visitName(node.getQualifiedIdentifier()); token(";"); // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed dropEmptyDeclarations(); return null; } @Override public Void visitBinary(BinaryTree node, Void unused) { sync(node); /* * Collect together all operators with same precedence to clean up indentation. */ List<ExpressionTree> operands = new ArrayList<>(); List<String> operators = new ArrayList<>(); walkInfix(precedence(node), node, operands, operators); FillMode fillMode = hasOnlyShortItems(operands) ? INDEPENDENT : UNIFIED; builder.open(plusFour); scan(operands.get(0), null); int operatorsN = operators.size(); for (int i = 0; i < operatorsN; i++) { builder.breakOp(fillMode, " ", ZERO); builder.op(operators.get(i)); builder.space(); scan(operands.get(i + 1), null); } builder.close(); return null; } @Override public Void visitInstanceOf(InstanceOfTree node, Void unused) { sync(node); builder.open(plusFour); scan(node.getExpression(), null); builder.breakOp(" "); builder.open(ZERO); token("instanceof"); builder.breakOp(" "); scan(node.getType(), null); builder.close(); builder.close(); return null; } @Override public Void visitIntersectionType(IntersectionTypeTree node, Void unused) { sync(node); builder.open(plusFour); boolean first = true; for (Tree type : node.getBounds()) { if (!first) { builder.breakToFill(" "); token("&"); builder.space(); } scan(type, null); first = false; } builder.close(); return null; } @Override public Void visitLabeledStatement(LabeledStatementTree node, Void unused) { sync(node); builder.open(ZERO); visit(node.getLabel()); token(":"); builder.forcedBreak(); builder.close(); scan(node.getStatement(), null); return null; } @Override public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { sync(node); boolean statementBody = node.getBodyKind() == LambdaExpressionTree.BodyKind.STATEMENT; boolean parens = builder.peekToken().equals(Optional.of("(")); builder.open(parens ? plusFour : ZERO); if (parens) { token("("); } boolean first = true; for (VariableTree parameter : node.getParameters()) { if (!first) { token(","); builder.breakOp(" "); } scan(parameter, null); first = false; } if (parens) { token(")"); } builder.close(); builder.space(); builder.op("->"); builder.open(statementBody ? ZERO : plusFour); if (statementBody) { builder.space(); } else { builder.breakOp(" "); } if (node.getBody().getKind() == Tree.Kind.BLOCK) { visitBlock((BlockTree) node.getBody(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); } else { scan(node.getBody(), null); } builder.close(); return null; } @Override public Void visitAnnotation(AnnotationTree node, Void unused) { sync(node); if (visitSingleMemberAnnotation(node)) { return null; } builder.open(ZERO); token("@"); scan(node.getAnnotationType(), null); if (!node.getArguments().isEmpty()) { builder.open(plusFour); token("("); builder.breakOp(); boolean first = true; // Format the member value pairs one-per-line if any of them are // initialized with arrays. boolean hasArrayInitializer = Iterables.any(node.getArguments(), JavaInputAstVisitor::isArrayValue); for (ExpressionTree argument : node.getArguments()) { if (!first) { token(","); if (hasArrayInitializer) { builder.forcedBreak(); } else { builder.breakOp(" "); } } if (argument instanceof AssignmentTree) { visitAnnotationArgument((AssignmentTree) argument); } else { scan(argument, null); } first = false; } token(")"); builder.close(); builder.close(); return null; } else if (builder.peekToken().equals(Optional.of("("))) { token("("); token(")"); } builder.close(); return null; } private static boolean isArrayValue(ExpressionTree argument) { if (!(argument instanceof AssignmentTree)) { return false; } ExpressionTree expression = ((AssignmentTree) argument).getExpression(); return expression instanceof NewArrayTree && ((NewArrayTree) expression).getType() == null; } public void visitAnnotationArgument(AssignmentTree node) { boolean isArrayInitializer = node.getExpression().getKind() == NEW_ARRAY; sync(node); builder.open(isArrayInitializer ? ZERO : plusFour); scan(node.getVariable(), null); builder.space(); token("="); if (isArrayInitializer) { builder.space(); } else { builder.breakOp(" "); } scan(node.getExpression(), null); builder.close(); } @Override public Void visitAnnotatedType(AnnotatedTypeTree node, Void unused) { sync(node); ExpressionTree base = node.getUnderlyingType(); if (base instanceof MemberSelectTree) { MemberSelectTree selectTree = (MemberSelectTree) base; scan(selectTree.getExpression(), null); token("."); visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO); builder.breakToFill(" "); visit(selectTree.getIdentifier()); } else if (base instanceof ArrayTypeTree) { visitAnnotatedArrayType(node); } else { visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO); builder.breakToFill(" "); scan(base, null); } return null; } @Override public Void visitMethod(MethodTree node, Void unused) { sync(node); List<? extends AnnotationTree> annotations = node.getModifiers().getAnnotations(); List<? extends AnnotationTree> returnTypeAnnotations = ImmutableList.of(); if (!node.getTypeParameters().isEmpty() && !annotations.isEmpty()) { int typeParameterStart = getStartPosition(node.getTypeParameters().get(0)); for (int i = 0; i < annotations.size(); i++) { if (getStartPosition(annotations.get(i)) > typeParameterStart) { returnTypeAnnotations = annotations.subList(i, annotations.size()); annotations = annotations.subList(0, i); break; } } } builder.addAll(visitModifiers(annotations, Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty())); Tree baseReturnType = null; Deque<List<AnnotationTree>> dims = null; if (node.getReturnType() != null) { TypeWithDims extractedDims = DimensionHelpers.extractDims(node.getReturnType(), SortedDims.YES); baseReturnType = extractedDims.node; dims = new ArrayDeque<>(extractedDims.dims); } builder.open(plusFour); BreakTag breakBeforeName = genSym(); BreakTag breakBeforeType = genSym(); builder.open(ZERO); { boolean first = true; if (!node.getTypeParameters().isEmpty()) { token("<"); typeParametersRest(node.getTypeParameters(), plusFour); if (!returnTypeAnnotations.isEmpty()) { builder.breakToFill(" "); visitAnnotations(returnTypeAnnotations, BreakOrNot.NO, 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 (baseReturnType != null) { if (!first) { builder.breakOp(INDEPENDENT, " ", ZERO, Optional.of(breakBeforeType)); } else { first = false; } if (!openedNameAndTypeScope) { builder.open(make(breakBeforeType, plusFour, ZERO)); openedNameAndTypeScope = true; } scan(baseReturnType, null); maybeAddDims(dims); } if (!first) { builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(breakBeforeName)); } else { first = false; } if (!openedNameAndTypeScope) { builder.open(ZERO); openedNameAndTypeScope = true; } String name = node.getName().toString(); if (name.equals("<init>")) { name = builder.peekToken().get(); } token(name); 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.getParameters().isEmpty() || node.getReceiverParameter() != null) { // Break before args. builder.breakToFill(""); visitFormals(Optional.ofNullable(node.getReceiverParameter()), node.getParameters()); } token(")"); if (dims != null) { maybeAddDims(dims); } if (!node.getThrows().isEmpty()) { builder.breakToFill(" "); builder.open(plusFour); { visitThrowsClause(node.getThrows()); } builder.close(); } if (node.getDefaultValue() != null) { builder.space(); token("default"); if (node.getDefaultValue().getKind() == Tree.Kind.NEW_ARRAY) { builder.open(minusFour); { builder.space(); scan(node.getDefaultValue(), null); } builder.close(); } else { builder.open(ZERO); { builder.breakToFill(" "); scan(node.getDefaultValue(), null); } builder.close(); } } } builder.close(); builder.close(); builder.close(); if (node.getBody() == null) { token(";"); } else { builder.space(); builder.token("{", Doc.Token.RealOrImaginary.REAL, plusTwo, Optional.of(plusTwo)); } builder.close(); if (node.getBody() != null) { methodBody(node); } return null; } private void methodBody(MethodTree node) { if (node.getBody().getStatements().isEmpty()) { builder.blankLineWanted(BlankLineWanted.NO); } else { builder.open(plusTwo); builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.PRESERVE); visitStatements(node.getBody().getStatements()); builder.close(); builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.NO); markForPartialFormat(); } token("}", plusTwo); } @Override public Void visitMethodInvocation(MethodInvocationTree node, Void unused) { sync(node); if (handleLogStatement(node)) { return null; } visitDot(node); return null; } /** * Special-cases log statements, to output: * * <pre>{@code * logger.atInfo().log( * "Number of foos: %d, foos.size()); * }</pre> * * <p>Instead of: * * <pre>{@code * logger * .atInfo() * .log( * "Number of foos: %d, foos.size()); * }</pre> */ private boolean handleLogStatement(MethodInvocationTree node) { if (!getMethodName(node).contentEquals("log")) { return false; } Deque<ExpressionTree> parts = new ArrayDeque<>(); ExpressionTree curr = node; while (curr instanceof MethodInvocationTree) { MethodInvocationTree method = (MethodInvocationTree) curr; parts.addFirst(method); if (!LOG_METHODS.contains(getMethodName(method).toString())) { return false; } curr = Trees.getMethodReceiver(method); } if (!(curr instanceof IdentifierTree)) { return false; } parts.addFirst(curr); visitDotWithPrefix(ImmutableList.copyOf(parts), false, ImmutableList.of(parts.size() - 1), INDEPENDENT); return true; } static final ImmutableSet<String> LOG_METHODS = ImmutableSet.of("at", "atConfig", "atDebug", "atFine", "atFiner", "atFinest", "atInfo", "atMostEvery", "atSevere", "atWarning", "every", "log", "logVarargs", "perUnique", "withCause", "withStackTrace"); private static List<Long> handleStream(List<ExpressionTree> parts) { return indexes(parts.stream(), p -> { if (!(p instanceof MethodInvocationTree)) { return false; } Name name = getMethodName((MethodInvocationTree) p); return Stream.of("stream", "parallelStream", "toBuilder").anyMatch(name::contentEquals); }).collect(toList()); } private static <T> Stream<Long> indexes(Stream<T> stream, Predicate<T> predicate) { return Streams.mapWithIndex(stream, (x, i) -> predicate.apply(x) ? i : -1).filter(x -> x != -1); } @Override public Void visitMemberSelect(MemberSelectTree node, Void unused) { sync(node); visitDot(node); return null; } @Override public Void visitLiteral(LiteralTree node, Void unused) { sync(node); String sourceForNode = getSourceForNode(node, getCurrentPath()); // A negative numeric literal -n is usually represented as unary minus on n, // but that doesn't work for integer or long MIN_VALUE. The parser works // around that by representing it directly as a signed literal (with no // unary minus), but the lexer still expects two tokens. if (sourceForNode.startsWith("-")) { token("-"); sourceForNode = sourceForNode.substring(1).trim(); } token(sourceForNode); return null; } private void visitPackage(ExpressionTree packageName, List<? extends AnnotationTree> packageAnnotations) { if (!packageAnnotations.isEmpty()) { for (AnnotationTree annotation : packageAnnotations) { builder.forcedBreak(); scan(annotation, null); } builder.forcedBreak(); } builder.open(plusFour); token("package"); builder.space(); visitName(packageName); builder.close(); token(";"); } @Override public Void visitParameterizedType(ParameterizedTypeTree node, Void unused) { sync(node); if (node.getTypeArguments().isEmpty()) { scan(node.getType(), null); token("<"); token(">"); } else { builder.open(plusFour); scan(node.getType(), null); token("<"); builder.breakOp(); builder.open(ZERO); boolean first = true; for (Tree typeArgument : node.getTypeArguments()) { if (!first) { token(","); builder.breakOp(" "); } scan(typeArgument, null); first = false; } builder.close(); builder.close(); token(">"); } return null; } @Override public Void visitParenthesized(ParenthesizedTree node, Void unused) { token("("); scan(node.getExpression(), null); token(")"); return null; } @Override public Void visitUnary(UnaryTree node, Void unused) { sync(node); String operatorName = operatorName(node); if (((JCTree) node).getTag().isPostUnaryOp()) { scan(node.getExpression(), null); splitToken(operatorName); } else { splitToken(operatorName); if (ambiguousUnaryOperator(node, operatorName)) { builder.space(); } scan(node.getExpression(), null); } return null; } private void splitToken(String operatorName) { for (int i = 0; i < operatorName.length(); i++) { token(String.valueOf(operatorName.charAt(i))); } } private boolean ambiguousUnaryOperator(UnaryTree node, String operatorName) { switch (node.getKind()) { case UNARY_MINUS: case UNARY_PLUS: break; default: return false; } if (!(node.getExpression() instanceof UnaryTree)) { return false; } JCTree.Tag tag = ((JCTree) node.getExpression()).getTag(); if (tag.isPostUnaryOp()) { return false; } if (!operatorName(node).startsWith(operatorName)) { return false; } return true; } @Override public Void visitPrimitiveType(PrimitiveTypeTree node, Void unused) { sync(node); switch (node.getPrimitiveTypeKind()) { case BOOLEAN: token("boolean"); break; case BYTE: token("byte"); break; case SHORT: token("short"); break; case INT: token("int"); break; case LONG: token("long"); break; case CHAR: token("char"); break; case FLOAT: token("float"); break; case DOUBLE: token("double"); break; case VOID: token("void"); break; default: throw new AssertionError(node.getPrimitiveTypeKind()); } return null; } public boolean visit(Name name) { token(name.toString()); return false; } @Override public Void visitReturn(ReturnTree node, Void unused) { sync(node); token("return"); if (node.getExpression() != null) { builder.space(); scan(node.getExpression(), null); } token(";"); return null; } // TODO(cushon): is this worth special-casing? boolean visitSingleMemberAnnotation(AnnotationTree node) { if (node.getArguments().size() != 1) { return false; } ExpressionTree value = getOnlyElement(node.getArguments()); if (value.getKind() == ASSIGNMENT) { return false; } boolean isArrayInitializer = value.getKind() == NEW_ARRAY; builder.open(isArrayInitializer ? ZERO : plusFour); token("@"); scan(node.getAnnotationType(), null); token("("); if (!isArrayInitializer) { builder.breakOp(); } scan(value, null); builder.close(); token(")"); return true; } @Override public Void visitCase(CaseTree node, Void unused) { sync(node); markForPartialFormat(); builder.forcedBreak(); if (node.getExpression() == null) { token("default", plusTwo); token(":"); } else { token("case", plusTwo); builder.space(); scan(node.getExpression(), null); token(":"); } builder.open(plusTwo); visitStatements(node.getStatements()); builder.close(); return null; } @Override public Void visitSwitch(SwitchTree node, Void unused) { sync(node); token("switch"); builder.space(); token("("); scan(skipParen(node.getExpression()), null); token(")"); builder.space(); tokenBreakTrailingComment("{", plusTwo); builder.blankLineWanted(BlankLineWanted.NO); builder.open(plusTwo); boolean first = true; for (CaseTree caseTree : node.getCases()) { if (!first) { builder.blankLineWanted(BlankLineWanted.PRESERVE); } scan(caseTree, null); first = false; } builder.close(); builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.NO); token("}", plusFour); return null; } @Override public Void visitSynchronized(SynchronizedTree node, Void unused) { sync(node); token("synchronized"); builder.space(); token("("); builder.open(plusFour); builder.breakOp(); scan(skipParen(node.getExpression()), null); builder.close(); token(")"); builder.space(); scan(node.getBlock(), null); return null; } @Override public Void visitThrow(ThrowTree node, Void unused) { sync(node); token("throw"); builder.space(); scan(node.getExpression(), null); token(";"); return null; } @Override public Void visitTry(TryTree node, Void unused) { sync(node); builder.open(ZERO); token("try"); builder.space(); if (!node.getResources().isEmpty()) { token("("); builder.open(node.getResources().size() > 1 ? plusFour : ZERO); boolean first = true; for (Tree resource : node.getResources()) { if (!first) { builder.forcedBreak(); } if (resource instanceof VariableTree) { VariableTree variableTree = (VariableTree) resource; declareOne(DeclarationKind.PARAMETER, fieldAnnotationDirection(variableTree.getModifiers()), Optional.of(variableTree.getModifiers()), variableTree.getType(), /* name= */ variableTree.getName(), "", "=", Optional.ofNullable(variableTree.getInitializer()), /* trailing= */ Optional.empty(), /* receiverExpression= */ Optional.empty(), /* typeWithDims= */ Optional.empty()); } else { // TODO(cushon): think harder about what to do with `try (resource1; resource2) {}` scan(resource, null); } if (builder.peekToken().equals(Optional.of(";"))) { token(";"); builder.space(); } first = false; } if (builder.peekToken().equals(Optional.of(";"))) { token(";"); builder.space(); } 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.getCatches().isEmpty() || node.getFinallyBlock() != null; visitBlock(node.getBlock(), CollapseEmptyOrNot.valueOf(!trailingClauses), AllowLeadingBlankLine.YES, AllowTrailingBlankLine.valueOf(trailingClauses)); for (int i = 0; i < node.getCatches().size(); i++) { CatchTree catchClause = node.getCatches().get(i); trailingClauses = i < node.getCatches().size() - 1 || node.getFinallyBlock() != null; visitCatchClause(catchClause, AllowTrailingBlankLine.valueOf(trailingClauses)); } if (node.getFinallyBlock() != null) { builder.space(); token("finally"); builder.space(); visitBlock(node.getFinallyBlock(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES, AllowTrailingBlankLine.NO); } builder.close(); return null; } public void visitClassDeclaration(ClassTree node) { sync(node); List<Op> breaks = visitModifiers(node.getModifiers(), Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty()); boolean hasSuperclassType = node.getExtendsClause() != null; boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty(); builder.addAll(breaks); token(node.getKind() == Tree.Kind.INTERFACE ? "interface" : "class"); builder.space(); visit(node.getSimpleName()); if (!node.getTypeParameters().isEmpty()) { token("<"); } builder.open(plusFour); { if (!node.getTypeParameters().isEmpty()) { typeParametersRest(node.getTypeParameters(), hasSuperclassType || hasSuperInterfaceTypes ? plusFour : ZERO); } if (hasSuperclassType) { builder.breakToFill(" "); token("extends"); builder.space(); scan(node.getExtendsClause(), null); } if (hasSuperInterfaceTypes) { builder.breakToFill(" "); builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO); token(node.getKind() == Tree.Kind.INTERFACE ? "extends" : "implements"); builder.space(); boolean first = true; for (Tree superInterfaceType : node.getImplementsClause()) { if (!first) { token(","); builder.breakOp(" "); } scan(superInterfaceType, null); first = false; } builder.close(); } } builder.close(); if (node.getMembers() == null) { token(";"); } else { addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); } dropEmptyDeclarations(); } @Override public Void visitTypeParameter(TypeParameterTree node, Void unused) { sync(node); builder.open(ZERO); visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.YES); visit(node.getName()); if (!node.getBounds().isEmpty()) { builder.space(); token("extends"); builder.open(plusFour); builder.breakOp(" "); builder.open(plusFour); boolean first = true; for (Tree typeBound : node.getBounds()) { if (!first) { builder.breakToFill(" "); token("&"); builder.space(); } scan(typeBound, null); first = false; } builder.close(); builder.close(); } builder.close(); return null; } @Override public Void visitUnionType(UnionTypeTree node, Void unused) { throw new IllegalStateException("expected manual descent into union types"); } @Override public Void visitWhileLoop(WhileLoopTree node, Void unused) { sync(node); token("while"); builder.space(); token("("); scan(skipParen(node.getCondition()), null); token(")"); visitStatement(node.getStatement(), CollapseEmptyOrNot.YES, AllowLeadingBlankLine.YES, AllowTrailingBlankLine.NO); return null; } @Override public Void visitWildcard(WildcardTree node, Void unused) { sync(node); builder.open(ZERO); token("?"); if (node.getBound() != null) { builder.open(plusFour); builder.space(); token(node.getKind() == EXTENDS_WILDCARD ? "extends" : "super"); builder.breakOp(" "); scan(node.getBound(), null); builder.close(); } builder.close(); return null; } // Helper methods. /** Helper method for annotations. */ void visitAnnotations(List<? extends AnnotationTree> annotations, BreakOrNot breakBefore, BreakOrNot breakAfter) { if (!annotations.isEmpty()) { if (breakBefore.isYes()) { builder.breakToFill(" "); } boolean first = true; for (AnnotationTree annotation : annotations) { if (!first) { builder.breakToFill(" "); } scan(annotation, null); first = false; } if (breakAfter.isYes()) { builder.breakToFill(" "); } } } /** Helper method for blocks. */ private void visitBlock(BlockTree node, CollapseEmptyOrNot collapseEmptyOrNot, AllowLeadingBlankLine allowLeadingBlankLine, AllowTrailingBlankLine allowTrailingBlankLine) { sync(node); if (node.isStatic()) { token("static"); builder.space(); } if (collapseEmptyOrNot.isYes() && node.getStatements().isEmpty()) { if (builder.peekToken().equals(Optional.of(";"))) { // TODO(cushon): is this needed? token(";"); } else { 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); } visitStatements(node.getStatements()); 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 statements. */ private void visitStatement(StatementTree node, CollapseEmptyOrNot collapseEmptyOrNot, AllowLeadingBlankLine allowLeadingBlank, AllowTrailingBlankLine allowTrailingBlank) { sync(node); switch (node.getKind()) { case BLOCK: builder.space(); visitBlock((BlockTree) node, collapseEmptyOrNot, allowLeadingBlank, allowTrailingBlank); break; default: builder.open(plusTwo); builder.breakOp(" "); scan(node, null); builder.close(); } } private void visitStatements(List<? extends StatementTree> statements) { boolean first = true; PeekingIterator<StatementTree> it = Iterators.peekingIterator(statements.iterator()); dropEmptyDeclarations(); while (it.hasNext()) { StatementTree tree = it.next(); builder.forcedBreak(); if (!first) { builder.blankLineWanted(BlankLineWanted.PRESERVE); } markForPartialFormat(); first = false; List<VariableTree> fragments = variableFragments(it, tree); if (!fragments.isEmpty()) { visitVariables(fragments, DeclarationKind.NONE, canLocalHaveHorizontalAnnotations(fragments.get(0).getModifiers())); } else { scan(tree, null); } } } /** Output combined modifiers and annotations and the trailing break. */ void visitAndBreakModifiers(ModifiersTree modifiers, Direction annotationDirection, Optional<BreakTag> declarationAnnotationBreak) { builder.addAll(visitModifiers(modifiers, annotationDirection, declarationAnnotationBreak)); } @Override public Void visitModifiers(ModifiersTree node, Void unused) { throw new IllegalStateException("expected manual descent into modifiers"); } /** Output combined modifiers and annotations and returns the trailing break. */ private List<Op> visitModifiers(ModifiersTree modifiersTree, Direction annotationsDirection, Optional<BreakTag> declarationAnnotationBreak) { return visitModifiers(modifiersTree.getAnnotations(), annotationsDirection, declarationAnnotationBreak); } private List<Op> visitModifiers(List<? extends AnnotationTree> annotationTrees, Direction annotationsDirection, Optional<BreakTag> declarationAnnotationBreak) { if (annotationTrees.isEmpty() && !nextIsModifier()) { return EMPTY_LIST; } Deque<AnnotationTree> annotations = new ArrayDeque<>(annotationTrees); builder.open(ZERO); boolean first = true; boolean lastWasAnnotation = false; while (!annotations.isEmpty()) { if (nextIsModifier()) { break; } if (!first) { builder.addAll(annotationsDirection.isVertical() ? forceBreakList(declarationAnnotationBreak) : breakList(declarationAnnotationBreak)); } scan(annotations.removeFirst(), null); first = false; lastWasAnnotation = true; } builder.close(); ImmutableList<Op> trailingBreak = annotationsDirection.isVertical() ? forceBreakList(declarationAnnotationBreak) : breakList(declarationAnnotationBreak); if (annotations.isEmpty() && !nextIsModifier()) { return trailingBreak; } if (lastWasAnnotation) { builder.addAll(trailingBreak); } builder.open(ZERO); first = true; while (nextIsModifier() || !annotations.isEmpty()) { if (!first) { builder.addAll(breakFillList(Optional.empty())); } if (nextIsModifier()) { token(builder.peekToken().get()); } else { scan(annotations.removeFirst(), null); lastWasAnnotation = true; } first = false; } builder.close(); return breakFillList(Optional.empty()); } boolean nextIsModifier() { switch (builder.peekToken().get()) { case "public": case "protected": case "private": case "abstract": case "static": case "final": case "transient": case "volatile": case "synchronized": case "native": case "strictfp": case "default": return true; default: return false; } } @Override public Void visitCatch(CatchTree node, Void unused) { throw new IllegalStateException("expected manual descent into catch trees"); } /** Helper method for {@link CatchTree}s. */ private void visitCatchClause(CatchTree node, AllowTrailingBlankLine allowTrailingBlankLine) { sync(node); builder.space(); token("catch"); builder.space(); token("("); builder.open(plusFour); VariableTree ex = node.getParameter(); if (ex.getType().getKind() == UNION_TYPE) { builder.open(ZERO); visitUnionType(ex); builder.close(); } else { // TODO(cushon): don't break after here for consistency with for, while, etc. builder.breakToFill(); builder.open(ZERO); scan(ex, null); builder.close(); } builder.close(); token(")"); builder.space(); visitBlock(node.getBlock(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES, allowTrailingBlankLine); } /** Formats a union type declaration in a catch clause. */ private void visitUnionType(VariableTree declaration) { UnionTypeTree type = (UnionTypeTree) declaration.getType(); builder.open(ZERO); sync(declaration); visitAndBreakModifiers(declaration.getModifiers(), Direction.HORIZONTAL, /* declarationAnnotationBreak= */ Optional.empty()); List<? extends Tree> union = type.getTypeAlternatives(); boolean first = true; for (int i = 0; i < union.size() - 1; i++) { if (!first) { builder.breakOp(" "); token("|"); builder.space(); } else { first = false; } scan(union.get(i), null); } builder.breakOp(" "); token("|"); builder.space(); Tree last = union.get(union.size() - 1); declareOne(DeclarationKind.NONE, Direction.HORIZONTAL, /* modifiers= */ Optional.empty(), last, /* name= */ declaration.getName(), /* op= */ "", "=", Optional.ofNullable(declaration.getInitializer()), /* trailing= */ Optional.empty(), /* receiverExpression= */ Optional.empty(), /* typeWithDims= */ Optional.empty()); builder.close(); } /** Accumulate the operands and operators. */ private static void walkInfix(int precedence, ExpressionTree expression, List<ExpressionTree> operands, List<String> operators) { if (expression instanceof BinaryTree) { BinaryTree binaryTree = (BinaryTree) expression; if (precedence(binaryTree) == precedence) { walkInfix(precedence, binaryTree.getLeftOperand(), operands, operators); operators.add(operatorName(expression)); walkInfix(precedence, binaryTree.getRightOperand(), operands, operators); } else { operands.add(expression); } } else { operands.add(expression); } } private void visitFormals(Optional<VariableTree> receiver, List<? extends VariableTree> parameters) { if (!receiver.isPresent() && parameters.isEmpty()) { return; } builder.open(ZERO); boolean first = true; if (receiver.isPresent()) { // TODO(jdd): Use builders. declareOne(DeclarationKind.PARAMETER, Direction.HORIZONTAL, Optional.of(receiver.get().getModifiers()), receiver.get().getType(), /* name= */ receiver.get().getName(), "", "", /* initializer= */ Optional.empty(), !parameters.isEmpty() ? Optional.of(",") : Optional.empty(), Optional.of(receiver.get().getNameExpression()), /* typeWithDims= */ Optional.empty()); first = false; } for (int i = 0; i < parameters.size(); i++) { VariableTree parameter = parameters.get(i); if (!first) { builder.breakOp(" "); } visitToDeclare(DeclarationKind.PARAMETER, Direction.HORIZONTAL, parameter, /* initializer= */ Optional.empty(), "=", i < parameters.size() - 1 ? Optional.of(",") : /* a= */ Optional.empty()); first = false; } builder.close(); } // /** Helper method for {@link MethodDeclaration}s. */ private void visitThrowsClause(List<? extends ExpressionTree> thrownExceptionTypes) { token("throws"); builder.breakToFill(" "); boolean first = true; for (ExpressionTree thrownExceptionType : thrownExceptionTypes) { if (!first) { token(","); builder.breakToFill(" "); } scan(thrownExceptionType, null); first = false; } } @Override public Void visitIdentifier(IdentifierTree node, Void unused) { sync(node); token(node.getName().toString()); return null; } @Override public Void visitModule(ModuleTree node, Void unused) { for (AnnotationTree annotation : node.getAnnotations()) { scan(annotation, null); builder.forcedBreak(); } if (node.getModuleType() == ModuleTree.ModuleKind.OPEN) { token("open"); builder.space(); } token("module"); builder.space(); scan(node.getName(), null); builder.space(); if (node.getDirectives().isEmpty()) { tokenBreakTrailingComment("{", plusTwo); builder.blankLineWanted(BlankLineWanted.NO); token("}", plusTwo); } else { builder.open(plusTwo); token("{"); builder.forcedBreak(); Optional<Tree.Kind> previousDirective = Optional.empty(); for (DirectiveTree directiveTree : node.getDirectives()) { markForPartialFormat(); builder.blankLineWanted(previousDirective.map(k -> !k.equals(directiveTree.getKind())).orElse(false) ? BlankLineWanted.YES : BlankLineWanted.NO); builder.forcedBreak(); scan(directiveTree, null); previousDirective = Optional.of(directiveTree.getKind()); } builder.close(); builder.forcedBreak(); token("}"); } return null; } private void visitDirective(String name, String separator, ExpressionTree nameExpression, @Nullable List<? extends ExpressionTree> items) { token(name); builder.space(); scan(nameExpression, null); if (items != null) { builder.open(plusFour); builder.space(); token(separator); builder.forcedBreak(); boolean first = true; for (ExpressionTree item : items) { if (!first) { token(","); builder.forcedBreak(); } scan(item, null); first = false; } token(";"); builder.close(); } else { token(";"); } } @Override public Void visitExports(ExportsTree node, Void unused) { visitDirective("exports", "to", node.getPackageName(), node.getModuleNames()); return null; } @Override public Void visitOpens(OpensTree node, Void unused) { visitDirective("opens", "to", node.getPackageName(), node.getModuleNames()); return null; } @Override public Void visitProvides(ProvidesTree node, Void unused) { visitDirective("provides", "with", node.getServiceName(), node.getImplementationNames()); return null; } @Override public Void visitRequires(RequiresTree node, Void unused) { token("requires"); builder.space(); while (true) { if (builder.peekToken().equals(Optional.of("static"))) { token("static"); builder.space(); } else if (builder.peekToken().equals(Optional.of("transitive"))) { token("transitive"); builder.space(); } else { break; } } scan(node.getModuleName(), null); token(";"); return null; } @Override public Void visitUses(UsesTree node, Void unused) { token("uses"); builder.space(); scan(node.getServiceName(), null); token(";"); return null; } /** Helper method for import declarations, names, and qualified names. */ private void visitName(Tree node) { Deque<Name> stack = new ArrayDeque<>(); for (; node instanceof MemberSelectTree; node = ((MemberSelectTree) node).getExpression()) { stack.addFirst(((MemberSelectTree) node).getIdentifier()); } stack.addFirst(((IdentifierTree) node).getName()); boolean first = true; for (Name name : stack) { if (!first) { token("."); } token(name.toString()); first = false; } } private void visitToDeclare(DeclarationKind kind, Direction annotationsDirection, VariableTree node, Optional<ExpressionTree> initializer, String equals, Optional<String> trailing) { sync(node); declareOne(kind, annotationsDirection, Optional.of(node.getModifiers()), node.getType(), node.getName(), "", equals, initializer, trailing, /* receiverExpression= */ Optional.empty(), /* typeWithDims= */ Optional.empty()); } /** Does not omit the leading '<', which should be associated with the type name. */ private void typeParametersRest(List<? extends TypeParameterTree> typeParameters, Indent plusIndent) { builder.open(plusIndent); builder.breakOp(); builder.open(ZERO); boolean first = true; for (TypeParameterTree typeParameter : typeParameters) { if (!first) { token(","); builder.breakOp(" "); } scan(typeParameter, null); first = false; } token(">"); builder.close(); builder.close(); } /** Collapse chains of {@code .} operators, across multiple {@link ASTNode} types. */ /** * Output a "." node. * * @param node0 the "." node */ void visitDot(ExpressionTree node0) { ExpressionTree node = node0; // collect a flattened list of "."-separated items // e.g. ImmutableList.builder().add(1).build() -> [ImmutableList, builder(), add(1), build()] Deque<ExpressionTree> stack = new ArrayDeque<>(); LOOP: do { stack.addFirst(node); if (node.getKind() == ARRAY_ACCESS) { node = getArrayBase(node); } switch (node.getKind()) { case MEMBER_SELECT: node = ((MemberSelectTree) node).getExpression(); break; case METHOD_INVOCATION: node = getMethodReceiver((MethodInvocationTree) node); break; case IDENTIFIER: 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. node = stack.removeFirst(); break LOOP; } } while (node != null); List<ExpressionTree> 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.getKind() == NEW_CLASS && ((NewClassTree) node).getClassBody() != null) { builder.open(ZERO); scan(getArrayBase(node), null); token("."); } else { builder.open(plusFour); scan(getArrayBase(node), null); builder.breakOp(); needDot = true; } formatArrayIndices(getArrayIndices(node)); if (stack.isEmpty()) { builder.close(); return; } } Set<Integer> prefixes = new LinkedHashSet<>(); // 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. TypeNameClassifier.typePrefixLength(simpleNames(stack)).ifPresent(prefixes::add); int invocationCount = 0; int firstInvocationIndex = -1; { for (int i = 0; i < items.size(); i++) { ExpressionTree expression = items.get(i); if (expression.getKind() == 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 && firstInvocationIndex > 0) { prefixes.add(firstInvocationIndex); } if (prefixes.isEmpty() && items.get(0) instanceof IdentifierTree) { switch (((IdentifierTree) items.get(0)).getName().toString()) { case "this": case "super": prefixes.add(1); break; default: break; } } List<Long> streamPrefixes = handleStream(items); streamPrefixes.forEach(x -> prefixes.add(x.intValue())); if (!prefixes.isEmpty()) { visitDotWithPrefix(items, needDot, prefixes, streamPrefixes.isEmpty() ? INDEPENDENT : UNIFIED); } 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<ExpressionTree> items, boolean needDot) { boolean trailingDereferences = items.size() > 1; boolean needDot0 = needDot; if (!needDot0) { builder.open(plusFour); } // 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 (ExpressionTree e : items) { if (needDot) { if (length > minLength) { builder.breakOp(FillMode.UNIFIED, "", ZERO); } token("."); length++; } if (!fillFirstArgument(e, items, trailingDereferences ? ZERO : minusFour)) { BreakTag tyargTag = genSym(); dotExpressionUpToArgs(e, Optional.of(tyargTag)); Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); dotExpressionArgsAndParen(e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO); } length += getLength(e, getCurrentPath()); needDot = true; } if (!needDot0) { builder.close(); } } // avoid formattings like: // // when( // something // .happens()) // .thenReturn(result); // private boolean fillFirstArgument(ExpressionTree e, List<ExpressionTree> items, Indent indent) { // is there a trailing dereference? if (items.size() < 2) { return false; } // don't special-case calls nested inside expressions if (e.getKind() != METHOD_INVOCATION) { return false; } MethodInvocationTree methodInvocation = (MethodInvocationTree) e; Name name = getMethodName(methodInvocation); if (!(methodInvocation.getMethodSelect() instanceof IdentifierTree) || name.length() > 4 || !methodInvocation.getTypeArguments().isEmpty() || methodInvocation.getArguments().size() != 1) { return false; } builder.open(ZERO); builder.open(indent); visit(name); token("("); ExpressionTree arg = getOnlyElement(methodInvocation.getArguments()); scan(arg, null); builder.close(); token(")"); builder.close(); return true; } /** * 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 prefixes the terminal indices of 'prefixes' of the expression that should be treated as * a syntactic unit */ private void visitDotWithPrefix(List<ExpressionTree> items, boolean needDot, Collection<Integer> prefixes, FillMode prefixFillMode) { // Are there method invocations or field accesses after the prefix? boolean trailingDereferences = !prefixes.isEmpty() && getLast(prefixes) < items.size() - 1; builder.open(plusFour); for (int times = 0; times < prefixes.size(); times++) { builder.open(ZERO); } Deque<Integer> unconsumedPrefixes = new ArrayDeque<>(ImmutableSortedSet.copyOf(prefixes)); BreakTag nameTag = genSym(); for (int i = 0; i < items.size(); i++) { ExpressionTree e = items.get(i); if (needDot) { FillMode fillMode; if (!unconsumedPrefixes.isEmpty() && i <= unconsumedPrefixes.peekFirst()) { fillMode = prefixFillMode; } else { fillMode = FillMode.UNIFIED; } builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag)); token("."); } BreakTag tyargTag = genSym(); dotExpressionUpToArgs(e, Optional.of(tyargTag)); if (!unconsumedPrefixes.isEmpty() && i == unconsumedPrefixes.peekFirst()) { builder.close(); unconsumedPrefixes.removeFirst(); } 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(Deque<ExpressionTree> stack) { ImmutableList.Builder<String> simpleNames = ImmutableList.builder(); OUTER: for (ExpressionTree expression : stack) { boolean isArray = expression.getKind() == ARRAY_ACCESS; expression = getArrayBase(expression); switch (expression.getKind()) { case MEMBER_SELECT: simpleNames.add(((MemberSelectTree) expression).getIdentifier().toString()); break; case IDENTIFIER: simpleNames.add(((IdentifierTree) expression).getName().toString()); break; case METHOD_INVOCATION: simpleNames.add(getMethodName((MethodInvocationTree) expression).toString()); break OUTER; default: break OUTER; } if (isArray) { break OUTER; } } return simpleNames.build(); } private void dotExpressionUpToArgs(ExpressionTree expression, Optional<BreakTag> tyargTag) { expression = getArrayBase(expression); switch (expression.getKind()) { case MEMBER_SELECT: MemberSelectTree fieldAccess = (MemberSelectTree) expression; visit(fieldAccess.getIdentifier()); break; case METHOD_INVOCATION: MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; if (!methodInvocation.getTypeArguments().isEmpty()) { builder.open(plusFour); addTypeArguments(methodInvocation.getTypeArguments(), ZERO); // TODO(jdd): Should indent the name -4. builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, tyargTag); builder.close(); } visit(getMethodName(methodInvocation)); break; case IDENTIFIER: visit(((IdentifierTree) expression).getName()); break; default: scan(expression, null); break; } } /** * Returns the base expression of an erray access, e.g. given {@code foo[0][0]} returns {@code * foo}. */ private ExpressionTree getArrayBase(ExpressionTree node) { while (node instanceof ArrayAccessTree) { node = ((ArrayAccessTree) node).getExpression(); } return node; } private ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) { ExpressionTree select = methodInvocation.getMethodSelect(); return select instanceof MemberSelectTree ? ((MemberSelectTree) select).getExpression() : null; } private void dotExpressionArgsAndParen(ExpressionTree expression, Indent tyargIndent, Indent indent) { Deque<ExpressionTree> indices = getArrayIndices(expression); expression = getArrayBase(expression); switch (expression.getKind()) { case METHOD_INVOCATION: builder.open(tyargIndent); MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; addArguments(methodInvocation.getArguments(), indent); builder.close(); break; default: break; } formatArrayIndices(indices); } /** Lays out one or more array indices. Does not output the expression for the array itself. */ private void formatArrayIndices(Deque<ExpressionTree> indices) { if (indices.isEmpty()) { return; } builder.open(ZERO); do { token("["); builder.breakToFill(); scan(indices.removeLast(), null); token("]"); } while (!indices.isEmpty()); builder.close(); } /** * Returns all array indices for the given expression, e.g. given {@code foo[0][0]} returns the * expressions for {@code [0][0]}. */ private Deque<ExpressionTree> getArrayIndices(ExpressionTree expression) { Deque<ExpressionTree> indices = new ArrayDeque<>(); while (expression instanceof ArrayAccessTree) { ArrayAccessTree array = (ArrayAccessTree) expression; indices.addLast(array.getIndex()); expression = array.getExpression(); } return indices; } /** Helper methods for method invocations. */ void addTypeArguments(List<? extends Tree> typeArguments, Indent plusIndent) { if (typeArguments == null || typeArguments.isEmpty()) { return; } token("<"); builder.open(plusIndent); boolean first = true; for (Tree typeArgument : typeArguments) { if (!first) { token(","); builder.breakToFill(" "); } scan(typeArgument, null); 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<? extends ExpressionTree> arguments, Indent plusIndent) { builder.open(plusIndent); token("("); if (!arguments.isEmpty()) { if (arguments.size() % 2 == 0 && argumentsAreTabular(arguments) == 2) { builder.forcedBreak(); builder.open(ZERO); boolean first = true; for (int i = 0; i < arguments.size() - 1; i += 2) { ExpressionTree argument0 = arguments.get(i); ExpressionTree argument1 = arguments.get(i + 1); if (!first) { token(","); builder.forcedBreak(); } builder.open(plusFour); scan(argument0, null); token(","); builder.breakOp(" "); scan(argument1, null); builder.close(); first = false; } builder.close(); } else if (isFormatMethod(arguments)) { builder.breakOp(); builder.open(ZERO); scan(arguments.get(0), null); token(","); builder.breakOp(" "); builder.open(ZERO); argList(arguments.subList(1, arguments.size())); builder.close(); builder.close(); } else { builder.breakOp(); argList(arguments); } } token(")"); builder.close(); } private void argList(List<? extends ExpressionTree> arguments) { builder.open(ZERO); boolean first = true; FillMode fillMode = hasOnlyShortItems(arguments) ? FillMode.INDEPENDENT : FillMode.UNIFIED; for (ExpressionTree argument : arguments) { if (!first) { token(","); builder.breakOp(fillMode, " ", ZERO); } scan(argument, null); first = false; } builder.close(); } /** * Identifies String formatting methods like {@link String#format} which we prefer to format as: * * <pre>{@code * String.format( * "the format string: %s %s %s", * arg, arg, arg); * }</pre> * * <p>And not: * * <pre>{@code * String.format( * "the format string: %s %s %s", * arg, * arg, * arg); * }</pre> */ private boolean isFormatMethod(List<? extends ExpressionTree> arguments) { if (arguments.size() < 2) { return false; } return isStringConcat(arguments.get(0)); } private static final Pattern FORMAT_SPECIFIER = Pattern.compile("%|\\{[0-9]\\}"); private boolean isStringConcat(ExpressionTree first) { final boolean[] stringLiteral = { true }; final boolean[] formatString = { false }; new TreeScanner() { @Override public void scan(JCTree tree) { if (tree == null) { return; } switch (tree.getKind()) { case STRING_LITERAL: break; case PLUS: super.scan(tree); break; default: stringLiteral[0] = false; break; } if (tree.getKind() == STRING_LITERAL) { Object value = ((LiteralTree) tree).getValue(); if (value instanceof String && FORMAT_SPECIFIER.matcher(value.toString()).find()) { formatString[0] = true; } } } }.scan((JCTree) first); return stringLiteral[0] && formatString[0]; } /** Returns the number of columns if the arguments arg laid out in a grid, or else {@code -1}. */ private int argumentsAreTabular(List<? extends ExpressionTree> arguments) { if (arguments.isEmpty()) { return -1; } List<List<ExpressionTree>> rows = new ArrayList<>(); PeekingIterator<ExpressionTree> it = Iterators.peekingIterator(arguments.iterator()); int start0 = actualColumn(it.peek()); { List<ExpressionTree> row = new ArrayList<>(); row.add(it.next()); while (it.hasNext() && actualColumn(it.peek()) > start0) { row.add(it.next()); } if (!it.hasNext()) { return -1; } if (rowLength(row) <= 1) { return -1; } rows.add(row); } while (it.hasNext()) { List<ExpressionTree> row = new ArrayList<>(); int start = actualColumn(it.peek()); if (start != start0) { return -1; } row.add(it.next()); while (it.hasNext() && actualColumn(it.peek()) > start0) { row.add(it.next()); } rows.add(row); } int size0 = rows.get(0).size(); if (!expressionsAreParallel(rows, 0, rows.size())) { return -1; } for (int i = 1; i < size0; i++) { if (!expressionsAreParallel(rows, i, rows.size() / 2 + 1)) { return -1; } } // if there are only two rows, they must be the same length if (rows.size() == 2) { if (size0 == rows.get(1).size()) { return size0; } return -1; } // allow a ragged trailing row for >= 3 columns for (int i = 1; i < rows.size() - 1; i++) { if (size0 != rows.get(i).size()) { return -1; } } if (size0 < getLast(rows).size()) { return -1; } return size0; } static int rowLength(List<? extends ExpressionTree> row) { int size = 0; for (ExpressionTree tree : row) { if (tree.getKind() != NEW_ARRAY) { size++; continue; } NewArrayTree array = (NewArrayTree) tree; if (array.getInitializers() == null) { size++; continue; } size += rowLength(array.getInitializers()); } return size; } private Integer actualColumn(ExpressionTree expression) { Map<Integer, Integer> positionToColumnMap = builder.getInput().getPositionToColumnMap(); return positionToColumnMap.get(builder.actualStartColumn(getStartPosition(expression))); } /** Returns true if {@code atLeastM} of the expressions in the given column are the same kind. */ private static boolean expressionsAreParallel(List<List<ExpressionTree>> rows, int column, int atLeastM) { Multiset<Tree.Kind> nodeTypes = HashMultiset.create(); for (List<? extends ExpressionTree> row : rows) { if (column >= row.size()) { continue; } nodeTypes.add(row.get(column).getKind()); } for (Multiset.Entry<Tree.Kind> nodeType : nodeTypes.entrySet()) { if (nodeType.getCount() >= atLeastM) { return true; } } return false; } // General helper functions. enum DeclarationKind { NONE, FIELD, PARAMETER } /** Declare one variable or variable-like thing. */ int declareOne(DeclarationKind kind, Direction annotationsDirection, Optional<ModifiersTree> modifiers, Tree type, Name name, String op, String equals, Optional<ExpressionTree> initializer, Optional<String> trailing, Optional<ExpressionTree> receiverExpression, Optional<TypeWithDims> typeWithDims) { BreakTag typeBreak = genSym(); BreakTag verticalAnnotationBreak = genSym(); // 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 = kind == DeclarationKind.FIELD; if (isField) { builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); } Deque<List<AnnotationTree>> dims = new ArrayDeque<>( typeWithDims.isPresent() ? typeWithDims.get().dims : Collections.emptyList()); int baseDims = 0; builder.open(kind == DeclarationKind.PARAMETER && (modifiers.isPresent() && !modifiers.get().getAnnotations().isEmpty()) ? plusFour : ZERO); { if (modifiers.isPresent()) { visitAndBreakModifiers(modifiers.get(), annotationsDirection, Optional.of(verticalAnnotationBreak)); } builder.open(type != null ? plusFour : ZERO); { builder.open(ZERO); { builder.open(ZERO); { if (typeWithDims.isPresent() && typeWithDims.get().node != null) { scan(typeWithDims.get().node, null); int totalDims = dims.size(); builder.open(plusFour); maybeAddDims(dims); builder.close(); baseDims = totalDims - dims.size(); } else { scan(type, null); } } builder.close(); if (type != null) { 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 (receiverExpression.isPresent()) { scan(receiverExpression.get(), null); } else { visit(name); } builder.op(op); } maybeAddDims(dims); builder.close(); } builder.close(); if (initializer.isPresent()) { builder.space(); token(equals); if (initializer.get().getKind() == Tree.Kind.NEW_ARRAY && ((NewArrayTree) initializer.get()).getType() == null) { builder.open(minusFour); builder.space(); initializer.get().accept(this, null); builder.close(); } else { builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); { builder.breakToFill(" "); scan(initializer.get(), null); } builder.close(); } } if (trailing.isPresent() && builder.peekToken().equals(trailing)) { builder.guessToken(trailing.get()); } // end of conditional name and initializer indent builder.close(); } builder.close(); if (isField) { builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); } return baseDims; } private void maybeAddDims(Deque<List<AnnotationTree>> annotations) { maybeAddDims(new ArrayDeque<>(), annotations); } /** * The compiler does not always preserve the concrete syntax of annotated array dimensions, and * mixed-notation array dimensions. Use look-ahead to preserve the original syntax. * * <p>It is assumed that any number of regular dimension specifiers ({@code []} with no * annotations) may be present in the input. * * @param dimExpressions an ordered list of dimension expressions (e.g. the {@code 0} in {@code * new int[0]} * @param annotations an ordered list of type annotations grouped by dimension (e.g. {@code * [[@A, @B], [@C]]} for {@code int @A [] @B @C []} */ private void maybeAddDims(Deque<ExpressionTree> dimExpressions, Deque<List<AnnotationTree>> annotations) { boolean lastWasAnnotation = false; while (builder.peekToken().isPresent()) { switch (builder.peekToken().get()) { case "@": if (annotations.isEmpty()) { return; } List<AnnotationTree> dimAnnotations = annotations.removeFirst(); if (dimAnnotations.isEmpty()) { continue; } builder.breakToFill(" "); visitAnnotations(dimAnnotations, BreakOrNot.NO, BreakOrNot.NO); lastWasAnnotation = true; break; case "[": if (lastWasAnnotation) { builder.breakToFill(" "); } else { builder.breakToFill(); } token("["); if (!builder.peekToken().get().equals("]")) { scan(dimExpressions.removeFirst(), null); } token("]"); lastWasAnnotation = false; break; case ".": if (!builder.peekToken().get().equals(".") || !builder.peekToken(1).get().equals(".")) { return; } if (lastWasAnnotation) { builder.breakToFill(" "); } else { builder.breakToFill(); } builder.op("..."); lastWasAnnotation = false; break; default: return; } } } private void declareMany(List<VariableTree> fragments, Direction annotationDirection) { builder.open(ZERO); ModifiersTree modifiers = fragments.get(0).getModifiers(); Tree type = fragments.get(0).getType(); visitAndBreakModifiers(modifiers, annotationDirection, /* declarationAnnotationBreak= */ Optional.empty()); builder.open(plusFour); builder.open(ZERO); TypeWithDims extractedDims = DimensionHelpers.extractDims(type, SortedDims.YES); Deque<List<AnnotationTree>> dims = new ArrayDeque<>(extractedDims.dims); scan(extractedDims.node, null); int baseDims = dims.size(); maybeAddDims(dims); baseDims = baseDims - dims.size(); boolean first = true; for (VariableTree fragment : fragments) { if (!first) { token(","); } TypeWithDims fragmentDims = variableFragmentDims(first, baseDims, fragment.getType()); dims = new ArrayDeque<>(fragmentDims.dims); builder.breakOp(" "); builder.open(ZERO); maybeAddDims(dims); visit(fragment.getName()); maybeAddDims(dims); ExpressionTree initializer = fragment.getInitializer(); if (initializer != null) { builder.space(); token("="); builder.open(plusFour); builder.breakOp(" "); scan(initializer, null); builder.close(); } builder.close(); if (first) { builder.close(); } first = false; } builder.close(); token(";"); builder.close(); } /** Add a list of declarations. */ void addBodyDeclarations(List<? extends Tree> 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; PeekingIterator<Tree> it = Iterators.peekingIterator(bodyDeclarations.iterator()); while (it.hasNext()) { Tree bodyDeclaration = it.next(); dropEmptyDeclarations(); builder.forcedBreak(); boolean thisOneGetsBlankLineBefore = bodyDeclaration.getKind() != VARIABLE || hasJavaDoc(bodyDeclaration); if (first) { builder.blankLineWanted(PRESERVE); } else if (!first && (thisOneGetsBlankLineBefore || lastOneGotBlankLineBefore)) { builder.blankLineWanted(YES); } markForPartialFormat(); if (bodyDeclaration.getKind() == VARIABLE) { visitVariables(variableFragments(it, bodyDeclaration), DeclarationKind.FIELD, fieldAnnotationDirection(((VariableTree) bodyDeclaration).getModifiers())); } else { scan(bodyDeclaration, null); } first = false; lastOneGotBlankLineBefore = thisOneGetsBlankLineBefore; } dropEmptyDeclarations(); builder.forcedBreak(); builder.close(); builder.forcedBreak(); markForPartialFormat(); if (braces.isYes()) { builder.blankLineWanted(BlankLineWanted.NO); token("}", plusTwo); builder.close(); } } } /** * The parser expands multi-variable declarations into separate single-variable declarations. All * of the fragments in the original declaration have the same start position, so we use that as a * signal to collect them and preserve the multi-variable declaration in the output. * * <p>e.g. {@code int x, y;} is parsed as {@code int x; int y;}. */ private List<VariableTree> variableFragments(PeekingIterator<? extends Tree> it, Tree first) { List<VariableTree> fragments = new ArrayList<>(); if (first.getKind() == VARIABLE) { int start = getStartPosition(first); fragments.add((VariableTree) first); while (it.hasNext() && it.peek().getKind() == VARIABLE && getStartPosition(it.peek()) == start) { fragments.add((VariableTree) it.next()); } } return fragments; } /** Does this declaration have javadoc preceding it? */ private boolean hasJavaDoc(Tree bodyDeclaration) { int position = ((JCTree) bodyDeclaration).getStartPosition(); Input.Token token = builder.getInput().getPositionTokenMap().get(position); if (token != null) { for (Input.Tok tok : token.getToksBefore()) { if (tok.getText().startsWith("/**")) { return true; } } } return false; } private static Optional<? extends Input.Token> getNextToken(Input input, int position) { return Optional.ofNullable(input.getPositionTokenMap().get(position)); } /** Does this list of trees end with the specified token? */ private boolean hasTrailingToken(Input input, List<? extends Tree> nodes, String token) { if (nodes.isEmpty()) { return false; } Tree lastNode = getLast(nodes); Optional<? extends Input.Token> nextToken = getNextToken(input, getEndPosition(lastNode, getCurrentPath())); return nextToken.isPresent() && nextToken.get().getTok().getText().equals(token); } /** * 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 ModifiersTree}s * @return whether the local can be declared with horizontal annotations */ private Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) { int markerAnnotations = 0; for (AnnotationTree annotation : modifiers.getAnnotations()) { if (annotation.getArguments().isEmpty()) { markerAnnotations++; } } return markerAnnotations <= 1 && markerAnnotations == modifiers.getAnnotations().size() ? 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. */ private Direction fieldAnnotationDirection(ModifiersTree modifiers) { for (AnnotationTree annotation : modifiers.getAnnotations()) { if (!annotation.getArguments().isEmpty()) { return Direction.VERTICAL; } } return Direction.HORIZONTAL; } /** * 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, /* breakAndIndentTrailingComment= */ Optional.empty()); } /** * 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, /* breakAndIndentTrailingComment= */ Optional.empty()); } /** 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.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(Tree node) { builder.sync(((JCTree) node).getStartPosition()); } final BreakTag genSym() { return new BreakTag(); } @Override public final String toString() { return MoreObjects.toStringHelper(this).add("builder", builder).toString(); } }