| /* |
| * 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 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; |
| } |
| 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)); |
| 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 Stream<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", "toBuilder").anyMatch(name::contentEquals); |
| }); |
| } |
| |
| 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; |
| } |
| } |
| |
| handleStream(items).forEach(x -> prefixes.add(x.intValue())); |
| |
| if (!prefixes.isEmpty()) { |
| visitDotWithPrefix(items, needDot, prefixes); |
| } 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) { |
| // 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 = FillMode.INDEPENDENT; |
| } 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(); |
| } |
| } |