Copyright (c) 2014, 2019 Mateusz Matela and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: Mateusz Matela - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519 Mateusz Matela - [formatter] follow up bug for comments - https://bugs.eclipse.org/458208
/******************************************************************************* * Copyright (c) 2014, 2019 Mateusz Matela and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Mateusz Matela <mateusz.matela@gmail.com> - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519 * Mateusz Matela <mateusz.matela@gmail.com> - [formatter] follow up bug for comments - https://bugs.eclipse.org/458208 *******************************************************************************/
package org.eclipse.jdt.internal.formatter.linewrap; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOLON; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMA; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_BLOCK; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_JAVADOC; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_LINE; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameDOT; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameEQUAL; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameIdentifier; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLBRACE; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLESS; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLPAREN; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameOR; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameQUESTION; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameRBRACE; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameRPAREN; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameSEMICOLON; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameStringLiteral; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameenum; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameextends; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameimplements; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamenew; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamesuper; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamethis; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamethrows; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameto; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewhile; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewith; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.function.ToIntFunction; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.ConditionalExpression; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.CreationReference; import org.eclipse.jdt.core.dom.DoStatement; 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.ExportsDirective; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionMethodReference; 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.IfStatement; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.InfixExpression.Operator; import org.eclipse.jdt.core.dom.LambdaExpression; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.OpensDirective; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.ProvidesDirective; import org.eclipse.jdt.core.dom.QualifiedName; 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.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.SwitchExpression; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.ThisExpression; 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.TypeMethodReference; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.UnionType; import org.eclipse.jdt.core.dom.VariableDeclaration; 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.formatter.CodeFormatter; import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions; import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions.Alignment; import org.eclipse.jdt.internal.formatter.Token; import org.eclipse.jdt.internal.formatter.Token.WrapMode; import org.eclipse.jdt.internal.formatter.Token.WrapPolicy; import org.eclipse.jdt.internal.formatter.TokenManager; import org.eclipse.jdt.internal.formatter.TokenTraverser; import org.eclipse.jface.text.IRegion; public class WrapPreparator extends ASTVisitor {
Helper for common handling of all expressions that should be treated the same as FieldAccess
/** * Helper for common handling of all expressions that should be treated the same as {@link FieldAccess} */
private static class FieldAccessAdapter { final Expression accessExpression; public FieldAccessAdapter(Expression expression) { this.accessExpression = expression; } public static boolean isFieldAccess(ASTNode expr) { return expr instanceof FieldAccess || expr instanceof QualifiedName || expr instanceof ThisExpression || expr instanceof SuperFieldAccess; } public Expression getExpression() { if (this.accessExpression instanceof FieldAccess) return ((FieldAccess) this.accessExpression).getExpression(); if (this.accessExpression instanceof QualifiedName) return ((QualifiedName) this.accessExpression).getQualifier(); if (this.accessExpression instanceof ThisExpression) return ((ThisExpression) this.accessExpression).getQualifier(); if (this.accessExpression instanceof SuperFieldAccess) return ((SuperFieldAccess) this.accessExpression).getQualifier(); throw new AssertionError(); } public int getIdentifierIndex(TokenManager tm) { if (this.accessExpression instanceof FieldAccess) return tm.firstIndexIn(((FieldAccess) this.accessExpression).getName(), TokenNameIdentifier); if (this.accessExpression instanceof QualifiedName) return tm.firstIndexIn(((QualifiedName) this.accessExpression).getName(), TokenNameIdentifier); if (this.accessExpression instanceof ThisExpression) return tm.lastIndexIn(this.accessExpression, TokenNamethis); if (this.accessExpression instanceof SuperFieldAccess) return tm.lastIndexIn(this.accessExpression, TokenNamesuper); throw new AssertionError(); } } private static final Map<Operator, Integer> OPERATOR_PRECEDENCE; private static final Map<Operator, ToIntFunction<DefaultCodeFormatterOptions>> OPERATOR_WRAPPING_OPTION; private static final Map<Operator, Predicate<DefaultCodeFormatterOptions>> OPERATOR_WRAP_BEFORE_OPTION; static { HashMap<Operator, Integer> precedence = new HashMap<>(); HashMap<Operator, ToIntFunction<DefaultCodeFormatterOptions>> wrappingOption = new HashMap<>(); HashMap<Operator, Predicate<DefaultCodeFormatterOptions>> wrapBeforeOption = new HashMap<>(); for (Operator op : Arrays.asList(Operator.TIMES, Operator.DIVIDE, Operator.REMAINDER)) { precedence.put(op, 1); wrappingOption.put(op, o -> o.alignment_for_multiplicative_operator); wrapBeforeOption.put(op, o -> o.wrap_before_multiplicative_operator); } for (Operator op : Arrays.asList(Operator.PLUS, Operator.MINUS)) { precedence.put(op, 2); wrappingOption.put(op, o -> o.alignment_for_additive_operator); wrapBeforeOption.put(op, o -> o.wrap_before_additive_operator); } for (Operator op : Arrays.asList(Operator.LEFT_SHIFT, Operator.RIGHT_SHIFT_SIGNED, Operator.RIGHT_SHIFT_UNSIGNED)) { precedence.put(op, 3); wrappingOption.put(op, o -> o.alignment_for_shift_operator); wrapBeforeOption.put(op, o -> o.wrap_before_shift_operator); } for (Operator op : Arrays.asList(Operator.LESS, Operator.GREATER, Operator.LESS_EQUALS, Operator.GREATER_EQUALS)) { precedence.put(op, 4); wrappingOption.put(op, o -> o.alignment_for_relational_operator); wrapBeforeOption.put(op, o -> o.wrap_before_relational_operator); } for (Operator op : Arrays.asList(Operator.EQUALS, Operator.NOT_EQUALS)) { precedence.put(op, 5); wrappingOption.put(op, o -> o.alignment_for_relational_operator); wrapBeforeOption.put(op, o -> o.wrap_before_relational_operator); } precedence.put(Operator.AND, 6); precedence.put(Operator.XOR, 7); precedence.put(Operator.OR, 8); for (Operator op : Arrays.asList(Operator.AND, Operator.XOR, Operator.OR)) { wrappingOption.put(op, o -> o.alignment_for_bitwise_operator); wrapBeforeOption.put(op, o -> o.wrap_before_bitwise_operator); } precedence.put(Operator.CONDITIONAL_AND, 9); precedence.put(Operator.CONDITIONAL_OR, 10); for (Operator op : Arrays.asList(Operator.CONDITIONAL_AND, Operator.CONDITIONAL_OR)) { wrappingOption.put(op, o -> o.alignment_for_logical_operator); wrapBeforeOption.put(op, o -> o.wrap_before_logical_operator); } // ternary and assignment operators not relevant to infix expressions OPERATOR_PRECEDENCE = Collections.unmodifiableMap(precedence); OPERATOR_WRAPPING_OPTION = Collections.unmodifiableMap(wrappingOption); OPERATOR_WRAP_BEFORE_OPTION = Collections.unmodifiableMap(wrapBeforeOption); }
Penalty multiplier for wraps that are preferred
/** Penalty multiplier for wraps that are preferred */
private final static float PREFERRED = 7f / 8; final TokenManager tm; final DefaultCodeFormatterOptions options; final int kind; final Aligner aligner; /* * temporary values used when calling {@link #handleWrap(int)} to avoid ArrayList initialization and long lists of * parameters */ private List<Integer> wrapIndexes = new ArrayList<>();
Indexes for wraps that shouldn't happen but should be indented if cannot be removed
/** Indexes for wraps that shouldn't happen but should be indented if cannot be removed */
private List<Integer> secondaryWrapIndexes = new ArrayList<>(); private List<Float> wrapPenalties = new ArrayList<>(); private int wrapParentIndex = -1; private int wrapGroupEnd = -1; private int currentDepth = 0; public WrapPreparator(TokenManager tokenManager, DefaultCodeFormatterOptions options, int kind) { this.tm = tokenManager; this.options = options; this.kind = kind; this.aligner = new Aligner(this.tm, this.options); } @Override public boolean preVisit2(ASTNode node) { this.currentDepth++; assert this.wrapIndexes.isEmpty() && this.secondaryWrapIndexes.isEmpty() && this.wrapPenalties.isEmpty(); assert this.wrapParentIndex == -1 && this.wrapGroupEnd == -1; boolean isMalformed = (node.getFlags() & ASTNode.MALFORMED) != 0; if (isMalformed) { this.tm.addDisableFormatTokenPair(this.tm.firstTokenIn(node, -1), this.tm.lastTokenIn(node, -1)); } return !isMalformed; } @Override public void postVisit(ASTNode node) { this.currentDepth--; } @Override public boolean visit(NormalAnnotation node) { int lParen = this.tm.firstIndexAfter(node.getTypeName(), TokenNameLPAREN); int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_annotation); handleArguments(node.values(), this.options.alignment_for_arguments_in_annotation); return true; } @Override public boolean visit(SingleMemberAnnotation node) { int lParen = this.tm.firstIndexAfter(node.getTypeName(), TokenNameLPAREN); int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_annotation); return true; } @Override public boolean visit(TypeDeclaration node) { Type superclassType = node.getSuperclassType(); if (superclassType != null) { this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); this.wrapGroupEnd = this.tm.lastIndexIn(superclassType, -1); this.wrapIndexes.add(this.tm.firstIndexBefore(superclassType, TokenNameextends)); this.wrapIndexes.add(this.tm.firstIndexIn(superclassType, -1)); handleWrap(this.options.alignment_for_superclass_in_type_declaration, PREFERRED); } List<Type> superInterfaceTypes = node.superInterfaceTypes(); if (!superInterfaceTypes.isEmpty()) { int implementsToken = node.isInterface() ? TokenNameextends : TokenNameimplements; this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); this.wrapIndexes.add(this.tm.firstIndexBefore(superInterfaceTypes.get(0), implementsToken)); prepareElementsList(superInterfaceTypes, TokenNameCOMMA, -1); handleWrap(this.options.alignment_for_superinterfaces_in_type_declaration, PREFERRED); } prepareElementsList(node.typeParameters(), TokenNameCOMMA, TokenNameLESS); handleWrap(this.options.alignment_for_type_parameters); this.aligner.handleAlign(node.bodyDeclarations()); return true; } @Override public boolean visit(AnnotationTypeDeclaration node) { this.aligner.handleAlign(node.bodyDeclarations()); return true; } @Override public boolean visit(AnonymousClassDeclaration node) { this.aligner.handleAlign(node.bodyDeclarations()); return true; } @Override public boolean visit(MethodDeclaration node) { int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); int rParen = node.getBody() == null ? this.tm.lastIndexIn(node, TokenNameRPAREN) : this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_declaration); List<SingleVariableDeclaration> parameters = node.parameters(); Type receiverType = node.getReceiverType(); if (!parameters.isEmpty() || receiverType != null) { if (receiverType != null) this.wrapIndexes.add(this.tm.firstIndexIn(receiverType, -1)); int wrappingOption = node.isConstructor() ? this.options.alignment_for_parameters_in_constructor_declaration : this.options.alignment_for_parameters_in_method_declaration; this.wrapGroupEnd = this.tm.lastIndexIn( parameters.isEmpty() ? receiverType : parameters.get(parameters.size() - 1), -1); handleArguments(parameters, wrappingOption); } List<Type> exceptionTypes = node.thrownExceptionTypes(); if (!exceptionTypes.isEmpty()) { int wrappingOption = node.isConstructor() ? this.options.alignment_for_throws_clause_in_constructor_declaration : this.options.alignment_for_throws_clause_in_method_declaration; if ((wrappingOption & Alignment.M_INDENT_ON_COLUMN) == 0) this.wrapParentIndex = lParen; prepareElementsList(exceptionTypes, TokenNameCOMMA, TokenNameRPAREN); // instead of the first exception type, wrap the "throws" token this.wrapIndexes.set(0, this.tm.firstIndexBefore(exceptionTypes.get(0), TokenNamethrows)); handleWrap(wrappingOption, 0.5f); } if (!node.isConstructor()) { this.wrapParentIndex = this.tm.findFirstTokenInLine(this.tm.firstIndexIn(node.getName(), -1)); while (this.tm.get(this.wrapParentIndex).isComment()) this.wrapParentIndex++; List<TypeParameter> typeParameters = node.typeParameters(); if (!typeParameters.isEmpty()) this.wrapIndexes.add(this.tm.firstIndexIn(typeParameters.get(0), -1)); if (node.getReturnType2() != null) { int returTypeIndex = this.tm.firstIndexIn(node.getReturnType2(), -1); if (returTypeIndex != this.wrapParentIndex) this.wrapIndexes.add(returTypeIndex); } this.wrapIndexes.add(this.tm.firstIndexIn(node.getName(), -1)); this.wrapGroupEnd = this.tm.lastIndexIn(node.getName(), -1); handleWrap(this.options.alignment_for_method_declaration); } prepareElementsList(node.typeParameters(), TokenNameCOMMA, TokenNameLESS); handleWrap(this.options.alignment_for_type_parameters); return true; } @Override public boolean visit(EnumDeclaration node) { List<EnumConstantDeclaration> enumConstants = node.enumConstants(); int constantsEnd = -1; if (!enumConstants.isEmpty()) { for (EnumConstantDeclaration constant : enumConstants) this.wrapIndexes.add(this.tm.firstIndexIn(constant, -1)); this.wrapParentIndex = (this.options.alignment_for_enum_constants & Alignment.M_INDENT_ON_COLUMN) > 0 ? this.tm.firstIndexBefore(enumConstants.get(0), TokenNameLBRACE) : this.tm.firstIndexIn(node, TokenNameenum); this.wrapGroupEnd = constantsEnd = this.tm.lastIndexIn(enumConstants.get(enumConstants.size() - 1), -1); handleWrap(this.options.alignment_for_enum_constants, node); } if (!this.options.join_wrapped_lines) { // preserve a line break between the last comma and semicolon int commaIndex = -1; int i = constantsEnd > 0 ? constantsEnd : this.tm.firstIndexAfter(node.getName(), TokenNameLBRACE); while (++i < this.tm.size()) { Token t = this.tm.get(i); if (t.isComment()) continue; if (t.tokenType == TokenNameCOMMA) { commaIndex = i; continue; } if (t.tokenType == TokenNameSEMICOLON && commaIndex >= 0 && this.tm.countLineBreaksBetween(this.tm.get(commaIndex), t) == 1) { t.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, commaIndex, 0)); } break; } } List<Type> superInterfaceTypes = node.superInterfaceTypes(); if (!superInterfaceTypes.isEmpty()) { this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); this.wrapIndexes.add(this.tm.firstIndexBefore(superInterfaceTypes.get(0), TokenNameimplements)); prepareElementsList(superInterfaceTypes, TokenNameCOMMA, -1); handleWrap(this.options.alignment_for_superinterfaces_in_enum_declaration, PREFERRED); } this.aligner.handleAlign(node.bodyDeclarations()); return true; } @Override public boolean visit(EnumConstantDeclaration node) { int lParen = this.tm.firstIndexAfter(node.getName(), -1); while (this.tm.get(lParen).isComment()) lParen++; if (this.tm.get(lParen).tokenType == TokenNameLPAREN) { int rParen = node.getAnonymousClassDeclaration() == null ? this.tm.lastIndexIn(node, TokenNameRPAREN) : this.tm.firstIndexBefore(node.getAnonymousClassDeclaration(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_enum_constant_declaration); } handleArguments(node.arguments(), this.options.alignment_for_arguments_in_enum_constant); AnonymousClassDeclaration anonymousClass = node.getAnonymousClassDeclaration(); if (anonymousClass != null) { forceContinuousWrapping(anonymousClass, this.tm.firstIndexIn(node.getName(), -1)); } return true; } @Override public boolean visit(Block node) { this.aligner.handleAlign(node); return true; } @Override public boolean visit(MethodInvocation node) { int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); handleArguments(node.arguments(), this.options.alignment_for_arguments_in_method_invocation); handleTypeArguments(node.typeArguments()); boolean isInvocationChainRoot = !(node.getParent() instanceof MethodInvocation) || node.getLocationInParent() != MethodInvocation.EXPRESSION_PROPERTY; if (isInvocationChainRoot) { Expression expression = node; MethodInvocation invocation = node; while (expression instanceof MethodInvocation) { invocation = (MethodInvocation) expression; expression = invocation.getExpression(); if (expression != null) { this.wrapIndexes.add(this.tm.firstIndexBefore(invocation.getName(), TokenNameDOT)); this.secondaryWrapIndexes.add(this.tm.firstIndexIn(invocation.getName(), TokenNameIdentifier)); } } Collections.reverse(this.wrapIndexes); this.wrapParentIndex = (expression != null) ? this.tm.lastIndexIn(expression, -1) : this.tm.lastIndexIn(invocation, -1); this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); handleWrap(this.options.alignment_for_selector_in_method_invocation); } return true; } @Override public boolean visit(SuperMethodInvocation node) { int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); handleArguments(node.arguments(), this.options.alignment_for_arguments_in_method_invocation); handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(ClassInstanceCreation node) { int lParen = this.tm.firstIndexAfter(node.getType(), TokenNameLPAREN); int rParen = node.getAnonymousClassDeclaration() == null ? this.tm.lastIndexIn(node, TokenNameRPAREN) : this.tm.firstIndexBefore(node.getAnonymousClassDeclaration(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); AnonymousClassDeclaration anonymousClass = node.getAnonymousClassDeclaration(); if (anonymousClass != null) { forceContinuousWrapping(anonymousClass, this.tm.firstIndexIn(node, TokenNamenew)); } int wrappingOption = node.getExpression() != null ? this.options.alignment_for_arguments_in_qualified_allocation_expression : this.options.alignment_for_arguments_in_allocation_expression; handleArguments(node.arguments(), wrappingOption); handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(ConstructorInvocation node) { int lParen = node.arguments().isEmpty() ? this.tm.lastIndexIn(node, TokenNameLPAREN) : this.tm.firstIndexBefore((ASTNode) node.arguments().get(0), TokenNameLPAREN); int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); handleArguments(node.arguments(), this.options.alignment_for_arguments_in_explicit_constructor_call); handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(SuperConstructorInvocation node) { int lParen = node.arguments().isEmpty() ? this.tm.lastIndexIn(node, TokenNameLPAREN) : this.tm.firstIndexBefore((ASTNode) node.arguments().get(0), TokenNameLPAREN); int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); handleArguments(node.arguments(), this.options.alignment_for_arguments_in_explicit_constructor_call); handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(FieldAccess node) { handleFieldAccess(node); return true; } @Override public boolean visit(QualifiedName node) { handleFieldAccess(node); return true; } @Override public boolean visit(ThisExpression node) { handleFieldAccess(node); return true; } @Override public boolean visit(SuperFieldAccess node) { handleFieldAccess(node); return true; } private void handleFieldAccess(Expression node) { boolean isAccessChainRoot = !FieldAccessAdapter.isFieldAccess(node.getParent()); if (!isAccessChainRoot) return; Expression expression = node; FieldAccessAdapter access = null; while (FieldAccessAdapter.isFieldAccess(expression)) { access = new FieldAccessAdapter(expression); int nameIndex = access.getIdentifierIndex(this.tm); // find a dot preceding the name, may not be there for (int i = nameIndex - 1; i > this.tm.firstIndexIn(node, -1); i--) { Token t = this.tm.get(i); if (t.tokenType == TokenNameDOT) { this.wrapIndexes.add(i); this.secondaryWrapIndexes.add(nameIndex); } if (!t.isComment() && t.tokenType != TokenNamesuper) break; } expression = access.getExpression(); } Collections.reverse(this.wrapIndexes); this.wrapParentIndex = this.tm.lastIndexIn(expression != null ? expression : access.accessExpression, -1); boolean isFollowedByInvocation = node.getParent() instanceof MethodInvocation && node.getLocationInParent() == MethodInvocation.EXPRESSION_PROPERTY; this.wrapGroupEnd = isFollowedByInvocation ? this.tm.lastIndexIn(node.getParent(), -1) : new FieldAccessAdapter(node).getIdentifierIndex(this.tm); // TODO need configuration for this, now only handles line breaks that cannot be removed handleWrap(Alignment.M_NO_ALIGNMENT); } @Override public boolean visit(InfixExpression node) { Integer operatorPrecedence = OPERATOR_PRECEDENCE.get(node.getOperator()); if (operatorPrecedence == null) return true; ASTNode parent = node.getParent(); if ((parent instanceof InfixExpression) && samePrecedence(node, (InfixExpression) parent)) return true; // this node has been handled higher in the AST int wrappingOption = OPERATOR_WRAPPING_OPTION.get(node.getOperator()).applyAsInt(this.options); boolean wrapBeforeOperator = OPERATOR_WRAP_BEFORE_OPTION.get(node.getOperator()).test(this.options); if (this.tm.isStringConcatenation(node)) { wrappingOption = this.options.alignment_for_string_concatenation; wrapBeforeOperator = this.options.wrap_before_string_concatenation; } findTokensToWrap(node, wrapBeforeOperator, 0); this.wrapParentIndex = this.wrapIndexes.remove(0); this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); if ((wrappingOption & Alignment.M_INDENT_ON_COLUMN) != 0 && this.wrapParentIndex > 0) this.wrapParentIndex--; for (int i = this.wrapParentIndex; i >= 0; i--) { if (!this.tm.get(i).isComment()) { this.wrapParentIndex = i; break; } } handleWrap(wrappingOption, !wrapBeforeOperator, node); return true; } private void findTokensToWrap(InfixExpression node, boolean wrapBeforeOperator, int depth) { Expression left = node.getLeftOperand(); if (left instanceof InfixExpression && samePrecedence(node, (InfixExpression) left)) { findTokensToWrap((InfixExpression) left, wrapBeforeOperator, depth + 1); } else if (this.wrapIndexes.isEmpty() // always add first operand, it will be taken as wrap parent || !wrapBeforeOperator) { this.wrapIndexes.add(this.tm.firstIndexIn(left, -1)); } Expression right = node.getRightOperand(); List<Expression> extended = node.extendedOperands(); for (int i = -1; i < extended.size(); i++) { Expression operand = (i == -1) ? right : extended.get(i); if (operand instanceof InfixExpression && samePrecedence(node, (InfixExpression) operand)) { findTokensToWrap((InfixExpression) operand, wrapBeforeOperator, depth + 1); } int indexBefore = this.tm.firstIndexBefore(operand, -1); while (this.tm.get(indexBefore).isComment()) indexBefore--; assert node.getOperator().toString().equals(this.tm.toString(indexBefore)); int indexAfter = this.tm.firstIndexIn(operand, -1); this.wrapIndexes.add(wrapBeforeOperator ? indexBefore : indexAfter); this.secondaryWrapIndexes.add(wrapBeforeOperator ? indexAfter : indexBefore); if (!this.options.join_wrapped_lines) { // TODO there should be an option for never joining wraps on opposite side of the operator if (wrapBeforeOperator) { if (this.tm.countLineBreaksBetween(this.tm.get(indexAfter - 1), this.tm.get(indexAfter)) > 0) this.wrapIndexes.add(indexAfter); } else { if (this.tm.countLineBreaksBetween(this.tm.get(indexBefore), this.tm.get(indexBefore - 1)) > 0) this.wrapIndexes.add(indexBefore); } } } } private boolean samePrecedence(InfixExpression expression1, InfixExpression expression2) { Integer precedence1 = OPERATOR_PRECEDENCE.get(expression1.getOperator()); Integer precedence2 = OPERATOR_PRECEDENCE.get(expression2.getOperator()); if (precedence1 == null || precedence2 == null) return false; return precedence1.equals(precedence2); } @Override public boolean visit(ConditionalExpression node) { boolean chainsMatter = (this.options.alignment_for_conditional_expression_chain & Alignment.SPLIT_MASK) != Alignment.M_NO_ALIGNMENT; boolean isNextInChain = node.getParent() instanceof ConditionalExpression && node == ((ConditionalExpression) node.getParent()).getElseExpression(); boolean isFirstInChain = node.getElseExpression() instanceof ConditionalExpression && !isNextInChain; boolean wrapBefore = this.options.wrap_before_conditional_operator; List<Integer> before = wrapBefore ? this.wrapIndexes : this.secondaryWrapIndexes; List<Integer> after = wrapBefore ? this.secondaryWrapIndexes : this.wrapIndexes; if (!chainsMatter || (!isFirstInChain && !isNextInChain)) { before.add(this.tm.firstIndexAfter(node.getExpression(), TokenNameQUESTION)); before.add(this.tm.firstIndexAfter(node.getThenExpression(), TokenNameCOLON)); after.add(this.tm.firstIndexIn(node.getThenExpression(), -1)); after.add(this.tm.firstIndexIn(node.getElseExpression(), -1)); this.wrapParentIndex = this.tm.lastIndexIn(node.getExpression(), -1); this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); handleWrap(this.options.alignment_for_conditional_expression); } else if (isFirstInChain) { List<ConditionalExpression> chain = new ArrayList<>(); chain.add(node); ConditionalExpression next = node; while (next.getElseExpression() instanceof ConditionalExpression) { next = (ConditionalExpression) next.getElseExpression(); chain.add(next); } for (ConditionalExpression conditional : chain) { before.add(this.tm.firstIndexAfter(conditional.getThenExpression(), TokenNameCOLON)); after.add(this.tm.firstIndexIn(conditional.getElseExpression(), -1)); } this.wrapParentIndex = this.tm.firstIndexIn(node.getExpression(), -1); this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); handleWrap(this.options.alignment_for_conditional_expression_chain); this.currentDepth++; for (ConditionalExpression conditional : chain) { before.add(this.tm.firstIndexAfter(conditional.getExpression(), TokenNameQUESTION)); after.add(this.tm.firstIndexIn(conditional.getThenExpression(), -1)); this.wrapParentIndex = this.tm.firstIndexIn(conditional.getExpression(), -1); this.wrapGroupEnd = this.tm.lastIndexIn(conditional.getThenExpression(), -1); handleWrap(this.options.alignment_for_conditional_expression); } this.currentDepth--; } return true; } @Override public boolean visit(ArrayInitializer node) { List<Expression> expressions = node.expressions(); if (!expressions.isEmpty()) { prepareElementsList(expressions, TokenNameCOMMA, TokenNameLBRACE); handleWrap(this.options.alignment_for_expressions_in_array_initializer, node); } int openingBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE); Token openingBrace = this.tm.get(openingBraceIndex); if (openingBrace.isNextLineOnWrap() && openingBrace.getWrapPolicy() == null && openingBraceIndex > 0) { // add fake wrap policy to make sure the brace indentation is right openingBrace.setWrapPolicy(new WrapPolicy(WrapMode.DISABLED, openingBraceIndex - 1, 0)); } if (!this.options.join_wrapped_lines && !this.options.insert_new_line_before_closing_brace_in_array_initializer) { // if there is a line break before the closing brace, formatter should treat it as a valid wrap to preserve int closingBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE); Token closingBrace = this.tm.get(closingBraceIndex); if (this.tm.countLineBreaksBetween(this.tm.get(closingBraceIndex - 1), closingBrace) == 1) { closingBrace.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, openingBraceIndex, closingBraceIndex, 0, this.currentDepth, 1, true, false)); } } return true; } @Override public boolean visit(Assignment node) { int rightSideIndex = this.tm.firstIndexIn(node.getRightHandSide(), -1); if (this.tm.get(rightSideIndex).getLineBreaksBefore() > 0) return true; // must be an array initializer in new line because of brace_position_for_array_initializer int operatorIndex = this.tm.firstIndexBefore(node.getRightHandSide(), -1); while (this.tm.get(operatorIndex).isComment()) operatorIndex--; assert node.getOperator().toString().equals(this.tm.toString(operatorIndex)); this.wrapIndexes.add(this.options.wrap_before_assignment_operator ? operatorIndex : rightSideIndex); this.secondaryWrapIndexes.add(this.options.wrap_before_assignment_operator ? rightSideIndex : operatorIndex); this.wrapParentIndex = operatorIndex - 1; this.wrapGroupEnd = this.tm.lastIndexIn(node.getRightHandSide(), -1); handleWrap(this.options.alignment_for_assignment); return true; } @Override public boolean visit(VariableDeclarationFragment node) { if (node.getInitializer() == null) return true; int rightSideIndex = this.tm.firstIndexIn(node.getInitializer(), -1); if (this.tm.get(rightSideIndex).getLineBreaksBefore() > 0) return true; // must be an array initializer in new line because of brace_position_for_array_initializer int equalIndex = this.tm.firstIndexBefore(node.getInitializer(), TokenNameEQUAL); this.wrapIndexes.add(this.options.wrap_before_assignment_operator ? equalIndex : rightSideIndex); this.secondaryWrapIndexes.add(this.options.wrap_before_assignment_operator ? rightSideIndex : equalIndex); this.wrapParentIndex = equalIndex - 1; this.wrapGroupEnd = this.tm.lastIndexIn(node.getInitializer(), -1); handleWrap(this.options.alignment_for_assignment); return true; } @Override public boolean visit(IfStatement node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_if_while_statement); Statement elseStatement = node.getElseStatement(); boolean keepThenOnSameLine = this.options.keep_then_statement_on_same_line || (this.options.keep_simple_if_on_one_line && elseStatement == null); if (keepThenOnSameLine) handleSimpleLoop(node.getThenStatement(), this.options.alignment_for_compact_if); if (this.options.keep_else_statement_on_same_line && elseStatement != null) handleSimpleLoop(elseStatement, this.options.alignment_for_compact_if); return true; } @Override public boolean visit(ForStatement node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_for_statement); List<Expression> initializers = node.initializers(); if (!initializers.isEmpty()) this.wrapIndexes.add(this.tm.firstIndexIn(initializers.get(0), -1)); if (node.getExpression() != null) this.wrapIndexes.add(this.tm.firstIndexIn(node.getExpression(), -1)); List<Expression> updaters = node.updaters(); if (!updaters.isEmpty()) this.wrapIndexes.add(this.tm.firstIndexIn(updaters.get(0), -1)); if (!this.wrapIndexes.isEmpty()) { this.wrapParentIndex = lParen; this.wrapGroupEnd = rParen; handleWrap(this.options.alignment_for_expressions_in_for_loop_header); } if (this.options.keep_simple_for_body_on_same_line) handleSimpleLoop(node.getBody(), this.options.alignment_for_compact_loop); return true; } @Override public boolean visit(EnhancedForStatement node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_for_statement); if (this.options.keep_simple_for_body_on_same_line) handleSimpleLoop(node.getBody(), this.options.alignment_for_compact_loop); return true; } @Override public boolean visit(WhileStatement node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_if_while_statement); if (this.options.keep_simple_while_body_on_same_line) handleSimpleLoop(node.getBody(), this.options.alignment_for_compact_loop); return true; } private void handleSimpleLoop(Statement body, int wrappingOption) { if (!(body instanceof Block)) { this.wrapIndexes.add(this.tm.firstIndexIn(body, -1)); this.wrapParentIndex = this.tm.firstIndexBefore(body, TokenNameRPAREN); this.wrapGroupEnd = this.tm.lastIndexIn(body, -1); handleWrap(wrappingOption, body.getParent()); body.accept(new ASTVisitor() { @Override public boolean visit(Block node) { forceContinuousWrapping(node, WrapPreparator.this.tm.firstIndexIn(node, -1)); return false; } }); } } @Override public void endVisit(DoStatement node) { if (this.options.keep_simple_do_while_body_on_same_line && !(node.getBody() instanceof Block)) { int whileIndex = this.tm.firstIndexAfter(node.getBody(), TokenNamewhile); this.wrapIndexes.add(whileIndex); this.wrapParentIndex = this.tm.lastIndexIn(node.getBody(), -1); this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); int alignment = this.options.alignment_for_compact_loop; for (int i = this.tm.firstIndexIn(node, -1) + 1; i < whileIndex; i++) { Token token = this.tm.get(i); if (token.getLineBreaksBefore() > 0 || token.getLineBreaksAfter() > 0) alignment |= Alignment.M_FORCE; } handleWrap(alignment, node); } } @Override public boolean visit(TryStatement node) { if (!node.resources().isEmpty()) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_try_clause); } prepareElementsList(node.resources(), TokenNameSEMICOLON, TokenNameLPAREN); handleWrap(this.options.alignment_for_resources_in_try); return true; } @Override public boolean visit(UnionType node) { List<Type> types = node.types(); if (types.isEmpty()) return true; if (this.options.wrap_before_or_operator_multicatch) { for (Type type : types) { if (this.wrapIndexes.isEmpty()) { this.wrapIndexes.add(this.tm.firstIndexIn(type, -1)); } else { this.wrapIndexes.add(this.tm.firstIndexBefore(type, TokenNameOR)); this.secondaryWrapIndexes.add(this.tm.firstIndexIn(type, -1)); } } this.wrapParentIndex = this.tm.firstIndexBefore(node, -1); while (this.tm.get(this.wrapParentIndex).isComment()) this.wrapParentIndex--; this.wrapGroupEnd = this.tm.lastIndexIn(types.get(types.size() - 1), -1); handleWrap(this.options.alignment_for_union_type_in_multicatch); } else { prepareElementsList(types, TokenNameOR, TokenNameLPAREN); handleWrap(this.options.alignment_for_union_type_in_multicatch); } return true; } @Override public boolean visit(LambdaExpression node) { int lParen = this.tm.firstIndexIn(node, -1); if (this.tm.get(lParen).tokenType == TokenNameLPAREN) { int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_lambda_declaration); } if (node.getBody() instanceof Block) { forceContinuousWrapping(node.getBody(), this.tm.firstIndexIn(node, -1)); List<Statement> statements = ((Block) node.getBody()).statements(); if (!statements.isEmpty()) { int openBraceIndex = this.tm.firstIndexBefore(statements.get(0), TokenNameLBRACE); int closeBraceIndex = this.tm.firstIndexAfter(statements.get(statements.size() - 1), TokenNameRBRACE); boolean areKeptOnOneLine = statements.stream() .allMatch(n -> this.tm.firstTokenIn(n, -1).getLineBreaksBefore() == 0); if (areKeptOnOneLine) { for (Statement statement : statements) this.wrapIndexes.add(this.tm.firstIndexIn(statement, -1)); this.wrapParentIndex = openBraceIndex; this.wrapGroupEnd = closeBraceIndex; handleWrap(Alignment.M_ONE_PER_LINE_SPLIT, node); this.tm.get(closeBraceIndex).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, openBraceIndex, closeBraceIndex, 0, this.currentDepth, 1, false, false)); } } } if (node.hasParentheses()) { List<VariableDeclaration> parameters = node.parameters(); // the legacy formatter didn't like wrapping lambda parameters, so neither do we this.currentDepth++; handleArguments(parameters, this.options.alignment_for_parameters_in_method_declaration); this.currentDepth--; } return true; } @Override public boolean visit(FieldDeclaration node) { handleVariableDeclarations(node.fragments()); return true; } @Override public boolean visit(VariableDeclarationStatement node) { handleVariableDeclarations(node.fragments()); return true; } @Override public boolean visit(ParameterizedType node) { prepareElementsList(node.typeArguments(), TokenNameCOMMA, TokenNameLESS); handleWrap(this.options.alignment_for_parameterized_type_references); return true; } @Override public boolean visit(TypeMethodReference node) { handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(ExpressionMethodReference node) { handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(SuperMethodReference node) { handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(CreationReference node) { handleTypeArguments(node.typeArguments()); return true; } private void handleTypeArguments(List<Type> typeArguments) { if (typeArguments.isEmpty()) return; prepareElementsList(typeArguments, TokenNameCOMMA, TokenNameLESS); handleWrap(this.options.alignment_for_type_arguments); } @Override public boolean visit(ExportsDirective node) { handleModuleStatement(node.modules(), TokenNameto); return true; } @Override public boolean visit(OpensDirective node) { handleModuleStatement(node.modules(), TokenNameto); return true; } @Override public boolean visit(ProvidesDirective node) { handleModuleStatement(node.implementations(), TokenNamewith); return true; } private void handleModuleStatement(List<Name> names, int joiningTokenType) { if (names.isEmpty()) return; int joiningTokenIndex = this.tm.firstIndexBefore(names.get(0), joiningTokenType); this.wrapParentIndex = this.tm.firstIndexBefore(names.get(0), TokenNameIdentifier); this.wrapIndexes.add(joiningTokenIndex); prepareElementsList(names, TokenNameCOMMA, -1); handleWrap(this.options.alignment_for_module_statements, PREFERRED); } @Override public boolean visit(CatchClause node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_catch_clause); return true; } @Override public boolean visit(SwitchStatement node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_switch_statement); return true; } @Override public boolean visit(SwitchExpression node) { int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_switch_statement); return true; } @Override public boolean visit(DoStatement node) { int lParen = this.tm.firstIndexBefore(node.getExpression(), TokenNameLPAREN); int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_if_while_statement); return true; }
Makes sure all new lines within given node will have wrap policy so that wrap executor will fix their indentation if necessary.
/** * Makes sure all new lines within given node will have wrap policy so that * wrap executor will fix their indentation if necessary. */
void forceContinuousWrapping(ASTNode node, int parentIndex) { int parentIndent = this.tm.get(parentIndex).getIndent(); int indentChange = -parentIndent; int lineStart = this.tm.findFirstTokenInLine(parentIndex); for (int i = parentIndex; i >= lineStart; i--) { int align = this.tm.get(i).getAlign(); if (align > 0) { indentChange = -2 * parentIndent + align; break; } } Token previous = null; int from = this.tm.firstIndexIn(node, -1); int to = this.tm.lastIndexIn(node, -1); for (int i = from; i <= to; i++) { Token token = this.tm.get(i); if ((token.getLineBreaksBefore() > 0 || (previous != null && previous.getLineBreaksAfter() > 0)) && (token.getWrapPolicy() == null || token.getWrapPolicy().wrapMode == WrapMode.BLOCK_INDENT)) { int extraIndent = token.getIndent() + indentChange; token.setWrapPolicy(new WrapPolicy(WrapMode.BLOCK_INDENT, parentIndex, extraIndent)); token.setIndent(parentIndent + extraIndent); } previous = token; } } private void handleVariableDeclarations(List<VariableDeclarationFragment> fragments) { if (fragments.size() > 1) { this.wrapParentIndex = this.tm.firstIndexIn(fragments.get(0), -1); prepareElementsList(fragments, TokenNameCOMMA, -1); this.wrapIndexes.remove(0); handleWrap(this.options.alignment_for_multiple_fields); } } private void handleArguments(List<? extends ASTNode> arguments, int wrappingOption) { this.wrapPenalties.add(1 / PREFERRED); prepareElementsList(arguments, TokenNameCOMMA, TokenNameLPAREN); handleWrap(wrappingOption); } private void prepareElementsList(List<? extends ASTNode> elements, int separatorType, int wrapParentType) { for (int i = 0; i < elements.size(); i++) { ASTNode element = elements.get(i); this.wrapIndexes.add(this.tm.firstIndexIn(element, -1)); if (i > 0) this.secondaryWrapIndexes.add(this.tm.firstIndexBefore(element, separatorType)); } // wrapIndexes may have been filled with additional values even if arguments is empty if (!this.wrapIndexes.isEmpty()) { Token firstToken = this.tm.get(this.wrapIndexes.get(0)); if (this.wrapParentIndex < 0) this.wrapParentIndex = this.tm.findIndex(firstToken.originalStart - 1, wrapParentType, false); if (!elements.isEmpty() && this.wrapGroupEnd < 0) this.wrapGroupEnd = this.tm.lastIndexIn(elements.get(elements.size() - 1), -1); } } private void handleWrap(int wrappingOption) { handleWrap(wrappingOption, null); } private void handleWrap(int wrappingOption, float firstPenaltyMultiplier) { this.wrapPenalties.add(firstPenaltyMultiplier); handleWrap(wrappingOption, null); } private void handleWrap(int wrappingOption, ASTNode parentNode) { handleWrap(wrappingOption, true, parentNode); } private void handleWrap(int wrappingOption, boolean wrapPreceedingComments, ASTNode parentNode) { doHandleWrap(wrappingOption, wrapPreceedingComments, parentNode); this.wrapIndexes.clear(); this.secondaryWrapIndexes.clear(); this.wrapPenalties.clear(); this.wrapParentIndex = this.wrapGroupEnd = -1; } private void doHandleWrap(int wrappingOption, boolean wrapPreceedingComments, ASTNode parentNode) { if (this.wrapIndexes.isEmpty()) return; assert this.wrapParentIndex >= 0 && this.wrapParentIndex < this.wrapIndexes.get(0); assert this.wrapGroupEnd >= this.wrapIndexes.get(this.wrapIndexes.size() - 1); while (this.tm.get(this.wrapParentIndex).isComment() && this.wrapParentIndex > 0) this.wrapParentIndex--; float penalty = this.wrapPenalties.isEmpty() ? 1 : this.wrapPenalties.get(0); WrapPolicy policy = getWrapPolicy(wrappingOption, penalty, true, parentNode); WrapPolicy existing = this.tm.get(this.wrapIndexes.get(0)).getWrapPolicy(); if (existing != null && existing.wrapMode == WrapMode.TOP_PRIORITY) { // SEPARATE_LINES_IF_WRAPPED assert existing.wrapParentIndex == this.wrapParentIndex; this.wrapGroupEnd = existing.groupEndIndex; policy = new WrapPolicy(WrapMode.TOP_PRIORITY, policy.wrapParentIndex, this.wrapGroupEnd, policy.extraIndent, policy.structureDepth, policy.penaltyMultiplier, true, policy.indentOnColumn); } setTokenWrapPolicy(0, policy, true); for (int i = 1; i < this.wrapIndexes.size(); i++) { penalty = this.wrapPenalties.size() > i ? this.wrapPenalties.get(i) : 1; if (penalty != policy.penaltyMultiplier || i == 1) policy = getWrapPolicy(wrappingOption, penalty, false, parentNode); setTokenWrapPolicy(i, policy, wrapPreceedingComments); } if (!this.secondaryWrapIndexes.isEmpty()) { int optionNoAlignment = (wrappingOption & ~Alignment.SPLIT_MASK) | Alignment.M_NO_ALIGNMENT; policy = getWrapPolicy(optionNoAlignment, 1, false, parentNode); for (int index : this.secondaryWrapIndexes) { Token token = this.tm.get(index); if (token.getWrapPolicy() == null) token.setWrapPolicy(policy); } } } private void setTokenWrapPolicy(int wrapIndexesIndex, WrapPolicy policy, boolean wrapPreceedingComments) { int index = this.wrapIndexes.get(wrapIndexesIndex); if (wrapPreceedingComments) { for (int i = index - 1; i >= 0; i--) { Token previous = this.tm.get(i); if (!previous.isComment()) break; if (previous.getWrapPolicy() == WrapPolicy.FORCE_FIRST_COLUMN) break; if (previous.getLineBreaksAfter() == 0 && i == index - 1) index = i; if (previous.getLineBreaksBefore() > 0) previous.setWrapPolicy(policy); } this.wrapIndexes.set(wrapIndexesIndex, index); } Token token = this.tm.get(index); if (token.getWrapPolicy() == WrapPolicy.DISABLE_WRAP) return; token.setWrapPolicy(policy); if (policy.wrapMode == WrapMode.FORCE) { token.breakBefore(); } else if (this.options.join_wrapped_lines && token.tokenType == TokenNameCOMMENT_BLOCK) { // allow wrap preparator to decide if this comment should be wrapped token.clearLineBreaksBefore(); } } private WrapPolicy getWrapPolicy(int wrappingOption, float penaltyMultiplier, boolean isFirst, ASTNode parentNode) { assert this.wrapParentIndex >= 0 && this.wrapGroupEnd >= 0; int extraIndent = this.options.continuation_indentation; boolean indentOnColumn = (wrappingOption & Alignment.M_INDENT_ON_COLUMN) != 0; boolean isForceWrap = (wrappingOption & Alignment.M_FORCE) != 0; boolean isAlreadyWrapped = false; if (indentOnColumn) { extraIndent = 0; } else if (parentNode instanceof EnumDeclaration) { // special behavior for compatibility with legacy formatter extraIndent = ((wrappingOption & Alignment.M_INDENT_BY_ONE) != 0) ? 2 : 1; if (!this.options.indent_body_declarations_compare_to_enum_declaration_header) extraIndent--; isAlreadyWrapped = isFirst; } else if (parentNode instanceof IfStatement || parentNode instanceof ForStatement || parentNode instanceof EnhancedForStatement || parentNode instanceof WhileStatement) { extraIndent = 1; this.wrapParentIndex = this.tm.firstIndexIn(parentNode, -1); // only if !indoentOnColumn } else if (parentNode instanceof DoStatement) { extraIndent = 0; this.wrapParentIndex = this.tm.firstIndexIn(parentNode, -1); // only if !indoentOnColumn } else if (parentNode instanceof LambdaExpression) { extraIndent = 1; } else if ((wrappingOption & Alignment.M_INDENT_BY_ONE) != 0) { extraIndent = 1; } else if (parentNode instanceof ArrayInitializer) { extraIndent = this.options.continuation_indentation_for_array_initializer; isAlreadyWrapped = isFirst && this.options.insert_new_line_after_opening_brace_in_array_initializer; } WrapMode wrapMode = WrapMode.WHERE_NECESSARY; boolean isTopPriority = false; switch (wrappingOption & Alignment.SPLIT_MASK) { case Alignment.M_NO_ALIGNMENT: wrapMode = WrapMode.DISABLED; isForceWrap = false; break; case Alignment.M_COMPACT_FIRST_BREAK_SPLIT: isTopPriority = isFirst; isForceWrap &= isFirst; break; case Alignment.M_ONE_PER_LINE_SPLIT: isTopPriority = true; break; case Alignment.M_NEXT_SHIFTED_SPLIT: isTopPriority = true; if (!isFirst) extraIndent++; break; case Alignment.M_NEXT_PER_LINE_SPLIT: isTopPriority = !isFirst; isForceWrap &= !isFirst; break; } if (isForceWrap) { wrapMode = WrapMode.FORCE; } else if (isAlreadyWrapped) { wrapMode = WrapMode.DISABLED; // to avoid triggering top priority wrapping } else if (isTopPriority) { wrapMode = WrapMode.TOP_PRIORITY; } extraIndent *= this.options.indentation_size; return new WrapPolicy(wrapMode, this.wrapParentIndex, this.wrapGroupEnd, extraIndent, this.currentDepth, penaltyMultiplier, isFirst, indentOnColumn); } public void finishUp(ASTNode astRoot, List<IRegion> regions) { preserveExistingLineBreaks(); applyBreaksOutsideRegions(regions); new WrapExecutor(this.tm, this.options).executeWraps(); this.aligner.alignComments(); wrapComments(); fixEnumConstantIndents(astRoot); } private void preserveExistingLineBreaks() { // normally n empty lines = n+1 line breaks, but not at the file start and end Token first = this.tm.get(0); int startingBreaks = first.getLineBreaksBefore(); first.clearLineBreaksBefore(); first.putLineBreaksBefore(startingBreaks - 1); this.tm.traverse(0, new TokenTraverser() { boolean join_wrapped_lines = WrapPreparator.this.options.join_wrapped_lines; @Override protected boolean token(Token token, int index) { int lineBreaks = getLineBreaksToPreserve(getPrevious(), token); if (lineBreaks > 1 || (!this.join_wrapped_lines && token.isWrappable()) || index == 0) token.putLineBreaksBefore(lineBreaks); return true; } }); Token last = this.tm.get(this.tm.size() - 1); last.clearLineBreaksAfter(); int endingBreaks = getLineBreaksToPreserve(last, null); if (endingBreaks > 0) { last.putLineBreaksAfter(endingBreaks); } else if ((this.kind & (CodeFormatter.K_COMPILATION_UNIT | CodeFormatter.K_MODULE_INFO)) != 0 && this.options.insert_new_line_at_end_of_file_if_missing) { last.breakAfter(); } } int getLineBreaksToPreserve(Token token1, Token token2) { if ((token1 != null && !token1.isPreserveLineBreaksAfter()) || (token2 != null && !token2.isPreserveLineBreaksBefore())) { return 0; } if (token1 != null) { List<Token> structure = token1.getInternalStructure(); if (structure != null && !structure.isEmpty()) token1 = structure.get(structure.size() - 1); } if (token2 != null) { List<Token> structure = token2.getInternalStructure(); if (structure != null && !structure.isEmpty()) token2 = structure.get(0); } int lineBreaks = WrapPreparator.this.tm.countLineBreaksBetween(token1, token2); int toPreserve = this.options.number_of_empty_lines_to_preserve; if (token1 != null && token2 != null) toPreserve++; // n empty lines = n+1 line breaks, except for file start and end return Math.min(lineBreaks, toPreserve); } private void applyBreaksOutsideRegions(List<IRegion> regions) { String source = this.tm.getSource(); int previousRegionEnd = 0; for (IRegion region : regions) { int index = this.tm.findIndex(previousRegionEnd, -1, true); Token token = this.tm.get(index); if (this.tm.countLineBreaksBetween(source, previousRegionEnd, Math.min(token.originalStart, region.getOffset())) > 0) token.breakBefore(); for (index++; index < this.tm.size(); index++) { Token next = this.tm.get(index); if (next.originalStart > region.getOffset()) { if (this.tm.countLineBreaksBetween(source, token.originalEnd, region.getOffset()) > 0) next.breakBefore(); break; } if (this.tm.countLineBreaksBetween(token, next) > 0) next.breakBefore(); token = next; } previousRegionEnd = region.getOffset() + region.getLength() - 1; } } private void wrapComments() { CommentWrapExecutor commentWrapper = new CommentWrapExecutor(this.tm, this.options); boolean isNLSTagInLine = false; for (int i = 0; i < this.tm.size(); i++) { Token token = this.tm.get(i); if (token.getLineBreaksBefore() > 0 || token.getLineBreaksAfter() > 0) isNLSTagInLine = false; if (token.hasNLSTag()) { assert token.tokenType == TokenNameStringLiteral; isNLSTagInLine = true; } List<Token> structure = token.getInternalStructure(); if (token.isComment() && structure != null && !structure.isEmpty() && !isNLSTagInLine) { int startPosition = this.tm.getPositionInLine(i); if (token.tokenType == TokenNameCOMMENT_LINE) { commentWrapper.wrapLineComment(token, startPosition); } else { assert token.tokenType == TokenNameCOMMENT_BLOCK || token.tokenType == TokenNameCOMMENT_JAVADOC; commentWrapper.wrapMultiLineComment(token, startPosition, false, false); } } } } private void fixEnumConstantIndents(ASTNode astRoot) { if (this.options.use_tabs_only_for_leading_indentations) { // enum constants should be indented like other declarations, not like wrapped elements astRoot.accept(new ASTVisitor() { @Override public boolean visit(EnumConstantDeclaration node) { WrapPreparator.this.tm.firstTokenIn(node, -1).setWrapPolicy(null); return true; } }); } } private void handleParenthesesPositions(int openingParenIndex, int closingParenIndex, String positionsSetting) { boolean isEmpty = openingParenIndex + 1 == closingParenIndex; switch (positionsSetting) { case DefaultCodeFormatterConstants.COMMON_LINES: // nothing to do break; case DefaultCodeFormatterConstants.SEPARATE_LINES_IF_WRAPPED: if (isEmpty) break; this.tm.get(openingParenIndex + 1).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, openingParenIndex, closingParenIndex, this.options.indentation_size, 1, 1, true, false)); this.tm.get(closingParenIndex).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, openingParenIndex, closingParenIndex, 0, 1, 1, false, false)); break; case DefaultCodeFormatterConstants.SEPARATE_LINES_IF_NOT_EMPTY: if (isEmpty) break; //$FALL-THROUGH$ case DefaultCodeFormatterConstants.SEPARATE_LINES: case DefaultCodeFormatterConstants.PRESERVE_POSITIONS: boolean always = !positionsSetting.equals(DefaultCodeFormatterConstants.PRESERVE_POSITIONS); Token afterOpening = this.tm.get(openingParenIndex + 1); if (always || this.tm.countLineBreaksBetween(this.tm.get(openingParenIndex), afterOpening) > 0) { afterOpening.setWrapPolicy( new WrapPolicy(WrapMode.WHERE_NECESSARY, openingParenIndex, this.options.indentation_size)); afterOpening.breakBefore(); } Token closingParen = this.tm.get(closingParenIndex); if (always || this.tm.countLineBreaksBetween(this.tm.get(closingParenIndex - 1), closingParen) > 0) { closingParen.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, openingParenIndex, 0)); closingParen.breakBefore(); } break; default: throw new IllegalArgumentException("Unrecognized parentheses positions setting: " + positionsSetting); //$NON-NLS-1$ } } }