diff --git a/.eclipse/org.eclipse.jdt.core.prefs b/.eclipse/org.eclipse.jdt.core.prefs index 1d379438..cc3e405e 100644 --- a/.eclipse/org.eclipse.jdt.core.prefs +++ b/.eclipse/org.eclipse.jdt.core.prefs @@ -127,7 +127,7 @@ org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=84 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 @@ -136,7 +136,7 @@ org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line=false +org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line=true org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.blank_lines_before_field=0 diff --git a/spring-javaformat/spring-javaformat-formatter-eclipse-jdt-jdk8/src/main/java/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java b/spring-javaformat/spring-javaformat-formatter-eclipse-jdt-jdk8/src/main/java/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java new file mode 100644 index 00000000..5ddaf1ca --- /dev/null +++ b/spring-javaformat/spring-javaformat-formatter-eclipse-jdt-jdk8/src/main/java/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java @@ -0,0 +1,1687 @@ +/******************************************************************************* + * Copyright (c) 2014, 2020 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 + *******************************************************************************/ +package org.eclipse.jdt.internal.formatter.linewrap; + +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.Annotation; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.AssertStatement; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.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.IExtendedModifier; +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.MemberValuePair; +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.PackageDeclaration; +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.RecordDeclaration; +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.VariableDeclarationExpression; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.dom.WhileStatement; +import org.eclipse.jdt.core.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; + +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; + +public class WrapPreparator extends ASTVisitor { + + // @formatter:off + + /** + * 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_PRECEDENCE; + private static final Map> OPERATOR_WRAPPING_OPTION; + private static final Map> OPERATOR_WRAP_BEFORE_OPTION; + static { + HashMap precedence = new HashMap<>(); + HashMap> wrappingOption = new HashMap<>(); + HashMap> 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 */ + 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 wrapIndexes = new ArrayList<>(); + /** + * Indexes for wraps that shouldn't happen but should be indented if cannot be removed + */ + private List secondaryWrapIndexes = new ArrayList<>(); + private List 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(PackageDeclaration node) { + handleAnnotations(node.annotations(), this.options.alignment_for_annotations_on_package); + return true; + } + + @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) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_type); + + 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 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) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_type); + this.aligner.handleAlign(node.bodyDeclarations()); + return true; + } + + @Override + public boolean visit(AnnotationTypeMemberDeclaration node) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_method); + return true; + } + + @Override + public boolean visit(AnonymousClassDeclaration node) { + this.aligner.handleAlign(node.bodyDeclarations()); + return true; + } + + @Override + public boolean visit(RecordDeclaration node) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_type); + + int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); + List components = node.recordComponents(); + int rParen = this.tm.firstIndexAfter( + components.isEmpty() ? node.getName() : components.get(components.size() - 1), TokenNameRPAREN); + handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_record_declaration); + + if (!components.isEmpty()) { + int wrappingOption = this.options.alignment_for_record_components; + this.wrapGroupEnd = this.tm.lastIndexIn(components.get(components.size() - 1), -1); + handleArguments(components, wrappingOption); + } + + List 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_record_declaration, PREFERRED); + } + return true; + } + + @Override + public boolean visit(MethodDeclaration node) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_method); + + if (!node.isCompactConstructor()) { + 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 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 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 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) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_type); + + List 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 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) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_enum_constant); + + 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) { + // Method patched based on + // https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/189391/4/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java + 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); + expression = (expression != null) ? expression : invocation; + this.wrapParentIndex = this.tm.lastIndexIn(expression, -1); + if ((this.options.alignment_for_selector_in_method_invocation & Alignment.M_INDENT_ON_COLUMN) == 0) { + this.wrapParentIndex = this.tm.firstIndexIn(expression, -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 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 before = wrapBefore ? this.wrapIndexes : this.secondaryWrapIndexes; + List 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 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 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)); + } + } + if (this.options.brace_position_for_array_initializer.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED) + && openingBrace.getWrapPolicy() == null && (node.getParent() instanceof SingleMemberAnnotation + || node.getParent() instanceof MemberValuePair)) { + int parentIndex = this.tm.firstIndexIn(node.getParent(), -1); + int indent = this.options.indentation_size; + openingBrace.setWrapPolicy(new WrapPolicy(WrapMode.BLOCK_INDENT, parentIndex, indent)); + } + 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 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 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 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 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 = this.tm.stream().skip(openBraceIndex + 1) + .limit(closeBraceIndex - openBraceIndex - 1) + .allMatch(t -> t.getLineBreaksBefore() == 0 && t.getLineBreaksAfter() == 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 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) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_field); + handleVariableDeclarations(node.fragments()); + return true; + } + + @Override + public boolean visit(VariableDeclarationStatement node) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_local_variable); + handleVariableDeclarations(node.fragments()); + return true; + } + + @Override + public boolean visit(VariableDeclarationExpression node) { + handleAnnotations(node.modifiers(), this.options.alignment_for_annotations_on_local_variable); + handleVariableDeclarations(node.fragments()); + return true; + } + + @Override + public boolean visit(SingleVariableDeclaration node) { + handleAnnotations(node.modifiers(), + node.getParent() instanceof EnhancedForStatement + ? this.options.alignment_for_annotations_on_local_variable + : this.options.alignment_for_annotations_on_parameter); + 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 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 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; + } + + @Override + public boolean visit(AssertStatement node) { + Expression message = node.getMessage(); + if (message != null) { + int atColon = this.tm.firstIndexBefore(message, TokenNameCOLON); + int afterColon = this.tm.firstIndexIn(message, -1); + if (this.options.wrap_before_assertion_message_operator) { + this.wrapIndexes.add(atColon); + this.secondaryWrapIndexes.add(afterColon); + } + else { + this.wrapIndexes.add(afterColon); + this.secondaryWrapIndexes.add(atColon); + } + this.wrapParentIndex = this.tm.firstIndexIn(node, -1); + this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); + handleWrap(this.options.alignment_for_assertion_message); + } + return true; + } + + /** + * 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 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 arguments, int wrappingOption) { + this.wrapPenalties.add(1 / PREFERRED); + prepareElementsList(arguments, TokenNameCOMMA, TokenNameLPAREN); + handleWrap(wrappingOption); + } + + private void handleAnnotations(List modifiers, int wrappingOption) { + Annotation last = null; + int i; + for (i = 0; i < modifiers.size(); i++) { + if (modifiers.get(i).isModifier()) { + break; + } + Annotation annotation = (Annotation) modifiers.get(i); + if (i == 0) { + this.wrapParentIndex = this.tm.firstIndexIn(annotation, -1); + } + else { + this.wrapIndexes.add(this.tm.firstIndexIn(annotation, -1)); + this.wrapGroupEnd = this.tm.lastIndexIn(annotation, -1); + } + last = annotation; + } + handleWrap(wrappingOption, last); + + if (i < modifiers.size()) { + // any annotations following other modifiers will be associated with + // declaration type + handleAnnotations(modifiers.subList(i + 1, modifiers.size()), this.options.alignment_for_type_annotations); + } + } + + private void prepareElementsList(List 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 Annotation) { + 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 regions) { + preserveExistingLineBreaks(); + applyBreaksOutsideRegions(regions); + new WrapExecutor(this.tm, this.options, regions).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 structure = token1.getInternalStructure(); + if (structure != null && !structure.isEmpty()) { + token1 = structure.get(structure.size() - 1); + } + } + if (token2 != null) { + List 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 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 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, this.currentDepth, 1, true, false)); + this.tm.get(closingParenIndex).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, openingParenIndex, + closingParenIndex, 0, this.currentDepth, 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$ + } + } + + // @formatter:on +} diff --git a/spring-javaformat/spring-javaformat-formatter-eclipse-rewriter/src/main/java/io/spring/javaformat/formatter/eclipse/rewrite/EclipseRewriter.java b/spring-javaformat/spring-javaformat-formatter-eclipse-rewriter/src/main/java/io/spring/javaformat/formatter/eclipse/rewrite/EclipseRewriter.java index 232f1d25..60aad783 100644 --- a/spring-javaformat/spring-javaformat-formatter-eclipse-rewriter/src/main/java/io/spring/javaformat/formatter/eclipse/rewrite/EclipseRewriter.java +++ b/spring-javaformat/spring-javaformat-formatter-eclipse-rewriter/src/main/java/io/spring/javaformat/formatter/eclipse/rewrite/EclipseRewriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,6 +77,7 @@ private void rewrite(JdkVersion jdkVersion, FileSystem zip) throws IOException { DefaultCodeFormatterManipulator::new); if (jdkVersion == JdkVersion.V8) { rewrite(zip, "org/eclipse/osgi/util/NLS$1.class", NlsJdk8Manipulator::new); + deleteWrapPreparator(zip); } else { rewrite(zip, "org/eclipse/osgi/util/NLS.class", NlsJdk11Manipulator::new); @@ -94,6 +95,11 @@ private void rewrite(FileSystem zip, String name, Function +