| /******************************************************************************* |
| * Copyright (c) 2014, 2018 Mateusz Matela and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * 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.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.TokenNameIdentifier; |
| 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.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| 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.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.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.ImportDeclaration; |
| 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.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.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.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 {@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 final static Map<Operator, Integer> OPERATOR_PRECEDENCE; |
| static { |
| HashMap<Operator, Integer> precedence = new HashMap<Operator, Integer>(); |
| precedence.put(Operator.TIMES, 1); |
| precedence.put(Operator.DIVIDE, 1); |
| precedence.put(Operator.REMAINDER, 1); |
| precedence.put(Operator.PLUS, 2); |
| precedence.put(Operator.MINUS, 2); |
| // shift and comparison operators left out intentionally for compatibility with |
| // the legacy formatter, which did not wrap these operators |
| precedence.put(Operator.AND, 6); |
| precedence.put(Operator.XOR, 7); |
| precedence.put(Operator.OR, 8); |
| precedence.put(Operator.CONDITIONAL_AND, 9); |
| precedence.put(Operator.CONDITIONAL_OR, 10); |
| // ternary and assignment operators not relevant to infix expressions |
| OPERATOR_PRECEDENCE = Collections.unmodifiableMap(precedence); |
| } |
| |
| /** Penalty multiplier for wraps that are preferred */ |
| private final static float PREFERRED = 7f / 8; |
| |
| final TokenManager tm; |
| final DefaultCodeFormatterOptions options; |
| final int kind; |
| |
| final FieldAligner fieldAligner; |
| |
| int importsStart = -1, importsEnd = -1; |
| |
| /* |
| * temporary values used when calling {@link #handleWrap(int)} to avoid ArrayList initialization and long lists of |
| * parameters |
| */ |
| private List<Integer> wrapIndexes = new ArrayList<Integer>(); |
| /** Indexes for wraps that shouldn't happen but should be indented if cannot be removed */ |
| private List<Integer> secondaryWrapIndexes = new ArrayList<Integer>(); |
| private List<Float> wrapPenalties = new ArrayList<Float>(); |
| 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.fieldAligner = new FieldAligner(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(CompilationUnit node) { |
| List<ImportDeclaration> imports = node.imports(); |
| if (!imports.isEmpty()) { |
| this.importsStart = this.tm.firstIndexIn(imports.get(0), -1); |
| this.importsEnd = this.tm.lastIndexIn(imports.get(imports.size() - 1), -1); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(NormalAnnotation node) { |
| handleArguments(node.values(), this.options.alignment_for_arguments_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.fieldAligner.handleAlign(node.bodyDeclarations()); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(AnnotationTypeDeclaration node) { |
| this.fieldAligner.handleAlign(node.bodyDeclarations()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(AnonymousClassDeclaration node) { |
| this.fieldAligner.handleAlign(node.bodyDeclarations()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(MethodDeclaration node) { |
| 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 = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); |
| 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.fieldAligner.handleAlign(node.bodyDeclarations()); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(EnumConstantDeclaration node) { |
| 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(MethodInvocation node) { |
| 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) { |
| handleArguments(node.arguments(), this.options.alignment_for_arguments_in_method_invocation); |
| handleTypeArguments(node.typeArguments()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(ClassInstanceCreation node) { |
| 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) { |
| handleArguments(node.arguments(), this.options.alignment_for_arguments_in_explicit_constructor_call); |
| handleTypeArguments(node.typeArguments()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(SuperConstructorInvocation node) { |
| 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 |
| |
| findTokensToWrap(node, 0); |
| this.wrapParentIndex = this.wrapIndexes.remove(0); |
| this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); |
| if ((this.options.alignment_for_binary_expression & 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(this.options.alignment_for_binary_expression, node); |
| return true; |
| } |
| |
| private void findTokensToWrap(InfixExpression node, int depth) { |
| Expression left = node.getLeftOperand(); |
| if (left instanceof InfixExpression && samePrecedence(node, (InfixExpression) left)) { |
| findTokensToWrap((InfixExpression) left, depth + 1); |
| } else if (this.wrapIndexes.isEmpty() // always add first operand, it will be taken as wrap parent |
| || !this.options.wrap_before_binary_operator) { |
| 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, 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(this.options.wrap_before_binary_operator ? indexBefore : indexAfter); |
| this.secondaryWrapIndexes.add(this.options.wrap_before_binary_operator ? 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 (this.options.wrap_before_binary_operator) { |
| 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 wrapBefore = this.options.wrap_before_conditional_operator; |
| List<Integer> before = wrapBefore ? this.wrapIndexes : this.secondaryWrapIndexes; |
| List<Integer> after = wrapBefore ? this.secondaryWrapIndexes : this.wrapIndexes; |
| 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); |
| 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) { |
| boolean keepThenOnSameLine = this.options.keep_then_statement_on_same_line |
| || (this.options.keep_simple_if_on_one_line && node.getElseStatement() == null); |
| if (keepThenOnSameLine) |
| handleSimpleLoop(node.getThenStatement(), this.options.alignment_for_compact_if); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(ForStatement node) { |
| 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 = this.tm.firstIndexIn(node, TokenNameLPAREN); |
| this.wrapGroupEnd = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); |
| 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) { |
| 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) { |
| 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) { |
| 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) { |
| if (node.getBody() instanceof Block) { |
| forceContinuousWrapping(node.getBody(), this.tm.firstIndexIn(node, -1)); |
| } |
| 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); |
| } |
| |
| /** |
| * 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) { |
| doHandleWrap(wrappingOption, parentNode); |
| this.wrapIndexes.clear(); |
| this.secondaryWrapIndexes.clear(); |
| this.wrapPenalties.clear(); |
| this.wrapParentIndex = this.wrapGroupEnd = -1; |
| } |
| |
| private void doHandleWrap(int wrappingOption, 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); |
| |
| boolean wrapPreceedingComments = !(parentNode instanceof InfixExpression) |
| || !this.options.wrap_before_binary_operator; |
| 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 ((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.fieldAligner.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) { |
| boolean isBetweenImports = index > WrapPreparator.this.importsStart |
| && index < WrapPreparator.this.importsEnd; |
| int lineBreaks = getLineBreaksToPreserve(getPrevious(), token, isBetweenImports); |
| 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, false); |
| 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, boolean isBetweenImports) { |
| 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); |
| if (isBetweenImports) |
| return lineBreaks > 1 ? (this.options.blank_lines_between_import_groups + 1) : 0; |
| |
| 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 (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; |
| } |
| }); |
| } |
| } |
| } |