| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
| * in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package com.google.googlejavaformat.java; |
| |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Multimap; |
| import com.google.googlejavaformat.CloseOp; |
| import com.google.googlejavaformat.Doc; |
| import com.google.googlejavaformat.Doc.FillMode; |
| import com.google.googlejavaformat.Indent; |
| import com.google.googlejavaformat.Input; |
| import com.google.googlejavaformat.Op; |
| import com.google.googlejavaformat.OpenOp; |
| import com.google.googlejavaformat.OpsBuilder; |
| import com.google.googlejavaformat.OpsBuilder.BlankLineWanted; |
| import com.google.googlejavaformat.Output.BreakTag; |
| |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.AnnotatableType; |
| import org.eclipse.jdt.core.dom.Annotation; |
| import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; |
| import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; |
| import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; |
| import org.eclipse.jdt.core.dom.ArrayAccess; |
| import org.eclipse.jdt.core.dom.ArrayCreation; |
| import org.eclipse.jdt.core.dom.ArrayInitializer; |
| import org.eclipse.jdt.core.dom.ArrayType; |
| import org.eclipse.jdt.core.dom.AssertStatement; |
| import org.eclipse.jdt.core.dom.Assignment; |
| import org.eclipse.jdt.core.dom.Block; |
| import org.eclipse.jdt.core.dom.BodyDeclaration; |
| import org.eclipse.jdt.core.dom.BooleanLiteral; |
| import org.eclipse.jdt.core.dom.BreakStatement; |
| import org.eclipse.jdt.core.dom.CastExpression; |
| import org.eclipse.jdt.core.dom.CatchClause; |
| import org.eclipse.jdt.core.dom.CharacterLiteral; |
| import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; |
| import org.eclipse.jdt.core.dom.ClassInstanceCreation; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.ConditionalExpression; |
| import org.eclipse.jdt.core.dom.ConstructorInvocation; |
| import org.eclipse.jdt.core.dom.ContinueStatement; |
| import org.eclipse.jdt.core.dom.CreationReference; |
| import org.eclipse.jdt.core.dom.Dimension; |
| import org.eclipse.jdt.core.dom.DoStatement; |
| import org.eclipse.jdt.core.dom.EmptyStatement; |
| import org.eclipse.jdt.core.dom.EnhancedForStatement; |
| import org.eclipse.jdt.core.dom.EnumConstantDeclaration; |
| import org.eclipse.jdt.core.dom.EnumDeclaration; |
| import org.eclipse.jdt.core.dom.Expression; |
| import org.eclipse.jdt.core.dom.ExpressionMethodReference; |
| import org.eclipse.jdt.core.dom.ExpressionStatement; |
| import org.eclipse.jdt.core.dom.FieldAccess; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| import org.eclipse.jdt.core.dom.ForStatement; |
| import org.eclipse.jdt.core.dom.IExtendedModifier; |
| import org.eclipse.jdt.core.dom.IfStatement; |
| import org.eclipse.jdt.core.dom.ImportDeclaration; |
| import org.eclipse.jdt.core.dom.InfixExpression; |
| import org.eclipse.jdt.core.dom.Initializer; |
| import org.eclipse.jdt.core.dom.InstanceofExpression; |
| import org.eclipse.jdt.core.dom.IntersectionType; |
| import org.eclipse.jdt.core.dom.LabeledStatement; |
| import org.eclipse.jdt.core.dom.LambdaExpression; |
| import org.eclipse.jdt.core.dom.MarkerAnnotation; |
| import org.eclipse.jdt.core.dom.MemberValuePair; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.MethodInvocation; |
| import org.eclipse.jdt.core.dom.Modifier; |
| import org.eclipse.jdt.core.dom.Name; |
| import org.eclipse.jdt.core.dom.NameQualifiedType; |
| import org.eclipse.jdt.core.dom.NormalAnnotation; |
| import org.eclipse.jdt.core.dom.NullLiteral; |
| import org.eclipse.jdt.core.dom.NumberLiteral; |
| import org.eclipse.jdt.core.dom.PackageDeclaration; |
| import org.eclipse.jdt.core.dom.ParameterizedType; |
| import org.eclipse.jdt.core.dom.ParenthesizedExpression; |
| import org.eclipse.jdt.core.dom.PostfixExpression; |
| import org.eclipse.jdt.core.dom.PrefixExpression; |
| import org.eclipse.jdt.core.dom.PrimitiveType; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.QualifiedType; |
| import org.eclipse.jdt.core.dom.ReturnStatement; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.SimpleType; |
| import org.eclipse.jdt.core.dom.SingleMemberAnnotation; |
| import org.eclipse.jdt.core.dom.SingleVariableDeclaration; |
| import org.eclipse.jdt.core.dom.Statement; |
| import org.eclipse.jdt.core.dom.StringLiteral; |
| import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; |
| import org.eclipse.jdt.core.dom.SuperConstructorInvocation; |
| import org.eclipse.jdt.core.dom.SuperFieldAccess; |
| import org.eclipse.jdt.core.dom.SuperMethodInvocation; |
| import org.eclipse.jdt.core.dom.SuperMethodReference; |
| import org.eclipse.jdt.core.dom.SwitchCase; |
| import org.eclipse.jdt.core.dom.SwitchStatement; |
| import org.eclipse.jdt.core.dom.SynchronizedStatement; |
| import org.eclipse.jdt.core.dom.ThisExpression; |
| import org.eclipse.jdt.core.dom.ThrowStatement; |
| import org.eclipse.jdt.core.dom.TryStatement; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.TypeDeclaration; |
| import org.eclipse.jdt.core.dom.TypeDeclarationStatement; |
| import org.eclipse.jdt.core.dom.TypeLiteral; |
| import org.eclipse.jdt.core.dom.TypeMethodReference; |
| import org.eclipse.jdt.core.dom.TypeParameter; |
| import org.eclipse.jdt.core.dom.UnionType; |
| import org.eclipse.jdt.core.dom.VariableDeclarationExpression; |
| import org.eclipse.jdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.jdt.core.dom.VariableDeclarationStatement; |
| import org.eclipse.jdt.core.dom.WhileStatement; |
| import org.eclipse.jdt.core.dom.WildcardType; |
| |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Deque; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * An extension of {@link OpsBuilder}, implementing a visit pattern for Eclipse AST nodes to build a |
| * sequence of {@link Op}s. |
| */ |
| @SuppressWarnings({"unchecked", "rawtypes"}) // jdt uses rawtypes extensively |
| public final class JavaInputAstVisitor extends ASTVisitor { |
| /** Direction for Annotations (usually VERTICAL). */ |
| enum Direction { |
| VERTICAL, HORIZONTAL; |
| |
| boolean isVertical() { |
| return this == VERTICAL; |
| } |
| } |
| |
| /** Whether to break or not. */ |
| enum BreakOrNot { |
| YES, NO; |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Whether to collapse empty blocks. */ |
| enum CollapseEmptyOrNot { |
| YES, NO; |
| |
| static CollapseEmptyOrNot valueOf(boolean b) { |
| return b ? YES : NO; |
| } |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Whether to allow leading blank lines in blocks. */ |
| enum AllowLeadingBlankLine { |
| YES, |
| NO; |
| |
| static AllowLeadingBlankLine valueOf(boolean b) { |
| return b ? YES : NO; |
| } |
| } |
| |
| /** Whether to allow trailing blank lines in blocks. */ |
| enum AllowTrailingBlankLine { |
| YES, NO; |
| |
| static AllowTrailingBlankLine valueOf(boolean b) { |
| return b ? YES : NO; |
| } |
| } |
| |
| /** Whether to include braces. */ |
| enum BracesOrNot { |
| YES, NO; |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Whether or not to include dimensions. */ |
| enum DimensionsOrNot { |
| YES, NO; |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Whether or not the declaration is Varargs. */ |
| enum VarArgsOrNot { |
| YES, NO; |
| |
| static VarArgsOrNot valueOf(boolean b) { |
| return b ? YES : NO; |
| } |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Whether the formal parameter declaration is a receiver. */ |
| enum ReceiverParameter { |
| YES, |
| NO; |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Whether these declarations are the first in the block. */ |
| enum FirstDeclarationsOrNot { |
| YES, NO; |
| |
| boolean isYes() { |
| return this == YES; |
| } |
| } |
| |
| /** Position in a list of declarations. */ |
| enum DeclarationPosition { |
| FIRST, |
| INTERIOR, |
| LAST; |
| |
| static EnumSet<DeclarationPosition> getPositionInParent(ASTNode node) { |
| EnumSet<DeclarationPosition> position = EnumSet.noneOf(DeclarationPosition.class); |
| StructuralPropertyDescriptor locationInParent = node.getLocationInParent(); |
| if (locationInParent instanceof ChildListPropertyDescriptor) { |
| List<ASTNode> propertyList = |
| (List<ASTNode>) node.getParent().getStructuralProperty(locationInParent); |
| int idx = propertyList.indexOf(node); |
| if (idx == 0) { |
| position.add(DeclarationPosition.FIRST); |
| } |
| if (idx == propertyList.size() - 1) { |
| position.add(DeclarationPosition.LAST); |
| } |
| if (position.isEmpty()) { |
| position.add(DeclarationPosition.INTERIOR); |
| } |
| } |
| return position; |
| } |
| } |
| |
| private final OpsBuilder builder; |
| |
| private static final Indent.Const ZERO = Indent.Const.ZERO; |
| private final int indentMultiplier; |
| private final Indent.Const minusTwo; |
| private final Indent.Const minusFour; |
| private final Indent.Const plusTwo; |
| private final Indent.Const plusFour; |
| private final Indent.Const plusEight; |
| |
| private static final ImmutableList<Op> breakList(Optional<BreakTag> breakTag) { |
| return ImmutableList.<Op>of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag)); |
| } |
| |
| private static final ImmutableList<Op> breakFillList(Optional<BreakTag> breakTag) { |
| return ImmutableList.of( |
| OpenOp.make(ZERO, 0), |
| Doc.Break.make(Doc.FillMode.INDEPENDENT, " ", ZERO, breakTag), |
| CloseOp.make()); |
| } |
| |
| private static final ImmutableList<Op> forceBreakList(Optional<BreakTag> breakTag) { |
| return ImmutableList.<Op>of(Doc.Break.make(FillMode.FORCED, "", Indent.Const.ZERO, breakTag)); |
| } |
| |
| private static final ImmutableList<Op> EMPTY_LIST = ImmutableList.of(); |
| private static final Map<String, Integer> PRECEDENCE = new HashMap<>(); |
| private static final int MAX_LINES_FOR_ARGUMENTS = 1; |
| private static final int MAX_LINES_FOR_ARRAY_INITIALIZERS = 1; |
| private static final int MAX_LINES_FOR_ANNOTATION_ELEMENT_VALUE_PAIRS = 1; |
| private static final int MAX_LINES_FOR_CHAINED_ACCESSES = 1; |
| |
| /** |
| * Allow multi-line filling (of array initializers, argument lists, and boolean |
| * expressions) for items with length less than or equal to this threshold. |
| */ |
| private static final int MAX_ITEM_LENGTH_FOR_FILLING = 10; |
| |
| /** |
| * A maxLinesFilled value for OpenOp.make that indicates one-per-line mode |
| * should never be used. |
| */ |
| private static final int ALWAYS_FILL = 0; |
| |
| static { |
| PRECEDENCE.put("*", 10); |
| PRECEDENCE.put("/", 10); |
| PRECEDENCE.put("%", 10); |
| PRECEDENCE.put("+", 9); |
| PRECEDENCE.put("-", 9); |
| PRECEDENCE.put("<<", 8); |
| PRECEDENCE.put(">>", 8); |
| PRECEDENCE.put(">>>", 8); |
| PRECEDENCE.put("<", 7); |
| PRECEDENCE.put(">", 7); |
| PRECEDENCE.put("<=", 7); |
| PRECEDENCE.put(">=", 7); |
| PRECEDENCE.put("==", 6); |
| PRECEDENCE.put("!=", 6); |
| PRECEDENCE.put("&", 5); |
| PRECEDENCE.put("^", 4); |
| PRECEDENCE.put("|", 3); |
| PRECEDENCE.put("&&", 2); |
| PRECEDENCE.put("||", 1); |
| } |
| |
| private static final int MAX_FILLED_INFIX_LINES = 1; |
| private static final int MAX_LINES_FOR_FORMAL_LIST = 1; |
| |
| private static final int MAX_EMPTY_DECLS = 10; |
| |
| /** |
| * The {@code Visitor} constructor. |
| * @param builder the {@link OpsBuilder} |
| */ |
| public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) { |
| this.builder = builder; |
| this.indentMultiplier = indentMultiplier; |
| minusTwo = Indent.Const.make(-2, indentMultiplier); |
| minusFour = Indent.Const.make(-4, indentMultiplier); |
| plusTwo = Indent.Const.make(+2, indentMultiplier); |
| plusFour = Indent.Const.make(+4, indentMultiplier); |
| plusEight = Indent.Const.make(+8, indentMultiplier); |
| } |
| |
| /** A record of whether we have visited into an expression. */ |
| private final Deque<Boolean> inExpression = new ArrayDeque<>(Arrays.asList(false)); |
| |
| private boolean inExpression() { |
| return inExpression.peekLast(); |
| } |
| |
| /** Pre-visits {@link ASTNode}s. */ |
| @Override |
| public void preVisit(ASTNode node) { |
| inExpression.addLast(node instanceof Expression || inExpression.peekLast()); |
| } |
| |
| /** Post-visits {@link ASTNode}s. */ |
| @Override |
| public void postVisit(ASTNode node) { |
| inExpression.removeLast(); |
| } |
| |
| /** Visitor method for a {@link CompilationUnit}. */ |
| @Override |
| public boolean visit(CompilationUnit node) { |
| boolean first = true; |
| if (node.getPackage() != null) { |
| markForPartialFormat(); |
| visit(node.getPackage()); |
| builder.forcedBreak(); |
| first = false; |
| } |
| if (!node.imports().isEmpty()) { |
| if (!first) { |
| builder.blankLineWanted(BlankLineWanted.YES); |
| } |
| for (ImportDeclaration importDeclaration : (List<ImportDeclaration>) node.imports()) { |
| markForPartialFormat(); |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| visit(importDeclaration); |
| builder.forcedBreak(); |
| } |
| first = false; |
| } |
| for (AbstractTypeDeclaration type : (List<AbstractTypeDeclaration>) node.types()) { |
| if (!first) { |
| builder.blankLineWanted(BlankLineWanted.YES); |
| } |
| dropEmptyDeclarations(); |
| markForPartialFormat(); |
| type.accept(this); |
| builder.forcedBreak(); |
| first = false; |
| } |
| dropEmptyDeclarations(); |
| // set a partial format marker at EOF to make sure we can format the entire file |
| markForPartialFormat(); |
| return false; |
| } |
| |
| /** Skips over extra semi-colon at the top-level, or in a class member declaration lists. */ |
| private void dropEmptyDeclarations() { |
| for (int times = 0; times < MAX_EMPTY_DECLS; times++) { |
| builder.guessToken(";"); |
| } |
| } |
| |
| /** Visitor method for {@link AnnotationTypeDeclaration}s. */ |
| @Override |
| public boolean visit(AnnotationTypeDeclaration node) { |
| sync(node); |
| builder.open(ZERO); |
| visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| builder.open(ZERO); |
| token("@"); |
| token("interface"); |
| builder.breakOp(" "); |
| visit(node.getName()); |
| builder.close(); |
| builder.close(); |
| if (node.bodyDeclarations() == null) { |
| builder.open(plusFour); |
| token(";"); |
| builder.close(); |
| } else { |
| addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); |
| } |
| builder.guessToken(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link AnnotationTypeMemberDeclaration}s. */ |
| @Override |
| public boolean visit(AnnotationTypeMemberDeclaration node) { |
| sync(node); |
| declareOne( |
| node, |
| Direction.VERTICAL, |
| node.modifiers(), |
| node.getType(), |
| VarArgsOrNot.NO, |
| ImmutableList.<Annotation>of(), |
| node.getName(), |
| "()", |
| ImmutableList.<Dimension>of(), |
| "default", |
| Optional.fromNullable(node.getDefault()), |
| Optional.of(";"), |
| ReceiverParameter.NO); |
| return false; |
| } |
| |
| /** Visitor method for {@link AnonymousClassDeclaration}s. */ |
| @Override |
| public boolean visit(AnonymousClassDeclaration node) { |
| sync(node); |
| addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); |
| return false; |
| } |
| |
| // TODO(jdd): Get rid of instanceof? |
| |
| /** Visitor method for {@link ArrayAccess}es. */ |
| @Override |
| public boolean visit(ArrayAccess node) { |
| sync(node); |
| builder.open(plusFour); |
| // Collapse chains of ArrayAccess nodes. |
| ArrayDeque<Expression> stack = new ArrayDeque<>(); |
| Expression array; |
| while (true) { |
| stack.addLast(node.getIndex()); |
| array = node.getArray(); |
| if (!(array instanceof ArrayAccess)) { |
| break; |
| } |
| node = (ArrayAccess) array; |
| } |
| array.accept(this); |
| do { |
| token("["); |
| builder.breakToFill(); |
| stack.removeLast().accept(this); |
| token("]"); |
| } while (!stack.isEmpty()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link ArrayCreation}s. */ |
| @Override |
| public boolean visit(ArrayCreation node) { |
| sync(node); |
| builder.open(plusFour); |
| token("new"); |
| builder.space(); |
| visitArrayType(node.getType(), DimensionsOrNot.NO); |
| int dimensions = node.getType().getDimensions(); |
| builder.open(ZERO); |
| for (int i = 0; i < dimensions; i++) { |
| builder.breakOp(); |
| token("["); |
| if (i < node.dimensions().size()) { |
| ((Expression) node.dimensions().get(i)).accept(this); |
| } |
| token("]"); |
| } |
| builder.close(); |
| builder.close(); |
| if (node.getInitializer() != null) { |
| builder.space(); |
| visit(node.getInitializer()); |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link ArrayInitializer}s. */ |
| @Override |
| public boolean visit(ArrayInitializer node) { |
| sync(node); |
| if (node.expressions().isEmpty()) { |
| tokenBreakTrailingComment("{", plusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token("}", plusTwo); |
| } else { |
| // Special-case the formatting of array initializers inside annotations |
| // to more eagerly use a one-per-line layout. |
| boolean inMemberValuePair = |
| node.getParent().getNodeType() == ASTNode.MEMBER_VALUE_PAIR |
| || node.getParent().getNodeType() == ASTNode.SINGLE_MEMBER_ANNOTATION; |
| boolean shortItems = hasOnlyShortItems(node.expressions()); |
| boolean allowFilledElementsOnOwnLine = shortItems || !inMemberValuePair; |
| |
| builder.open(plusTwo); |
| tokenBreakTrailingComment("{", plusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| boolean hasTrailingComma = hasTrailingToken(builder.getInput(), node.expressions(), ","); |
| builder.breakOp(hasTrailingComma ? FillMode.FORCED : FillMode.UNIFIED, "", ZERO); |
| if (allowFilledElementsOnOwnLine) { |
| builder.open(ZERO, shortItems ? ALWAYS_FILL : MAX_LINES_FOR_ARRAY_INITIALIZERS); |
| } |
| boolean first = true; |
| for (Expression expression : (List<Expression>) node.expressions()) { |
| if (!first) { |
| token(","); |
| if (allowFilledElementsOnOwnLine) { |
| builder.breakToFill(" "); |
| } else { |
| builder.breakOp(" "); |
| } |
| } |
| expression.accept(this); |
| first = false; |
| } |
| builder.guessToken(","); |
| if (allowFilledElementsOnOwnLine) { |
| builder.close(); |
| } |
| builder.breakOp(minusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| builder.close(); |
| token("}", plusTwo); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns {@code defaultThreshold} if bin-packing can be used for the given |
| * expression list, and {code NEVER_FILL} otherwise. |
| */ |
| private int maxLinesFilledForItems(List<Expression> expressions, int defaultThreshold) { |
| return hasOnlyShortItems(expressions) ? ALWAYS_FILL : defaultThreshold; |
| } |
| |
| private boolean hasOnlyShortItems(List<Expression> expressions) { |
| for (Expression expression : expressions) { |
| if (builder.actualSize(expression.getStartPosition(), expression.getLength()) |
| >= MAX_ITEM_LENGTH_FOR_FILLING) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** Visitor method for {@link ArrayType}s. */ |
| @Override |
| public boolean visit(ArrayType node) { |
| sync(node); |
| visitArrayType(node, DimensionsOrNot.YES); |
| return false; |
| } |
| |
| /** Visitor method for {@link AssertStatement}s. */ |
| @Override |
| public boolean visit(AssertStatement node) { |
| sync(node); |
| builder.open(ZERO); |
| token("assert"); |
| builder.space(); |
| builder.open(node.getMessage() == null ? ZERO : plusFour); |
| node.getExpression().accept(this); |
| if (node.getMessage() != null) { |
| builder.breakOp(" "); |
| token(":"); |
| builder.space(); |
| node.getMessage().accept(this); |
| } |
| builder.close(); |
| builder.close(); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link Assignment}s. */ |
| @Override |
| public boolean visit(Assignment node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getLeftHandSide().accept(this); |
| builder.space(); |
| builder.op(node.getOperator().toString()); |
| builder.breakOp(" "); |
| node.getRightHandSide().accept(this); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link Block}s. */ |
| @Override |
| public boolean visit(Block node) { |
| visitBlock(node, CollapseEmptyOrNot.YES, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); |
| return false; |
| } |
| |
| /** Visitor method for {@link BooleanLiteral}s. */ |
| @Override |
| public boolean visit(BooleanLiteral node) { |
| sync(node); |
| token(node.toString()); |
| return false; |
| } |
| |
| /** Visitor method for {@link BreakStatement}s. */ |
| @Override |
| public boolean visit(BreakStatement node) { |
| sync(node); |
| builder.open(plusFour); |
| token("break"); |
| if (node.getLabel() != null) { |
| builder.breakOp(" "); |
| visit(node.getLabel()); |
| } |
| builder.close(); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link CastExpression}s. */ |
| @Override |
| public boolean visit(CastExpression node) { |
| sync(node); |
| builder.open(plusFour); |
| token("("); |
| node.getType().accept(this); |
| token(")"); |
| builder.breakOp(" "); |
| node.getExpression().accept(this); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link CharacterLiteral}s. */ |
| @Override |
| public boolean visit(CharacterLiteral node) { |
| sync(node); |
| token(node.getEscapedValue()); |
| return false; |
| } |
| |
| /** Visitor method for {@link ClassInstanceCreation}s. */ |
| @Override |
| public boolean visit(ClassInstanceCreation node) { |
| sync(node); |
| builder.open(ZERO); |
| if (node.getExpression() != null) { |
| node.getExpression().accept(this); |
| builder.breakOp(); |
| token("."); |
| } |
| token("new"); |
| builder.space(); |
| addTypeArguments(node.typeArguments(), plusFour); |
| node.getType().accept(this); |
| token("("); |
| addArguments(node.arguments(), plusFour); |
| token(")"); |
| builder.close(); |
| if (node.getAnonymousClassDeclaration() != null) { |
| visit(node.getAnonymousClassDeclaration()); |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link ConditionalExpression}s. */ |
| @Override |
| public boolean visit(ConditionalExpression node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getExpression().accept(this); |
| builder.breakOp(" "); |
| token("?"); |
| builder.space(); |
| node.getThenExpression().accept(this); |
| builder.breakOp(" "); |
| token(":"); |
| builder.space(); |
| node.getElseExpression().accept(this); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link ConstructorInvocation}s. */ |
| @Override |
| public boolean visit(ConstructorInvocation node) { |
| sync(node); |
| addTypeArguments(node.typeArguments(), plusFour); |
| token("this"); |
| token("("); |
| addArguments(node.arguments(), plusFour); |
| token(")"); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link ContinueStatement}s. */ |
| @Override |
| public boolean visit(ContinueStatement node) { |
| sync(node); |
| builder.open(plusFour); |
| token("continue"); |
| if (node.getLabel() != null) { |
| builder.breakOp(" "); |
| visit(node.getLabel()); |
| } |
| token(";"); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link CreationReference}s. */ |
| @Override |
| public boolean visit(CreationReference node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getType().accept(this); |
| builder.breakOp(); |
| builder.op("::"); |
| addTypeArguments(node.typeArguments(), plusFour); |
| token("new"); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link Dimension}s. */ |
| @Override |
| public boolean visit(Dimension node) { |
| sync(node); |
| if (!node.annotations().isEmpty()) { |
| builder.open(ZERO); |
| visitAnnotations(node.annotations(), BreakOrNot.NO, BreakOrNot.NO); |
| builder.breakToFill(" "); |
| builder.close(); |
| } |
| token("["); |
| token("]"); |
| return false; |
| } |
| |
| /** Visitor method for {@link DoStatement}s. */ |
| @Override |
| public boolean visit(DoStatement node) { |
| sync(node); |
| token("do"); |
| visitStatement( |
| node.getBody(), |
| CollapseEmptyOrNot.YES, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.YES); |
| if (node.getBody().getNodeType() == ASTNode.BLOCK) { |
| builder.space(); |
| } else { |
| builder.breakOp(" "); |
| } |
| token("while"); |
| builder.space(); |
| token("("); |
| node.getExpression().accept(this); |
| token(")"); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link EmptyStatement}s. */ |
| @Override |
| public boolean visit(EmptyStatement node) { |
| sync(node); |
| builder.guessToken(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link EnhancedForStatement}s. */ |
| @Override |
| public boolean visit(EnhancedForStatement node) { |
| sync(node); |
| builder.open(ZERO); |
| token("for"); |
| builder.space(); |
| token("("); |
| builder.open(ZERO); |
| visitToDeclare( |
| Direction.HORIZONTAL, node.getParameter(), Optional.of(node.getExpression()), ":"); |
| builder.close(); |
| token(")"); |
| builder.close(); |
| visitStatement( |
| node.getBody(), |
| CollapseEmptyOrNot.YES, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.NO); |
| return false; |
| } |
| |
| /** Visitor method for {@link EnumConstantDeclaration}s. */ |
| @Override |
| public boolean visit(EnumConstantDeclaration node) { |
| sync(node); |
| List<Op> breaks = |
| visitModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| if (!breaks.isEmpty()) { |
| builder.open(ZERO); |
| builder.addAll(breaks); |
| builder.close(); |
| } |
| visit(node.getName()); |
| if (node.arguments().isEmpty()) { |
| builder.guessToken("("); |
| builder.guessToken(")"); |
| } else { |
| token("("); |
| addArguments(node.arguments(), plusFour); |
| token(")"); |
| } |
| if (node.getAnonymousClassDeclaration() != null) { |
| visit(node.getAnonymousClassDeclaration()); |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link EnumDeclaration}s. */ |
| @Override |
| public boolean visit(EnumDeclaration node) { |
| sync(node); |
| builder.open(ZERO); |
| visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| builder.open(plusFour); |
| token("enum"); |
| builder.breakOp(" "); |
| visit(node.getName()); |
| builder.close(); |
| builder.close(); |
| if (!node.superInterfaceTypes().isEmpty()) { |
| builder.open(plusFour); |
| builder.breakOp(" "); |
| builder.open(plusFour); |
| token("implements"); |
| builder.breakOp(" "); |
| builder.open(ZERO); |
| boolean first = true; |
| for (Type superInterfaceType : (List<Type>) node.superInterfaceTypes()) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| superInterfaceType.accept(this); |
| first = false; |
| } |
| builder.close(); |
| builder.close(); |
| builder.close(); |
| } |
| builder.space(); |
| tokenBreakTrailingComment("{", plusTwo); |
| if (node.enumConstants().isEmpty() && node.bodyDeclarations().isEmpty()) { |
| builder.open(ZERO); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token("}"); |
| builder.close(); |
| } else { |
| builder.open(plusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| builder.forcedBreak(); |
| builder.open(ZERO); |
| boolean first = true; |
| for (EnumConstantDeclaration enumConstant : |
| (List<EnumConstantDeclaration>) node.enumConstants()) { |
| if (!first) { |
| token(","); |
| builder.forcedBreak(); |
| } |
| visit(enumConstant); |
| first = false; |
| } |
| if (builder.peekToken().or("").equals(",")) { |
| token(","); |
| builder.forcedBreak(); // The ";" goes on its own line. |
| } |
| builder.close(); |
| builder.close(); |
| builder.open(ZERO); |
| if (node.bodyDeclarations().isEmpty()) { |
| builder.guessToken(";"); |
| } else { |
| token(";"); |
| builder.forcedBreak(); |
| addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.NO, FirstDeclarationsOrNot.NO); |
| } |
| builder.forcedBreak(); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token("}", plusTwo); |
| builder.close(); |
| } |
| builder.guessToken(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link ExpressionMethodReference}s. */ |
| @Override |
| public boolean visit(ExpressionMethodReference node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getExpression().accept(this); |
| builder.breakOp(); |
| builder.op("::"); |
| if (!node.typeArguments().isEmpty()) { |
| addTypeArguments(node.typeArguments(), plusFour); |
| } |
| visit(node.getName()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link ExpressionStatement}s. */ |
| @Override |
| public boolean visit(ExpressionStatement node) { |
| sync(node); |
| node.getExpression().accept(this); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link FieldAccess}es. */ |
| @Override |
| public boolean visit(FieldAccess node) { |
| sync(node); |
| visitDot(node); |
| return false; |
| } |
| |
| /** Visitor method for {@link FieldDeclaration}s. */ |
| @Override |
| public boolean visit(FieldDeclaration node) { |
| sync(node); |
| markForPartialFormat(); |
| addDeclaration( |
| node, |
| node.modifiers(), |
| node.getType(), |
| node.fragments(), |
| fieldAnnotationDirection(node.modifiers())); |
| return false; |
| } |
| |
| /** Visitor method for {@link ForStatement}s. */ |
| @Override |
| public boolean visit(ForStatement node) { |
| sync(node); |
| token("for"); |
| builder.space(); |
| token("("); |
| builder.open(plusFour); |
| builder.open(node.initializers().size() <= 1 ? ZERO : plusFour); |
| boolean first = true; |
| for (Expression initializer : (List<Expression>) node.initializers()) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| initializer.accept(this); |
| first = false; |
| } |
| builder.close(); |
| token(";"); |
| builder.breakOp(" "); |
| if (node.getExpression() != null) { |
| node.getExpression().accept(this); |
| } |
| token(";"); |
| builder.breakOp(" "); |
| if (!node.updaters().isEmpty()) { |
| builder.open(node.updaters().size() <= 1 ? ZERO : plusFour); |
| boolean firstUpdater = true; |
| for (Expression updater : (List<Expression>) node.updaters()) { |
| if (!firstUpdater) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| updater.accept(this); |
| firstUpdater = false; |
| } |
| builder.close(); |
| } |
| builder.close(); |
| token(")"); |
| visitStatement( |
| node.getBody(), |
| CollapseEmptyOrNot.YES, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.NO); |
| return false; |
| } |
| |
| /** Visitor method for {@link IfStatement}s. */ |
| @Override |
| public boolean visit(IfStatement node) { |
| sync(node); |
| // Collapse chains of else-ifs. |
| List<Expression> expressions = new ArrayList<>(); |
| List<Statement> statements = new ArrayList<>(); |
| while (true) { |
| expressions.add(node.getExpression()); |
| statements.add(node.getThenStatement()); |
| if (node.getElseStatement() != null |
| && node.getElseStatement().getNodeType() == ASTNode.IF_STATEMENT) { |
| node = (IfStatement) node.getElseStatement(); |
| } else { |
| break; |
| } |
| } |
| builder.open(ZERO); |
| boolean first = true; |
| boolean followingBlock = false; |
| int expressionsN = expressions.size(); |
| for (int i = 0; i < expressionsN; i++) { |
| if (!first) { |
| if (followingBlock) { |
| builder.space(); |
| } else { |
| builder.forcedBreak(); |
| } |
| token("else"); |
| builder.space(); |
| } |
| token("if"); |
| builder.space(); |
| token("("); |
| expressions.get(i).accept(this); |
| token(")"); |
| // An empty block can collapse to "{}" if there are no if/else or else clauses |
| boolean onlyClause = expressionsN == 1 && node.getElseStatement() == null; |
| // Trailing blank lines are permitted if this isn't the last clause |
| boolean trailingClauses = i < expressionsN - 1 || node.getElseStatement() != null; |
| visitStatement( |
| statements.get(i), |
| CollapseEmptyOrNot.valueOf(onlyClause), |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.valueOf(trailingClauses)); |
| followingBlock = statements.get(i).getNodeType() == ASTNode.BLOCK; |
| first = false; |
| } |
| if (node.getElseStatement() != null) { |
| if (followingBlock) { |
| builder.space(); |
| } else { |
| builder.forcedBreak(); |
| } |
| token("else"); |
| visitStatement( |
| node.getElseStatement(), |
| CollapseEmptyOrNot.NO, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.NO); |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link ImportDeclaration}s. */ |
| @Override |
| public boolean visit(ImportDeclaration node) { |
| sync(node); |
| token("import"); |
| builder.space(); |
| if (node.isStatic()) { |
| token("static"); |
| builder.space(); |
| } |
| visitName(node.getName(), BreakOrNot.NO); |
| if (node.isOnDemand()) { |
| token("."); |
| token("*"); |
| } |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link InfixExpression}s. */ |
| @Override |
| public boolean visit(InfixExpression node) { |
| sync(node); |
| /* |
| * Collect together all operators with same precedence to clean up indentation. Eclipse's |
| * extended operands help a little (to collect together the same operator), but they're applied |
| * inconsistently, and don't apply to other operators of the same precedence. |
| */ |
| List<Expression> operands = new ArrayList<>(); |
| List<String> operators = new ArrayList<>(); |
| walkInfix(PRECEDENCE.get(node.getOperator().toString()), node, operands, operators); |
| builder.open(plusFour, maxLinesFilledForItems(operands, MAX_FILLED_INFIX_LINES)); |
| operands.get(0).accept(this); |
| int operatorsN = operators.size(); |
| for (int i = 0; i < operatorsN; i++) { |
| builder.breakToFill(" "); |
| builder.op(operators.get(i)); |
| builder.space(); |
| operands.get(i + 1).accept(this); |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link Initializer}s. */ |
| @Override |
| public boolean visit(Initializer node) { |
| sync(node); |
| visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| node.getBody().accept(this); |
| builder.guessToken(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link InstanceofExpression}s. */ |
| @Override |
| public boolean visit(InstanceofExpression node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getLeftOperand().accept(this); |
| builder.breakOp(" "); |
| builder.open(ZERO); |
| token("instanceof"); |
| builder.breakOp(" "); |
| node.getRightOperand().accept(this); |
| builder.close(); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link IntersectionType}s. */ |
| @Override |
| public boolean visit(IntersectionType node) { |
| sync(node); |
| builder.open(plusFour); |
| List<Type> types = new ArrayList<>(); |
| walkIntersectionTypes(types, node); |
| boolean first = true; |
| for (Type type : types) { |
| if (!first) { |
| builder.breakToFill(" "); |
| token("&"); |
| builder.space(); |
| } |
| type.accept(this); |
| first = false; |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link LabeledStatement}s. */ |
| @Override |
| public boolean visit(LabeledStatement node) { |
| sync(node); |
| builder.open(ZERO); |
| visit(node.getLabel()); |
| token(":"); |
| builder.forcedBreak(); |
| builder.close(); |
| node.getBody().accept(this); |
| return false; |
| } |
| |
| /** Visitor method for {@link LambdaExpression}s. */ |
| @Override |
| public boolean visit(LambdaExpression node) { |
| sync(node); |
| boolean statementBody = node.getBody().getNodeType() == ASTNode.BLOCK; |
| builder.open(statementBody ? ZERO : plusFour); |
| builder.open(plusFour); |
| if (node.hasParentheses()) { |
| token("("); |
| } |
| boolean first = true; |
| for (ASTNode parameter : (List<ASTNode>) node.parameters()) { |
| if (!first) { |
| token(","); |
| builder.breakOp(" "); |
| } |
| parameter.accept(this);; |
| first = false; |
| } |
| if (node.hasParentheses()) { |
| token(")"); |
| } |
| builder.close(); |
| builder.space(); |
| builder.op("->"); |
| if (statementBody) { |
| builder.space(); |
| } else { |
| builder.breakOp(" "); |
| } |
| node.getBody().accept(this); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link MarkerAnnotation}s. */ |
| @Override |
| public boolean visit(MarkerAnnotation node) { |
| sync(node); |
| builder.open(ZERO); |
| token("@"); |
| node.getTypeName().accept(this); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link MemberValuePair}s. */ |
| @Override |
| public boolean visit(MemberValuePair node) { |
| boolean isArrayInitializer = node.getValue().getNodeType() == ASTNode.ARRAY_INITIALIZER; |
| sync(node); |
| builder.open(isArrayInitializer ? ZERO : plusFour); |
| visit(node.getName()); |
| builder.space(); |
| token("="); |
| if (isArrayInitializer) { |
| builder.space(); |
| } else { |
| builder.breakOp(" "); |
| } |
| node.getValue().accept(this); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link MethodDeclaration}s. */ |
| @Override |
| public boolean visit(MethodDeclaration node) { |
| sync(node); |
| visitAndBreakModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| |
| builder.open(plusFour); |
| { |
| BreakTag breakBeforeName = genSym(); |
| BreakTag breakBeforeType = genSym(); |
| builder.open(ZERO); |
| { |
| boolean first = true; |
| if (!node.typeParameters().isEmpty()) { |
| visitTypeParameters(node.typeParameters(), ZERO, BreakOrNot.NO); |
| first = false; |
| } |
| |
| boolean openedNameAndTypeScope = false; |
| if (!node.isConstructor()) { |
| if (!first) { |
| builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, |
| Optional.of(breakBeforeType)); |
| } else { |
| first = false; |
| } |
| if (!openedNameAndTypeScope) { |
| builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO)); |
| openedNameAndTypeScope = true; |
| } |
| if (node.getReturnType2() != null) { |
| node.getReturnType2().accept(this); |
| } |
| } |
| if (!first) { |
| builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, |
| Optional.of(breakBeforeName)); |
| } else { |
| first = false; |
| } |
| if (!openedNameAndTypeScope) { |
| builder.open(ZERO); |
| openedNameAndTypeScope = true; |
| } |
| visit(node.getName()); |
| token("("); |
| // end of name and type scope |
| builder.close(); |
| } |
| builder.close(); |
| |
| builder.open(Indent.If.make(breakBeforeName, plusFour, ZERO)); |
| builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO)); |
| builder.open(ZERO); |
| { |
| if (!node.parameters().isEmpty() || node.getReceiverType() != null) { |
| // Break before args. |
| builder.breakToFill(""); |
| visitFormals( |
| node, |
| Optional.fromNullable(node.getReceiverType()), |
| node.getReceiverQualifier(), |
| node.parameters()); |
| } |
| token(")"); |
| extraDimensions(plusFour, node.extraDimensions()); |
| if (!node.thrownExceptionTypes().isEmpty()) { |
| builder.breakToFill(" "); |
| builder.open(plusFour); |
| { |
| visitThrowsClause(node.thrownExceptionTypes()); |
| } |
| builder.close(); |
| } |
| } |
| builder.close(); |
| builder.close(); |
| builder.close(); |
| } |
| builder.close(); |
| |
| if (node.getBody() == null) { |
| token(";"); |
| } else { |
| builder.space(); |
| visitBlock( |
| node.getBody(), |
| CollapseEmptyOrNot.YES, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.NO); |
| } |
| builder.guessToken(";"); |
| |
| return false; |
| } |
| |
| /** Visitor method for {@link MethodInvocation}s. */ |
| @Override |
| public boolean visit(MethodInvocation node) { |
| sync(node); |
| visitDot(node); |
| return false; |
| } |
| |
| /** Visitor method for {@link Modifier}s. */ |
| @Override |
| public boolean visit(Modifier node) { |
| sync(node); |
| token(node.toString()); |
| return false; |
| } |
| |
| // TODO(jdd): Collapse chains of "." operators here too. |
| |
| /** Visitor method for {@link NameQualifiedType}s. */ |
| @Override |
| public boolean visit(NameQualifiedType node) { |
| sync(node); |
| beforeAnnotatableType(node); |
| builder.open(plusFour); |
| node.getQualifier().accept(this); |
| builder.breakOp(); |
| token("."); |
| visit(node.getName()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link NormalAnnotation}s. */ |
| @Override |
| public boolean visit(NormalAnnotation node) { |
| sync(node); |
| builder.open(ZERO); |
| token("@"); |
| node.getTypeName().accept(this); |
| builder.open(plusTwo, MAX_LINES_FOR_ANNOTATION_ELEMENT_VALUE_PAIRS); |
| token("("); |
| builder.breakOp(); |
| boolean first = true; |
| |
| // Format the member value pairs one-per-line if any of them are |
| // initialized with arrays. |
| boolean hasArrayInitializer = false; |
| for (MemberValuePair value : (List<MemberValuePair>) node.values()) { |
| if (value.getValue().getNodeType() == ASTNode.ARRAY_INITIALIZER) { |
| hasArrayInitializer = true; |
| break; |
| } |
| } |
| |
| for (MemberValuePair value : (List<MemberValuePair>) node.values()) { |
| if (!first) { |
| token(","); |
| if (hasArrayInitializer) { |
| builder.forcedBreak(); |
| } else { |
| builder.breakOp(" "); |
| } |
| } |
| value.accept(this); |
| first = false; |
| } |
| builder.breakOp(FillMode.UNIFIED, "", minusTwo, Optional.<BreakTag>absent()); |
| token(")"); |
| builder.close(); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link NullLiteral}s. */ |
| @Override |
| public boolean visit(NullLiteral node) { |
| sync(node); |
| token(node.toString()); |
| return false; |
| } |
| |
| /** Visitor method for {@link NumberLiteral}s. */ |
| @Override |
| public boolean visit(NumberLiteral node) { |
| sync(node); |
| String value = node.getToken(); |
| if (value.startsWith("-")) { |
| // jdt normally parses negative numeric literals as a unary minus on an |
| // unsigned literal, but in the case of Long.MIN_VALUE and |
| // Integer.MIN_VALUE it creates a signed literal without a unary minus |
| // expression. |
| // |
| // Unfortunately in both cases the input token stream will still have |
| // the '-' token followed by an unsigned numeric literal token. We |
| // hack around this by checking for a leading '-' in the text of the |
| // given numeric literal, and emitting two separate tokens if it is |
| // present to match the input stream. |
| token("-"); |
| value = value.substring(1); |
| } |
| token(value); |
| return false; |
| } |
| |
| /** Visitor method for {@link PackageDeclaration}s. */ |
| @Override |
| public boolean visit(PackageDeclaration node) { |
| sync(node); |
| visitAndBreakModifiers(node.annotations(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| builder.open(plusFour); |
| token("package"); |
| builder.space(); |
| visitName(node.getName(), BreakOrNot.NO); |
| builder.close(); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link ParameterizedType}s. */ |
| @Override |
| public boolean visit(ParameterizedType node) { |
| sync(node); |
| if (node.typeArguments().isEmpty()) { |
| node.getType().accept(this); |
| token("<"); |
| token(">"); |
| } else { |
| builder.open(plusFour); |
| node.getType().accept(this); |
| token("<"); |
| builder.breakOp(); |
| builder.open(ZERO); |
| boolean first = true; |
| for (Type typeArgument : (List<Type>) node.typeArguments()) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| typeArgument.accept(this); |
| first = false; |
| } |
| builder.close(); |
| builder.close(); |
| token(">"); |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link ParenthesizedExpression}s. */ |
| @Override |
| public boolean visit(ParenthesizedExpression node) { |
| sync(node); |
| token("("); |
| node.getExpression().accept(this); |
| token(")"); |
| return false; |
| } |
| |
| /** Visitor method for {@link PostfixExpression}s. */ |
| @Override |
| public boolean visit(PostfixExpression node) { |
| sync(node); |
| node.getOperand().accept(this); |
| builder.op(node.getOperator().toString()); |
| return false; |
| } |
| |
| /** Visitor method for {@link PrefixExpression}s. */ |
| @Override |
| public boolean visit(PrefixExpression node) { |
| sync(node); |
| String op = node.getOperator().toString(); |
| builder.op(op); |
| // Keep prefixes unambiguous. |
| Expression operand = node.getOperand(); |
| if ((op.equals("+") || op.equals("-")) && operand.getNodeType() == ASTNode.PREFIX_EXPRESSION |
| && ((PrefixExpression) operand).getOperator().toString().startsWith(op)) { |
| builder.space(); |
| } |
| operand.accept(this); |
| return false; |
| } |
| |
| /** Visitor method for {@link PrimitiveType}s. */ |
| @Override |
| public boolean visit(PrimitiveType node) { |
| sync(node); |
| beforeAnnotatableType(node); |
| token(node.toString()); |
| return false; |
| } |
| |
| /** Visitor method for {@link QualifiedName}s. */ |
| @Override |
| public boolean visit(QualifiedName node) { |
| visitQualifiedName(node, BreakOrNot.YES); |
| return false; |
| } |
| |
| // TODO(jdd): Can we share? |
| |
| /** Visitor method for {@link QualifiedType}s. */ |
| @Override |
| public boolean visit(QualifiedType node) { |
| sync(node); |
| builder.open(plusFour); |
| // Collapse chains of "." operators. |
| ArrayDeque<SimpleName> stack = new ArrayDeque<>(); |
| Type qualifier; |
| while (true) { |
| stack.add(node.getName()); |
| qualifier = node.getQualifier(); |
| if (qualifier.getNodeType() != ASTNode.QUALIFIED_TYPE) { |
| break; |
| } |
| node = (QualifiedType) qualifier; |
| } |
| qualifier.accept(this); |
| do { |
| builder.breakOp(); |
| token("."); |
| visit(stack.removeLast()); |
| } while (!stack.isEmpty()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link ReturnStatement}s. */ |
| @Override |
| public boolean visit(ReturnStatement node) { |
| sync(node); |
| token("return"); |
| if (node.getExpression() != null) { |
| builder.space(); |
| node.getExpression().accept(this); |
| } |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link SimpleName}s. */ |
| @Override |
| public boolean visit(SimpleName node) { |
| sync(node); |
| token(node.getIdentifier()); |
| return false; |
| } |
| |
| /** Visitor method for {@link SimpleType}s. */ |
| @Override |
| public boolean visit(SimpleType node) { |
| sync(node); |
| beforeAnnotatableType(node); |
| node.getName().accept(this); |
| return false; |
| } |
| |
| /** Visitor method for {@link SingleMemberAnnotation}s. */ |
| @Override |
| public boolean visit(SingleMemberAnnotation node) { |
| sync(node); |
| Expression value = node.getValue(); |
| boolean isArrayInitializer = value.getNodeType() == ASTNode.ARRAY_INITIALIZER; |
| builder.open(isArrayInitializer ? ZERO : plusFour); |
| token("@"); |
| node.getTypeName().accept(this); |
| token("("); |
| if (!isArrayInitializer) { |
| builder.breakOp(); |
| } |
| value.accept(this); |
| builder.close(); |
| token(")"); |
| return false; |
| } |
| |
| /** Visitor method for {@link SingleVariableDeclaration}s. */ |
| @Override |
| public boolean visit(SingleVariableDeclaration node) { |
| visitToDeclare(Direction.HORIZONTAL, node, Optional.fromNullable(node.getInitializer()), "="); |
| return false; |
| } |
| |
| /** Visitor method for {@link StringLiteral}s. */ |
| @Override |
| public boolean visit(StringLiteral node) { |
| sync(node); |
| token(node.getEscapedValue()); |
| return false; |
| } |
| |
| /** Visitor method for {@link SuperConstructorInvocation}s. */ |
| @Override |
| public boolean visit(SuperConstructorInvocation node) { |
| sync(node); |
| if (node.getExpression() != null) { |
| node.getExpression().accept(this); |
| token("."); |
| } |
| addTypeArguments(node.typeArguments(), plusFour); |
| token("super"); |
| token("("); |
| addArguments(node.arguments(), plusFour); |
| token(")"); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link SuperFieldAccess}es. */ |
| @Override |
| public boolean visit(SuperFieldAccess node) { |
| sync(node); |
| builder.open(plusFour); |
| if (node.getQualifier() != null) { |
| node.getQualifier().accept(this); |
| builder.breakOp(); |
| token("."); |
| } |
| token("super"); |
| builder.breakOp(); |
| token("."); |
| visit(node.getName()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link SuperMethodInvocation}s. */ |
| @Override |
| public boolean visit(SuperMethodInvocation node) { |
| sync(node); |
| builder.open(ZERO); |
| if (node.getQualifier() != null) { |
| node.getQualifier().accept(this); |
| builder.breakOp(); |
| token("."); |
| } |
| token("super"); |
| builder.breakOp(); |
| token("."); |
| builder.close(); |
| addTypeArguments(node.typeArguments(), plusFour); |
| visit(node.getName()); |
| token("("); |
| addArguments(node.arguments(), plusFour); |
| token(")"); |
| return false; |
| } |
| |
| /** Visitor method for {@link SuperMethodReference}s. */ |
| @Override |
| public boolean visit(SuperMethodReference node) { |
| sync(node); |
| builder.open(plusFour); |
| if (node.getQualifier() != null) { |
| builder.open(plusFour); |
| node.getQualifier().accept(this); |
| builder.breakOp(); |
| token("."); |
| builder.close(); |
| } |
| token("super"); |
| builder.breakOp(); |
| builder.op("::"); |
| if (!node.typeArguments().isEmpty()) { |
| addTypeArguments(node.typeArguments(), plusFour); |
| } |
| visit(node.getName()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link SwitchCase}s. */ |
| @Override |
| public boolean visit(SwitchCase node) { |
| sync(node); |
| markForPartialFormat(); |
| if (node.isDefault()) { |
| token("default", plusTwo); |
| token(":"); |
| } else { |
| token("case", plusTwo); |
| builder.space(); |
| node.getExpression().accept(this); |
| token(":"); |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link SwitchStatement}s. */ |
| @Override |
| public boolean visit(SwitchStatement node) { |
| sync(node); |
| token("switch"); |
| builder.space(); |
| token("("); |
| node.getExpression().accept(this); |
| token(")"); |
| builder.space(); |
| tokenBreakTrailingComment("{", plusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| builder.open(plusFour); |
| boolean first = true; |
| boolean lastWasSwitchCase = false; |
| for (ASTNode statement : (List<ASTNode>) node.statements()) { |
| if (!first && !lastWasSwitchCase) { |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| } |
| if (statement.getNodeType() == ASTNode.SWITCH_CASE) { |
| builder.open(minusTwo); |
| builder.forcedBreak(); |
| visit((SwitchCase) statement); |
| builder.close(); |
| lastWasSwitchCase = true; |
| } else { |
| builder.forcedBreak(); |
| statement.accept(this); |
| lastWasSwitchCase = false; |
| } |
| first = false; |
| } |
| builder.close(); |
| builder.forcedBreak(); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token("}", plusFour); |
| return false; |
| } |
| |
| /** Visitor method for {@link SynchronizedStatement}s. */ |
| @Override |
| public boolean visit(SynchronizedStatement node) { |
| sync(node); |
| token("synchronized"); |
| builder.space(); |
| token("("); |
| builder.open(plusFour); |
| builder.breakOp(); |
| node.getExpression().accept(this); |
| builder.close(); |
| token(")"); |
| builder.space(); |
| node.getBody().accept(this); |
| return false; |
| } |
| |
| /** Visitor method for {@link ThisExpression}s. */ |
| @Override |
| public boolean visit(ThisExpression node) { |
| sync(node); |
| if (node.getQualifier() != null) { |
| builder.open(plusFour); |
| node.getQualifier().accept(this); |
| builder.breakOp(); |
| token("."); |
| builder.close(); |
| } |
| token("this"); |
| return false; |
| } |
| |
| /** Visitor method for {@link ThrowStatement}s. */ |
| @Override |
| public boolean visit(ThrowStatement node) { |
| sync(node); |
| token("throw"); |
| builder.space(); |
| node.getExpression().accept(this); |
| token(";"); |
| return false; |
| } |
| |
| /** Visitor method for {@link TryStatement}s. */ |
| @Override |
| public boolean visit(TryStatement node) { |
| sync(node); |
| builder.open(ZERO); |
| token("try"); |
| builder.space(); |
| if (!node.resources().isEmpty()) { |
| token("("); |
| builder.open(plusFour); |
| boolean first = true; |
| for (VariableDeclarationExpression resource : |
| (List<VariableDeclarationExpression>) node.resources()) { |
| if (!first) { |
| token(";"); |
| builder.forcedBreak(); |
| } |
| visit(resource); |
| first = false; |
| } |
| // TODO(cushon): emit a space after the optional trailing semi-colon |
| builder.guessToken(";"); |
| token(")"); |
| builder.close(); |
| builder.space(); |
| } |
| // An empty try-with-resources body can collapse to "{}" if there are no trailing catch or |
| // finally blocks. |
| boolean trailingClauses = !node.catchClauses().isEmpty() || node.getFinally() != null; |
| visitBlock( |
| node.getBody(), |
| CollapseEmptyOrNot.valueOf(!trailingClauses), |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.valueOf(trailingClauses)); |
| for (int i = 0; i < node.catchClauses().size(); i++) { |
| CatchClause catchClause = (CatchClause) node.catchClauses().get(i); |
| trailingClauses = i < node.catchClauses().size() - 1 || node.getFinally() != null; |
| visitCatchClause(catchClause, AllowTrailingBlankLine.valueOf(trailingClauses)); |
| } |
| if (node.getFinally() != null) { |
| builder.space(); |
| token("finally"); |
| builder.space(); |
| visitBlock( |
| node.getFinally(), |
| CollapseEmptyOrNot.NO, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.NO); |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link TypeDeclaration}s. */ |
| @Override |
| public boolean visit(TypeDeclaration node) { |
| sync(node); |
| List<Op> breaks = |
| visitModifiers(node.modifiers(), Direction.VERTICAL, Optional.<BreakTag>absent()); |
| boolean hasSuperclassType = node.getSuperclassType() != null; |
| boolean hasSuperInterfaceTypes = !node.superInterfaceTypes().isEmpty(); |
| builder.open(ZERO); |
| builder.addAll(breaks); |
| token(node.isInterface() ? "interface" : "class"); |
| builder.space(); |
| visit(node.getName()); |
| if (!node.typeParameters().isEmpty()) { |
| visitTypeParameters( |
| node.typeParameters(), hasSuperclassType || hasSuperInterfaceTypes ? plusFour : ZERO, |
| BreakOrNot.YES); |
| } |
| if (hasSuperclassType || hasSuperInterfaceTypes) { |
| builder.open(plusFour); |
| if (hasSuperclassType) { |
| builder.breakToFill(" "); |
| token("extends"); |
| // TODO(b/20761216): using a non-breaking space here could cause >100 char lines |
| builder.space(); |
| node.getSuperclassType().accept(this); |
| } |
| if (hasSuperInterfaceTypes) { |
| builder.breakToFill(" "); |
| builder.open(node.superInterfaceTypes().size() > 1 ? plusFour : ZERO); |
| token(node.isInterface() ? "extends" : "implements"); |
| builder.space(); |
| boolean first = true; |
| for (Type superInterfaceType : (List<Type>) node.superInterfaceTypes()) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| superInterfaceType.accept(this); |
| first = false; |
| } |
| builder.close(); |
| } |
| builder.close(); |
| } |
| builder.close(); |
| if (node.bodyDeclarations() == null) { |
| token(";"); |
| } else { |
| addBodyDeclarations(node.bodyDeclarations(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); |
| builder.guessToken(";"); |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link TypeDeclarationStatement}s. */ |
| @Override |
| public boolean visit(TypeDeclarationStatement node) { |
| sync(node); |
| node.getDeclaration().accept(this); |
| return false; |
| } |
| |
| /** Visitor method for {@link TypeLiteral}s. */ |
| @Override |
| public boolean visit(TypeLiteral node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getType().accept(this); |
| builder.breakOp(); |
| token("."); |
| token("class"); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link TypeMethodReference}s. */ |
| @Override |
| public boolean visit(TypeMethodReference node) { |
| sync(node); |
| builder.open(plusFour); |
| node.getType().accept(this); |
| builder.breakOp(); |
| builder.op("::"); |
| if (!node.typeArguments().isEmpty()) { |
| addTypeArguments(node.typeArguments(), plusFour); |
| } |
| visit(node.getName()); |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link TypeParameter}s. */ |
| @Override |
| public boolean visit(TypeParameter node) { |
| sync(node); |
| builder.open(ZERO); |
| visitAndBreakModifiers(node.modifiers(), Direction.HORIZONTAL, Optional.<BreakTag>absent()); |
| visit(node.getName()); |
| if (!node.typeBounds().isEmpty()) { |
| builder.space(); |
| token("extends"); |
| builder.open(plusFour); |
| builder.breakOp(" "); |
| builder.open(plusFour); |
| boolean first = true; |
| for (Type typeBound : (List<Type>) node.typeBounds()) { |
| if (!first) { |
| builder.breakToFill(" "); |
| token("&"); |
| builder.space(); |
| } |
| typeBound.accept(this); |
| first = false; |
| } |
| builder.close(); |
| builder.close(); |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link UnionType}s. */ |
| @Override |
| public boolean visit(UnionType node) { |
| sync(node); |
| builder.open(plusFour); |
| List<Type> types = new ArrayList<>(); |
| walkUnionTypes(types, node); |
| boolean first = true; |
| for (Type type : types) { |
| if (!first) { |
| builder.breakOp(" "); |
| token("|"); |
| builder.space(); |
| } |
| type.accept(this); |
| first = false; |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link VariableDeclarationExpression}s. */ |
| @Override |
| public boolean visit(VariableDeclarationExpression node) { |
| sync(node); |
| builder.open(plusFour); |
| // TODO(jdd): Why no use common method? |
| for (IExtendedModifier modifier : (List<IExtendedModifier>) node.modifiers()) { |
| ((ASTNode) modifier).accept(this); |
| builder.breakToFill(" "); |
| } |
| node.getType().accept(this); |
| if (node.fragments().size() == 1) { |
| builder.breakToFill(" "); |
| visit((VariableDeclarationFragment) node.fragments().get(0)); |
| } else { |
| // TODO(jdd): Are the indentations consistent here? |
| builder.breakToFill(" "); |
| builder.open(plusFour); |
| boolean first = true; |
| for (VariableDeclarationFragment fragment : |
| (List<VariableDeclarationFragment>) node.fragments()) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| visit(fragment); |
| first = false; |
| } |
| builder.close(); |
| } |
| builder.close(); |
| return false; |
| } |
| |
| /** Visitor method for {@link VariableDeclarationFragment}s. */ |
| @Override |
| public boolean visit(VariableDeclarationFragment node) { |
| sync(node); |
| // TODO(jdd): Why no open-close? |
| visit(node.getName()); |
| extraDimensions(plusFour, node.extraDimensions()); |
| if (node.getInitializer() != null) { |
| builder.space(); |
| token("="); |
| builder.breakToFill(" "); |
| // TODO(jdd): Why this way. |
| builder.open(ZERO); |
| node.getInitializer().accept(this); |
| builder.close(); |
| } |
| return false; |
| } |
| |
| // TODO(jdd): Worry about upper and lower bounds. |
| |
| /** Visitor method for {@link VariableDeclarationStatement}s. */ |
| @Override |
| public boolean visit(VariableDeclarationStatement node) { |
| sync(node); |
| addDeclaration( |
| node, |
| node.modifiers(), node.getType(), node.fragments(), |
| canLocalHaveHorizontalAnnotations(node.modifiers())); |
| return false; |
| } |
| |
| /** Visitor method for {@link WhileStatement}s. */ |
| @Override |
| public boolean visit(WhileStatement node) { |
| sync(node); |
| token("while"); |
| builder.space(); |
| token("("); |
| node.getExpression().accept(this); |
| token(")"); |
| visitStatement( |
| node.getBody(), |
| CollapseEmptyOrNot.YES, |
| AllowLeadingBlankLine.YES, |
| AllowTrailingBlankLine.NO); |
| return false; |
| } |
| |
| /** Visitor method for {@link WildcardType}s. */ |
| @Override |
| public boolean visit(WildcardType node) { |
| sync(node); |
| beforeAnnotatableType(node); |
| builder.open(ZERO); |
| token("?"); |
| if (node.getBound() != null) { |
| builder.open(plusFour); |
| builder.space(); |
| token(node.isUpperBound() ? "extends" : "super"); |
| builder.breakOp(" "); |
| node.getBound().accept(this); |
| builder.close(); |
| } |
| builder.close(); |
| return false; |
| } |
| |
| // Helper methods. |
| |
| /** Before Visitor methods for {@link Type}. */ |
| private void beforeAnnotatableType(AnnotatableType node) { |
| if (!node.annotations().isEmpty()) { |
| builder.open(ZERO); |
| for (Annotation annotation : (List<Annotation>) node.annotations()) { |
| annotation.accept(this); |
| builder.breakOp(" "); |
| } |
| builder.close(); |
| } |
| } |
| |
| /** Helper method for {@link Annotation}s and declareOne. */ |
| void visitAnnotations( |
| List<Annotation> annotations, BreakOrNot breakBefore, BreakOrNot breakAfter) { |
| if (!annotations.isEmpty()) { |
| if (breakBefore.isYes()) { |
| builder.breakToFill(" "); |
| } |
| boolean first = true; |
| for (Annotation annotation : annotations) { |
| if (!first) { |
| builder.breakToFill(" "); |
| } |
| annotation.accept(this); |
| first = false; |
| } |
| if (breakAfter.isYes()) { |
| builder.breakToFill(" "); |
| } |
| } |
| } |
| |
| /** |
| * Helper method for {@link Block}s, {@link CatchClause}s, {@link Statement}s, |
| * {@link TryStatement}s, and {@link WhileStatement}s. |
| */ |
| private void visitBlock( |
| Block node, |
| CollapseEmptyOrNot collapseEmptyOrNot, |
| AllowLeadingBlankLine allowLeadingBlankLine, |
| AllowTrailingBlankLine allowTrailingBlankLine) { |
| sync(node); |
| if (collapseEmptyOrNot.isYes() && node.statements().isEmpty()) { |
| tokenBreakTrailingComment("{", plusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token("}", plusTwo); |
| } else { |
| builder.open(ZERO); |
| builder.open(plusTwo); |
| tokenBreakTrailingComment("{", plusTwo); |
| if (allowLeadingBlankLine == AllowLeadingBlankLine.NO) { |
| builder.blankLineWanted(BlankLineWanted.NO); |
| } else { |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| } |
| boolean first = true; |
| for (Statement statement : (List<Statement>) node.statements()) { |
| builder.forcedBreak(); |
| if (!first) { |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| } |
| first = false; |
| markForPartialFormat(); |
| statement.accept(this); |
| } |
| builder.close(); |
| builder.forcedBreak(); |
| builder.close(); |
| if (allowTrailingBlankLine == AllowTrailingBlankLine.NO) { |
| builder.blankLineWanted(BlankLineWanted.NO); |
| } else { |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| } |
| markForPartialFormat(); |
| token("}", plusTwo); |
| } |
| } |
| |
| /** |
| * Helper method for {@link DoStatement}s, {@link EnhancedForStatement}s, {@link ForStatement}s, |
| * {@link IfStatement}s, and WhileStatements. |
| */ |
| private void visitStatement( |
| Statement node, |
| CollapseEmptyOrNot collapseEmptyOrNot, |
| AllowLeadingBlankLine allowLeadingBlank, |
| AllowTrailingBlankLine allowTrailingBlank) { |
| sync(node); |
| switch (node.getNodeType()) { |
| case ASTNode.BLOCK: |
| builder.space(); |
| visitBlock((Block) node, collapseEmptyOrNot, allowLeadingBlank, allowTrailingBlank); |
| break; |
| default: |
| // TODO(jdd): Fix. |
| builder.open(plusTwo); |
| builder.breakOp(" "); |
| node.accept(this); |
| builder.close(); |
| } |
| } |
| |
| /** Helper method for {@link ArrayCreation}s and {@link ArrayType}s. */ |
| private void visitArrayType(ArrayType node, DimensionsOrNot includeDimensions) { |
| if (includeDimensions.isYes() && !node.dimensions().isEmpty()) { |
| builder.open(plusFour); |
| } |
| node.getElementType().accept(this); |
| if (includeDimensions.isYes()) { |
| for (Dimension dimension : (List<Dimension>) node.dimensions()) { |
| builder.breakToFill(dimension.annotations().isEmpty() ? "" : " "); |
| visit(dimension); |
| } |
| } |
| if (includeDimensions.isYes() && !node.dimensions().isEmpty()) { |
| builder.close(); |
| } |
| } |
| |
| /** |
| * Helper methods for {@link AnnotationTypeDeclaration}s, |
| * {@link AnnotationTypeMemberDeclaration}s, {@link EnumDeclaration}s, {@link Initializer}s, |
| * {@link MethodDeclaration}s, {@link PackageDeclaration}s, and {@link TypeParameter}s. Output |
| * combined modifiers and annotations and the trailing break. |
| * @param modifiers a list of {@link IExtendedModifier}s, which can include annotations |
| * @param annotationDirection direction of annotations |
| * @param declarationAnnotationBreak a tag for the {code Break} after any declaration annotations. |
| */ |
| void visitAndBreakModifiers( |
| List<IExtendedModifier> modifiers, |
| Direction annotationDirection, |
| Optional<BreakTag> declarationAnnotationBreak) { |
| builder.addAll( |
| visitModifiers(modifiers, annotationDirection, declarationAnnotationBreak)); |
| } |
| |
| /** |
| * Helper method for {@link EnumConstantDeclaration}s, {@link TypeDeclaration}s, and |
| * {@code visitAndBreakModifiers}. Output combined modifiers and annotations and returns the |
| * trailing break. |
| * @param modifiers a list of {@link IExtendedModifier}s, which can include annotations |
| * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL} |
| * @param declarationAnnotationBreak a tag for the {code Break} after any declaration annotations. |
| * @return the list of {@link Doc.Break}s following the modifiers and annotations |
| */ |
| private List<Op> visitModifiers( |
| List<IExtendedModifier> modifiers, |
| Direction annotationsDirection, |
| Optional<BreakTag> declarationAnnotationBreak) { |
| if (modifiers.isEmpty()) { |
| return EMPTY_LIST; |
| } |
| builder.open(ZERO); |
| boolean first = true; |
| boolean lastWasAnnotation = false; |
| int idx = 0; |
| for (; idx < modifiers.size(); idx++) { |
| IExtendedModifier modifier = modifiers.get(idx); |
| if (modifier.isModifier()) { |
| break; |
| } |
| if (!first) { |
| builder.addAll( |
| annotationsDirection.isVertical() |
| ? forceBreakList(declarationAnnotationBreak) |
| : breakList(declarationAnnotationBreak)); |
| } |
| ((ASTNode) modifier).accept(this); |
| first = false; |
| lastWasAnnotation = true; |
| } |
| builder.close(); |
| ImmutableList<Op> trailingBreak = |
| annotationsDirection.isVertical() |
| ? forceBreakList(declarationAnnotationBreak) |
| : breakList(declarationAnnotationBreak); |
| if (idx >= modifiers.size()) { |
| return trailingBreak; |
| } |
| if (lastWasAnnotation) { |
| builder.addAll(trailingBreak); |
| } |
| |
| builder.open(ZERO); |
| first = true; |
| for (; idx < modifiers.size(); idx++) { |
| IExtendedModifier modifier = modifiers.get(idx); |
| if (!first) { |
| builder.addAll(breakFillList(Optional.<BreakTag>absent())); |
| } |
| ((ASTNode) modifier).accept(this); |
| first = false; |
| lastWasAnnotation = modifier.isAnnotation(); |
| } |
| builder.close(); |
| return breakFillList(Optional.<BreakTag>absent()); |
| } |
| |
| /** Helper method for {@link CatchClause}s. */ |
| private void visitCatchClause(CatchClause node, AllowTrailingBlankLine allowTrailingBlankLine) { |
| sync(node); |
| builder.space(); |
| token("catch"); |
| builder.space(); |
| token("("); |
| builder.open(plusFour); |
| builder.breakOp(); |
| builder.open(ZERO); |
| visit(node.getException()); |
| builder.close(); |
| builder.close(); |
| token(")"); |
| builder.space(); |
| visitBlock( |
| node.getBody(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES, allowTrailingBlankLine); |
| } |
| |
| /** |
| * Helper method for {@link InfixExpression}s. Visit this {@link Expression} node, and its |
| * children, as long as they are {@link InfixExpression} nodes of the same precedence. Accumulate |
| * the operands and operators. |
| * @param precedence the precedence of the operators to collect |
| * @param operands the output list of {@code n + 1} operands |
| * @param operators the output list of {@code n} operators |
| */ |
| private static void walkInfix( |
| int precedence, Expression expression, List<Expression> operands, List<String> operators) { |
| if (expression.getNodeType() == ASTNode.INFIX_EXPRESSION) { |
| InfixExpression infixExpression = (InfixExpression) expression; |
| String myOperator = infixExpression.getOperator().toString(); |
| if (PRECEDENCE.get(myOperator) == precedence) { |
| walkInfix(precedence, infixExpression.getLeftOperand(), operands, operators); |
| operators.add(myOperator); |
| walkInfix(precedence, infixExpression.getRightOperand(), operands, operators); |
| if (infixExpression.hasExtendedOperands()) { |
| for (Expression extendedOperand : (List<Expression>) infixExpression.extendedOperands()) { |
| operators.add(myOperator); |
| walkInfix(precedence, extendedOperand, operands, operators); |
| } |
| } |
| } else { |
| operands.add(expression); |
| } |
| } else { |
| operands.add(expression); |
| } |
| } |
| |
| // TODO(jdd): Merge with union types. |
| |
| /** Helper method for {@link IntersectionType}s. */ |
| private static void walkIntersectionTypes(List<Type> types, IntersectionType node) { |
| for (ASTNode type : (List<ASTNode>) node.types()) { |
| if (type.getNodeType() == ASTNode.INTERSECTION_TYPE) { |
| walkIntersectionTypes(types, (IntersectionType) type); |
| } else { |
| types.add((Type) type); |
| } |
| } |
| } |
| |
| /** Helper method for {@link MethodDeclaration}s. */ |
| private void visitFormals( |
| ASTNode node, |
| Optional<Type> receiverType, SimpleName receiverQualifier, |
| List<SingleVariableDeclaration> parameters) { |
| if (receiverType.isPresent() || !parameters.isEmpty()) { |
| builder.open(ZERO, MAX_LINES_FOR_FORMAL_LIST); |
| boolean first = true; |
| if (receiverType.isPresent()) { |
| // TODO(jdd): Use builders. |
| declareOne( |
| node, |
| Direction.HORIZONTAL, |
| ImmutableList.<IExtendedModifier>of(), |
| receiverType.get(), |
| VarArgsOrNot.NO, |
| ImmutableList.<Annotation>of(), |
| receiverQualifier, |
| "", |
| ImmutableList.<Dimension>of(), |
| "", |
| Optional.<Expression>absent(), |
| Optional.<String>absent(), |
| ReceiverParameter.YES); |
| first = false; |
| } |
| for (SingleVariableDeclaration parameter : parameters) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| // TODO(jdd): Check for "=". |
| visitToDeclare(Direction.HORIZONTAL, parameter, Optional.<Expression>absent(), "="); |
| first = false; |
| } |
| builder.close(); |
| } |
| } |
| |
| /** Helper method for {@link MethodDeclaration}s. */ |
| private void visitThrowsClause(List<Type> thrownExceptionTypes) { |
| token("throws"); |
| builder.breakToFill(" "); |
| boolean first = true; |
| for (Type thrownExceptionType : thrownExceptionTypes) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| thrownExceptionType.accept(this); |
| first = false; |
| } |
| } |
| |
| /** Helper method for {@link ImportDeclaration}s, {@link Name}s, and {@link QualifiedName}s. */ |
| private void visitName(Name node, BreakOrNot breaks) { |
| sync(node); |
| if (node.isSimpleName()) { |
| visit((SimpleName) node); |
| } else { |
| visitQualifiedName((QualifiedName) node, breaks); |
| } |
| } |
| |
| /** |
| * Helper method for {@link EnhancedForStatement}s, {@link MethodDeclaration}s, and |
| * {@link SingleVariableDeclaration}s. |
| */ |
| private void visitToDeclare( |
| Direction annotationsDirection, SingleVariableDeclaration node, |
| Optional<Expression> initializer, String equals) { |
| sync(node); |
| declareOne( |
| node, |
| annotationsDirection, |
| node.modifiers(), |
| node.getType(), |
| VarArgsOrNot.valueOf(node.isVarargs()), |
| node.varargsAnnotations(), |
| node.getName(), |
| "", |
| node.extraDimensions(), |
| equals, |
| initializer, |
| Optional.<String>absent(), |
| ReceiverParameter.NO); |
| } |
| |
| /** Helper method for {@link MethodDeclaration}s and {@link TypeDeclaration}s. */ |
| private void visitTypeParameters( |
| List<TypeParameter> nodes, Indent plusIndent, BreakOrNot breakAfterOpen) { |
| if (!nodes.isEmpty()) { |
| token("<"); |
| builder.open(plusIndent); |
| builder.open(plusFour); |
| if (breakAfterOpen.isYes()) { |
| builder.breakOp(); |
| } |
| boolean first = true; |
| for (TypeParameter node : nodes) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| visit(node); |
| first = false; |
| } |
| builder.close(); |
| builder.close(); |
| token(">"); |
| } |
| } |
| |
| /** Helper method for {@link UnionType}s. */ |
| private static void walkUnionTypes(List<Type> types, UnionType node) { |
| for (ASTNode type : (List<ASTNode>) node.types()) { |
| if (type.getNodeType() == ASTNode.UNION_TYPE) { |
| walkUnionTypes(types, (UnionType) type); |
| } else { |
| types.add((Type) type); |
| } |
| } |
| } |
| |
| /** Collapse chains of {@code .} operators, across multiple {@link ASTNode} types. */ |
| |
| /** |
| * Output a "." node. |
| * @param node0 the "." node |
| */ |
| void visitDot(Expression node0) { |
| Expression node = node0; |
| |
| // collect a flattened list of "."-separated items |
| // e.g. ImmutableList.builder().add(1).build() -> [ImmutableList, builder(), add(1), build()] |
| ArrayDeque<Expression> stack = new ArrayDeque<>(); |
| LOOP: |
| do { |
| stack.addFirst(node); |
| switch (node.getNodeType()) { |
| case ASTNode.FIELD_ACCESS: |
| node = ((FieldAccess) node).getExpression(); |
| break; |
| case ASTNode.METHOD_INVOCATION: |
| node = ((MethodInvocation) node).getExpression(); |
| break; |
| case ASTNode.QUALIFIED_NAME: |
| node = ((QualifiedName) node).getQualifier(); |
| break; |
| case ASTNode.SIMPLE_NAME: |
| node = null; |
| break LOOP; |
| default: |
| // If the dot chain starts with a primary expression |
| // (e.g. a class instance creation, or a conditional expression) |
| // then remove it from the list and deal with it first. |
| stack.removeFirst(); |
| break LOOP; |
| } |
| } while (node != null); |
| List<Expression> items = new ArrayList<>(stack); |
| |
| boolean needDot = false; |
| |
| // The dot chain started with a primary expression: output it normally, and indent |
| // the rest of the chain +4. |
| if (node != null) { |
| // Exception: if it's an anonymous class declaration, we don't need to |
| // break and indent after the trailing '}'. |
| if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION |
| && ((ClassInstanceCreation) node).getAnonymousClassDeclaration() != null) { |
| builder.open(ZERO); |
| node.accept(this); |
| token("."); |
| } else { |
| builder.open(plusFour); |
| node.accept(this); |
| builder.breakOp(); |
| needDot = true; |
| } |
| } |
| |
| // Check if the dot chain has a prefix that looks like a type name, so we can |
| // treat the type name-shaped part as a single syntactic unit. |
| int prefixIndex = TypeNameClassifier.typePrefixLength(simpleNames(stack)); |
| |
| int invocationCount = 0; |
| int firstInvocationIndex = -1; |
| { |
| for (int i = 0; i < items.size(); i++) { |
| Expression expression = items.get(i); |
| if (expression.getNodeType() == ASTNode.METHOD_INVOCATION) { |
| if (i > 0 || node != null) { |
| // we only want dereference invocations |
| invocationCount++; |
| } |
| if (firstInvocationIndex < 0) { |
| firstInvocationIndex = i; |
| } |
| } |
| } |
| } |
| |
| // If there's only one invocation, treat leading field accesses as a single |
| // unit. In the normal case we want to preserve the alignment of subsequent |
| // method calls, and would emit e.g.: |
| // |
| // myField |
| // .foo() |
| // .bar(); |
| // |
| // But if there's no 'bar()' to worry about the alignment of we prefer: |
| // |
| // myField.foo(); |
| // |
| // to: |
| // |
| // myField |
| // .foo(); |
| // |
| if (invocationCount == 1) { |
| prefixIndex = firstInvocationIndex; |
| } |
| |
| if (prefixIndex > 0) { |
| visitDotWithPrefix(items, needDot, prefixIndex); |
| } else { |
| visitRegularDot(items, needDot); |
| } |
| |
| if (node != null) { |
| builder.close(); |
| } |
| } |
| |
| /** |
| * Output a "regular" chain of dereferences, possibly in builder-style. Break before every dot. |
| * |
| * @param items in the chain |
| * @param needDot whether a leading dot is needed |
| */ |
| private void visitRegularDot(List<Expression> items, boolean needDot) { |
| boolean trailingDereferences = items.size() > 1; |
| boolean needDot0 = needDot; |
| if (!needDot0) { |
| builder.open(plusFour, MAX_LINES_FOR_CHAINED_ACCESSES); |
| } |
| // don't break after the first element if it is every small, unless the |
| // chain starts with another expression |
| int minLength = indentMultiplier * 4; |
| int length = needDot0 ? minLength : 0; |
| for (Expression e : items) { |
| if (needDot) { |
| if (length > minLength) { |
| builder.breakOp(FillMode.UNIFIED, "", ZERO); |
| } |
| token("."); |
| length++; |
| } |
| BreakTag tyargTag = genSym(); |
| dotExpressionUpToArgs(e, Optional.of(tyargTag)); |
| Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); |
| dotExpressionArgsAndParen( |
| e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO); |
| length += e.getLength(); |
| needDot = true; |
| } |
| if (!needDot0) { |
| builder.close(); |
| } |
| } |
| |
| /** |
| * Output a chain of dereferences where some prefix should be treated as a single |
| * syntactic unit, either because it looks like a type name or because there |
| * is only a single method invocation in the chain. |
| * |
| * @param items in the chain |
| * @param needDot whether a leading dot is needed |
| * @param prefixIndex the index of the last item in the prefix |
| */ |
| private void visitDotWithPrefix(List<Expression> items, boolean needDot, int prefixIndex) { |
| // Are there method invocations or field accesses after the prefix? |
| boolean trailingDereferences = prefixIndex >= 0 && prefixIndex < items.size() - 1; |
| |
| builder.open(plusFour, MAX_LINES_FOR_CHAINED_ACCESSES); |
| builder.open(trailingDereferences ? ZERO : ZERO); |
| |
| BreakTag nameTag = genSym(); |
| for (int i = 0; i < items.size(); i++) { |
| Expression e = items.get(i); |
| if (needDot) { |
| FillMode fillMode; |
| if (prefixIndex >= 0 && i <= prefixIndex) { |
| fillMode = FillMode.INDEPENDENT; |
| } else { |
| fillMode = FillMode.UNIFIED; |
| } |
| |
| builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag)); |
| token("."); |
| } |
| BreakTag tyargTag = genSym(); |
| dotExpressionUpToArgs(e, Optional.of(tyargTag)); |
| if (prefixIndex >= 0 && i == prefixIndex) { |
| builder.close(); |
| } |
| |
| Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); |
| Indent argsIndent = Indent.If.make(nameTag, plusFour, trailingDereferences ? plusFour : ZERO); |
| dotExpressionArgsAndParen(e, tyargIndent, argsIndent); |
| |
| needDot = true; |
| } |
| |
| builder.close(); |
| } |
| |
| /** Returns the simple names of expressions in a "." chain. */ |
| private List<String> simpleNames(ArrayDeque<Expression> stack) { |
| ImmutableList.Builder<String> simpleNames = ImmutableList.builder(); |
| OUTER: |
| for (Expression expression : stack) { |
| switch (expression.getNodeType()) { |
| case ASTNode.FIELD_ACCESS: |
| simpleNames.add(((FieldAccess) expression).getName().getIdentifier()); |
| break; |
| case ASTNode.QUALIFIED_NAME: |
| simpleNames.add(((QualifiedName) expression).getName().getIdentifier()); |
| break; |
| case ASTNode.SIMPLE_NAME: |
| simpleNames.add(((SimpleName) expression).getIdentifier()); |
| break; |
| case ASTNode.METHOD_INVOCATION: |
| simpleNames.add(((MethodInvocation) expression).getName().getIdentifier()); |
| break OUTER; |
| default: |
| break OUTER; |
| } |
| } |
| return simpleNames.build(); |
| } |
| |
| private void dotExpressionUpToArgs(Expression expression, Optional<BreakTag> tyargTag) { |
| switch (expression.getNodeType()) { |
| case ASTNode.FIELD_ACCESS: |
| FieldAccess fieldAccess = (FieldAccess) expression; |
| visit(fieldAccess.getName()); |
| break; |
| case ASTNode.METHOD_INVOCATION: |
| MethodInvocation methodInvocation = (MethodInvocation) expression; |
| if (!methodInvocation.typeArguments().isEmpty()) { |
| builder.open(plusFour); |
| addTypeArguments(methodInvocation.typeArguments(), ZERO); |
| // TODO(jdd): Should indent the name -4. |
| builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, tyargTag); |
| builder.close(); |
| } |
| visit(methodInvocation.getName()); |
| token("("); |
| break; |
| case ASTNode.QUALIFIED_NAME: |
| visit(((QualifiedName) expression).getName()); |
| break; |
| case ASTNode.SIMPLE_NAME: |
| visit(((SimpleName) expression)); |
| break; |
| default: |
| expression.accept(this); |
| } |
| } |
| |
| private void dotExpressionArgsAndParen(Expression expression, Indent tyargIndent, Indent indent) { |
| switch (expression.getNodeType()) { |
| case ASTNode.METHOD_INVOCATION: |
| builder.open(tyargIndent); |
| MethodInvocation methodInvocation = (MethodInvocation) expression; |
| addArguments(methodInvocation.arguments(), indent); |
| builder.close(); |
| // TODO(cushon): default to suppressing blank lines? |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token(")"); |
| break; |
| case ASTNode.FIELD_ACCESS: |
| case ASTNode.SIMPLE_NAME: |
| case ASTNode.QUALIFIED_NAME: |
| default: |
| break; |
| } |
| } |
| |
| /** Helper methods for method invocations. */ |
| |
| void addTypeArguments(List<Type> typeArguments, Indent plusIndent) { |
| if (!typeArguments.isEmpty()) { |
| token("<"); |
| builder.open(plusIndent); |
| boolean first = true; |
| for (Type typeArgument : typeArguments) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| typeArgument.accept(this); |
| first = false; |
| } |
| builder.close(); |
| token(">"); |
| } |
| } |
| |
| /** |
| * Add arguments to a method invocation, etc. The arguments indented {@code plusFour}, filled, |
| * from the current indent. The arguments may be output two at a time if they seem to be arguments |
| * to a map constructor, etc. |
| * @param arguments the arguments |
| * @param plusIndent the extra indent for the arguments |
| */ |
| void addArguments(List<Expression> arguments, Indent plusIndent) { |
| if (!arguments.isEmpty()) { |
| if (argumentsArePaired(arguments)) { |
| builder.open(plusIndent); |
| builder.forcedBreak(); |
| builder.open(ZERO); |
| boolean first = true; |
| for (int i = 0; i < arguments.size() - 1; i += 2) { |
| Expression argument0 = arguments.get(i); |
| Expression argument1 = arguments.get(i + 1); |
| if (!first) { |
| token(","); |
| builder.forcedBreak(); |
| } |
| builder.open(plusFour); |
| argument0.accept(this); |
| token(","); |
| builder.breakOp(" "); |
| argument1.accept(this); |
| builder.close(); |
| first = false; |
| } |
| builder.close(); |
| builder.close(); |
| } else { |
| builder.open(plusIndent); |
| builder.breakOp(); |
| builder.open(ZERO, maxLinesFilledForItems(arguments, MAX_LINES_FOR_ARGUMENTS)); |
| boolean first = true; |
| for (Expression argument : arguments) { |
| if (!first) { |
| token(","); |
| builder.breakToFill(" "); |
| } |
| argument.accept(this); |
| first = false; |
| } |
| builder.close(); |
| builder.close(); |
| } |
| } |
| } |
| |
| private boolean argumentsArePaired(List<Expression> arguments) { |
| int n = arguments.size(); |
| if (n % 2 != 0 || n < 4) { |
| return false; |
| } |
| List<Expression> firsts = new ArrayList<>(); |
| List<Expression> seconds = new ArrayList<>(); |
| for (int i = 0; i < n; i++) { |
| (i % 2 == 0 ? firsts : seconds).add(arguments.get(i)); |
| } |
| Integer firstColumn0 = actualColumn(firsts.get(0)); |
| if (firstColumn0 == null) { |
| return false; |
| } |
| for (int i = 1; i < n / 2; i++) { |
| Integer firstColumnI = actualColumn(firsts.get(i)); |
| if (!firstColumn0.equals(firstColumnI)) { |
| return false; |
| } |
| } |
| for (int i = 0; i < n / 2; i++) { |
| Integer secondColumnI = actualColumn(seconds.get(i)); |
| if (secondColumnI == null) { |
| return false; |
| } |
| if (!(firstColumn0 < secondColumnI)) { |
| return false; |
| } |
| } |
| return expressionsAreParallel(firsts, n / 2) && expressionsAreParallel(seconds, n / 4 + 1); |
| } |
| |
| private Integer actualColumn(Expression expression) { |
| Map<Integer, Integer> positionToColumnMap = builder.getInput().getPositionToColumnMap(); |
| return positionToColumnMap.get(builder.actualStartColumn(expression.getStartPosition())); |
| } |
| |
| private static boolean expressionsAreParallel(List<Expression> expressions, int atLeastM) { |
| Multimap<Integer, Expression> map = HashMultimap.create(); |
| for (Expression expression : expressions) { |
| map.put(expression.getNodeType(), expression); |
| } |
| for (Integer nodeType : map.keys()) { |
| if (map.get(nodeType).size() >= atLeastM) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Visitor method for {@link QualifiedName}s. */ |
| private void visitQualifiedName(QualifiedName node0, BreakOrNot breaks) { |
| QualifiedName node = node0; |
| sync(node); |
| |
| // defer to visitDot for builder-style wrapping if breaks are enabled |
| if (breaks.isYes()) { |
| visitDot(node0); |
| return; |
| } |
| |
| // Collapse chains of "." operators. |
| ArrayDeque<SimpleName> stack = new ArrayDeque<>(); |
| Name qualifier; |
| while (true) { |
| stack.addFirst(node.getName()); |
| qualifier = node.getQualifier(); |
| if (qualifier == null || qualifier.getNodeType() != ASTNode.QUALIFIED_NAME) { |
| break; |
| } |
| node = (QualifiedName) qualifier; |
| } |
| if (qualifier != null) { |
| visitName(qualifier, breaks); |
| token("."); |
| } |
| boolean needDot = false; |
| for (SimpleName name : stack) { |
| if (needDot) { |
| token("."); |
| } |
| visit(name); |
| needDot = true; |
| } |
| } |
| |
| // General helper functions. |
| |
| // TODO(jdd): Mention annotation declarations. |
| |
| /** |
| * Declare one variable or variable-like thing. |
| * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL} |
| * @param modifiers the {@link IExtendedModifier}s, including annotations |
| * @param type the {@link Type} |
| * @param isVarargs is the type varargs? |
| * @param varargsAnnotations annotations on the varargs |
| * @param name the name |
| * @param op if non-empty, tokens to follow the name |
| * @param extraDimensions the extra dimensions |
| * @param equals "=" or equivalent |
| * @param initializer the (optional) initializer |
| * @param trailing the (optional) trailing token, e.g. ';' |
| * @param receiverParameter whether this is a receiver parameter |
| */ |
| void declareOne( |
| ASTNode node, |
| Direction annotationsDirection, |
| List<IExtendedModifier> modifiers, |
| Type type, |
| VarArgsOrNot isVarargs, |
| List<Annotation> varargsAnnotations, |
| SimpleName name, |
| String op, |
| List<Dimension> extraDimensions, |
| String equals, |
| Optional<Expression> initializer, |
| Optional<String> trailing, |
| ReceiverParameter receiverParameter) { |
| |
| BreakTag typeBreak = genSym(); |
| BreakTag verticalAnnotationBreak = genSym(); |
| |
| EnumSet<DeclarationPosition> position = DeclarationPosition.getPositionInParent(node); |
| |
| // If the node is a field declaration, try to output any declaration |
| // annotations in-line. If the entire declaration doesn't fit on a single |
| // line, fall back to one-per-line. |
| boolean isField = node.getNodeType() == ASTNode.FIELD_DECLARATION; |
| |
| if (isField) { |
| if (!position.contains(DeclarationPosition.FIRST)) { |
| builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); |
| } else { |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| } |
| } |
| |
| builder.open(ZERO); |
| { |
| visitAndBreakModifiers(modifiers, annotationsDirection, Optional.of(verticalAnnotationBreak)); |
| builder.open(plusFour); |
| { |
| builder.open(ZERO); |
| { |
| builder.open(ZERO); |
| { |
| type.accept(this); |
| if (isVarargs.isYes()) { |
| visitAnnotations(varargsAnnotations, BreakOrNot.YES, BreakOrNot.YES); |
| builder.op("..."); |
| } |
| } |
| builder.close(); |
| |
| builder.breakOp( |
| Doc.FillMode.INDEPENDENT, |
| " ", |
| ZERO, |
| Optional.of(typeBreak)); |
| |
| // conditionally ident the name and initializer +4 if the type spans |
| // multiple lines |
| builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); |
| if (receiverParameter.isYes()) { |
| if (name != null) { |
| visit(name); |
| token("."); |
| } |
| token("this"); |
| } else { |
| visit(name); |
| } |
| builder.op(op); |
| extraDimensions(initializer.isPresent() ? plusFour : ZERO, extraDimensions); |
| } |
| builder.close(); |
| } |
| builder.close(); |
| |
| if (initializer.isPresent()) { |
| builder.space(); |
| token(equals); |
| if (initializer.get().getNodeType() == ASTNode.ARRAY_INITIALIZER) { |
| builder.open(minusFour); |
| { |
| builder.space(); |
| initializer.get().accept(this); |
| } |
| builder.close(); |
| } else { |
| builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); |
| { |
| builder.breakToFill(" "); |
| initializer.get().accept(this); |
| } |
| builder.close(); |
| } |
| } |
| // end of conditional name and initializer indent |
| builder.close(); |
| |
| if (trailing.isPresent()) { |
| builder.guessToken(trailing.get()); |
| } |
| } |
| builder.close(); |
| |
| if (isField) { |
| if (!position.contains(DeclarationPosition.LAST)) { |
| builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); |
| } else { |
| builder.blankLineWanted(BlankLineWanted.NO); |
| } |
| } |
| } |
| |
| /** |
| * Declare multiple variables or variable-like things. |
| * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL} |
| * @param modifiers the {@link IExtendedModifier}s, including annotations |
| * @param type the {@link Type}s |
| * @param fragments the {@link VariableDeclarationFragment}s |
| */ |
| private void declareMany( |
| Direction annotationsDirection, List<IExtendedModifier> modifiers, Type type, |
| List<VariableDeclarationFragment> fragments) { |
| builder.open(ZERO); |
| visitAndBreakModifiers(modifiers, annotationsDirection, Optional.<BreakTag>absent()); |
| builder.open(plusFour); |
| type.accept(this); |
| // TODO(jdd): Open another time? |
| boolean first = true; |
| for (VariableDeclarationFragment fragment : fragments) { |
| if (!first) { |
| token(","); |
| } |
| builder.breakOp(" "); |
| builder.open(ZERO); |
| visit(fragment.getName()); |
| Expression initializer = fragment.getInitializer(); |
| extraDimensions(initializer != null ? plusEight : plusFour, fragment.extraDimensions()); |
| if (initializer != null) { |
| builder.space(); |
| token("="); |
| if (initializer.getNodeType() == ASTNode.ARRAY_INITIALIZER) { |
| // TODO(jdd): Check on this. |
| builder.close(); |
| builder.open(ZERO); |
| builder.space(); |
| initializer.accept(this); |
| } else { |
| builder.open(plusFour); |
| builder.breakOp(" "); |
| initializer.accept(this); |
| builder.close(); |
| } |
| } |
| builder.close(); |
| first = false; |
| } |
| builder.close(); |
| token(";"); |
| builder.close(); |
| } |
| |
| /** |
| * Add a declaration. |
| * @param modifiers the {@link IExtendedModifier}s, including annotations |
| * @param type the {@link Type}s |
| * @param fragments the {@link VariableDeclarationFragment}s |
| * @param annotationsDirection {@link Direction#VERTICAL} or {@link Direction#HORIZONTAL} |
| */ |
| void addDeclaration( |
| ASTNode node, |
| List<IExtendedModifier> modifiers, Type type, List<VariableDeclarationFragment> fragments, |
| Direction annotationsDirection) { |
| if (fragments.size() == 1) { |
| VariableDeclarationFragment fragment = fragments.get(0); |
| declareOne( |
| node, |
| annotationsDirection, |
| modifiers, |
| type, |
| VarArgsOrNot.NO, |
| ImmutableList.<Annotation>of(), |
| fragment.getName(), |
| "", |
| fragment.extraDimensions(), |
| "=", |
| Optional.fromNullable(fragment.getInitializer()), |
| Optional.of(";"), |
| ReceiverParameter.NO); |
| } else { |
| declareMany(annotationsDirection, modifiers, type, fragments); |
| } |
| } |
| |
| // TODO(jdd): State precondition (and check callers). |
| /** |
| * Emit extra dimensions (if any). |
| * @param plusIndent the extra indentation for the extra dimensions |
| * @param extraDimensions the extra {@link Dimension}s |
| */ |
| void extraDimensions(Indent plusIndent, List<Dimension> extraDimensions) { |
| builder.open(plusIndent); |
| for (Dimension extraDimension : extraDimensions) { |
| builder.breakToFill(extraDimension.annotations().isEmpty() ? "" : " "); |
| visit(extraDimension); |
| } |
| builder.close(); |
| } |
| |
| // TODO(jdd): Static checks? |
| /** |
| * Add a list of {@link BodyDeclaration}s |
| * @param bodyDeclarations the {@link BodyDeclaration}s |
| * @param braces whether to include braces in the output |
| * @param first0 is the first {@link BodyDeclaration} the first to be output? |
| */ |
| void addBodyDeclarations( |
| List<BodyDeclaration> bodyDeclarations, BracesOrNot braces, FirstDeclarationsOrNot first0) { |
| if (bodyDeclarations.isEmpty()) { |
| if (braces.isYes()) { |
| builder.space(); |
| tokenBreakTrailingComment("{", plusTwo); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| builder.open(ZERO); |
| token("}", plusTwo); |
| builder.close(); |
| } |
| } else { |
| if (braces.isYes()) { |
| builder.space(); |
| tokenBreakTrailingComment("{", plusTwo); |
| builder.open(ZERO); |
| } |
| builder.open(plusTwo); |
| boolean first = first0.isYes(); |
| boolean lastOneGotBlankLineBefore = false; |
| for (BodyDeclaration bodyDeclaration : bodyDeclarations) { |
| dropEmptyDeclarations(); |
| builder.forcedBreak(); |
| boolean thisOneGetsBlankLineBefore = |
| bodyDeclaration.getNodeType() != ASTNode.FIELD_DECLARATION |
| || hasJavaDoc(bodyDeclaration); |
| if (first) { |
| builder.blankLineWanted(BlankLineWanted.PRESERVE); |
| } else if (!first && (thisOneGetsBlankLineBefore || lastOneGotBlankLineBefore)) { |
| builder.blankLineWanted(BlankLineWanted.YES); |
| } |
| markForPartialFormat(); |
| bodyDeclaration.accept(this); |
| first = false; |
| lastOneGotBlankLineBefore = thisOneGetsBlankLineBefore; |
| } |
| builder.close(); |
| builder.forcedBreak(); |
| markForPartialFormat(); |
| if (braces.isYes()) { |
| dropEmptyDeclarations(); |
| builder.blankLineWanted(BlankLineWanted.NO); |
| token("}", plusTwo); |
| builder.close(); |
| } |
| } |
| } |
| |
| // Use Eclipse token ID instead of position? |
| /** Does this {@link BodyDeclaration} have JavaDoc preceding it? */ |
| private boolean hasJavaDoc(BodyDeclaration bodyDeclaration) { |
| int position = bodyDeclaration.getStartPosition(); |
| Map.Entry<Integer, ? extends Input.Token> entry = |
| builder.getInput().getPositionTokenMap().ceilingEntry(position); |
| if (entry != null) { |
| for (Input.Tok tok : entry.getValue().getToksBefore()) { |
| if (tok.getText().startsWith("/**")) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static Optional<? extends Input.Token> getNextToken(Input input, int position) { |
| Map.Entry<Integer, ? extends Input.Token> ceilingEntry = |
| input.getPositionTokenMap().ceilingEntry(position); |
| return ceilingEntry == null |
| ? Optional.<JavaInput.Token>absent() |
| : Optional.of(ceilingEntry.getValue()); |
| } |
| |
| /** |
| * Does this list of {@link ASTNode}s ends with the specified token? |
| * @param input the {@link Input} |
| * @param nodes list of {@link ASTNode}s |
| * @return whether the list has an extra trailing comma |
| */ |
| private static boolean hasTrailingToken(Input input, List<ASTNode> nodes, String token) { |
| if (nodes.isEmpty()) { |
| return false; |
| } |
| ASTNode lastNode = nodes.get(nodes.size() - 1); |
| Optional<? extends Input.Token> nextToken = |
| getNextToken(input, lastNode.getStartPosition() + lastNode.getLength()); |
| return nextToken.isPresent() && nextToken.get().getTok().getText().equals(token); |
| } |
| |
| // TODO(jdd): Use constants for limits? |
| |
| /** |
| * Can a local with a set of modifiers be declared with horizontal annotations? This is currently |
| * true if there is at most one marker annotation, and no others. |
| * @param modifiers the list of {@link IExtendedModifier}s |
| * @return whether the local can be declared with horizontal annotations |
| */ |
| private static Direction canLocalHaveHorizontalAnnotations(List<IExtendedModifier> modifiers) { |
| int normalAnnotations = 0; |
| int markerAnnotations = 0; |
| int singleMemberAnnotations = 0; |
| for (IExtendedModifier modifier : modifiers) { |
| switch (((ASTNode) modifier).getNodeType()) { |
| case ASTNode.NORMAL_ANNOTATION: |
| ++normalAnnotations; |
| break; |
| case ASTNode.MARKER_ANNOTATION: |
| ++markerAnnotations; |
| break; |
| case ASTNode.SINGLE_MEMBER_ANNOTATION: |
| ++singleMemberAnnotations; |
| break; |
| default: |
| break; |
| } |
| } |
| return normalAnnotations == 0 && markerAnnotations <= 1 && singleMemberAnnotations == 0 |
| ? Direction.HORIZONTAL |
| : Direction.VERTICAL; |
| } |
| |
| /** |
| * Should a field with a set of modifiers be declared with horizontal annotations? |
| * This is currently true if all annotations are marker annotations. |
| * |
| * @param modifiers the list of {@link IExtendedModifier}s |
| * @return whether the local can be declared with horizontal annotations |
| */ |
| private static Direction fieldAnnotationDirection(List<IExtendedModifier> modifiers) { |
| for (IExtendedModifier modifier : modifiers) { |
| if (modifier.isAnnotation() |
| && ((ASTNode) modifier).getNodeType() != ASTNode.MARKER_ANNOTATION) { |
| return Direction.VERTICAL; |
| } |
| } |
| return Direction.HORIZONTAL; |
| } |
| |
| // TODO(jdd): Do more? |
| /** |
| * Emit a {@link Doc.Token}. |
| * @param token the {@link String} to wrap in a {@link Doc.Token} |
| */ |
| final void token(String token) { |
| builder.token(token, Doc.Token.RealOrImaginary.REAL, ZERO, Optional.<Indent>absent()); |
| } |
| |
| /** |
| * Emit a {@link Doc.Token}. |
| * @param token the {@link String} to wrap in a {@link Doc.Token} |
| * @param plusIndentCommentsBefore extra indent for comments before this token |
| */ |
| final void token(String token, Indent plusIndentCommentsBefore) { |
| builder.token( |
| token, Doc.Token.RealOrImaginary.REAL, plusIndentCommentsBefore, Optional.<Indent>absent()); |
| } |
| |
| /** |
| * Emit a {@link Doc.Token}, and breaks and indents trailing javadoc or block comments. |
| */ |
| final void tokenBreakTrailingComment(String token, Indent breakAndIndentTrailingComment) { |
| builder.token( |
| token, |
| Doc.Token.RealOrImaginary.REAL, |
| ZERO, |
| Optional.<Indent>of(breakAndIndentTrailingComment)); |
| } |
| |
| private void markForPartialFormat() { |
| if (!inExpression()) { |
| builder.markForPartialFormat(); |
| } |
| } |
| |
| /** |
| * Sync to position in the input. If we've skipped outputting any tokens that were present in the |
| * input tokens, output them here and complain. |
| * @param node the ASTNode holding the input position |
| */ |
| final void sync(ASTNode node) { |
| builder.sync(node.getStartPosition()); |
| } |
| |
| final BreakTag genSym() { |
| return new BreakTag(); |
| } |
| |
| @Override |
| public final String toString() { |
| return MoreObjects.toStringHelper(this).add("builder", builder).toString(); |
| } |
| } |