1 /******************************************************************************* 2 * Copyright (c) 2014, 2019 Mateusz Matela and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * 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 13 * Mateusz Matela <mateusz.matela@gmail.com> - [formatter] follow up bug for comments - https://bugs.eclipse.org/458208 14 *******************************************************************************/ 15 package org.eclipse.jdt.internal.formatter.linewrap; 16 17 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOLON; 18 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMA; 19 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_BLOCK; 20 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_JAVADOC; 21 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameCOMMENT_LINE; 22 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameDOT; 23 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameEQUAL; 24 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameIdentifier; 25 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLBRACE; 26 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLESS; 27 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLPAREN; 28 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameOR; 29 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameQUESTION; 30 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameRBRACE; 31 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameRPAREN; 32 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameSEMICOLON; 33 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameStringLiteral; 34 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameenum; 35 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameextends; 36 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameimplements; 37 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamenew; 38 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamesuper; 39 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamethis; 40 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamethrows; 41 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameto; 42 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewhile; 43 import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNamewith; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.function.Predicate; 52 import java.util.function.ToIntFunction; 53 54 import org.eclipse.jdt.core.dom.ASTNode; 55 import org.eclipse.jdt.core.dom.ASTVisitor; 56 import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; 57 import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; 58 import org.eclipse.jdt.core.dom.ArrayInitializer; 59 import org.eclipse.jdt.core.dom.Assignment; 60 import org.eclipse.jdt.core.dom.Block; 61 import org.eclipse.jdt.core.dom.CatchClause; 62 import org.eclipse.jdt.core.dom.ClassInstanceCreation; 63 import org.eclipse.jdt.core.dom.ConditionalExpression; 64 import org.eclipse.jdt.core.dom.ConstructorInvocation; 65 import org.eclipse.jdt.core.dom.CreationReference; 66 import org.eclipse.jdt.core.dom.DoStatement; 67 import org.eclipse.jdt.core.dom.EnhancedForStatement; 68 import org.eclipse.jdt.core.dom.EnumConstantDeclaration; 69 import org.eclipse.jdt.core.dom.EnumDeclaration; 70 import org.eclipse.jdt.core.dom.ExportsDirective; 71 import org.eclipse.jdt.core.dom.Expression; 72 import org.eclipse.jdt.core.dom.ExpressionMethodReference; 73 import org.eclipse.jdt.core.dom.FieldAccess; 74 import org.eclipse.jdt.core.dom.FieldDeclaration; 75 import org.eclipse.jdt.core.dom.ForStatement; 76 import org.eclipse.jdt.core.dom.IfStatement; 77 import org.eclipse.jdt.core.dom.InfixExpression; 78 import org.eclipse.jdt.core.dom.InfixExpression.Operator; 79 import org.eclipse.jdt.core.dom.LambdaExpression; 80 import org.eclipse.jdt.core.dom.MethodDeclaration; 81 import org.eclipse.jdt.core.dom.MethodInvocation; 82 import org.eclipse.jdt.core.dom.Name; 83 import org.eclipse.jdt.core.dom.NormalAnnotation; 84 import org.eclipse.jdt.core.dom.OpensDirective; 85 import org.eclipse.jdt.core.dom.ParameterizedType; 86 import org.eclipse.jdt.core.dom.ProvidesDirective; 87 import org.eclipse.jdt.core.dom.QualifiedName; 88 import org.eclipse.jdt.core.dom.RecordDeclaration; 89 import org.eclipse.jdt.core.dom.SingleMemberAnnotation; 90 import org.eclipse.jdt.core.dom.SingleVariableDeclaration; 91 import org.eclipse.jdt.core.dom.Statement; 92 import org.eclipse.jdt.core.dom.SuperConstructorInvocation; 93 import org.eclipse.jdt.core.dom.SuperFieldAccess; 94 import org.eclipse.jdt.core.dom.SuperMethodInvocation; 95 import org.eclipse.jdt.core.dom.SuperMethodReference; 96 import org.eclipse.jdt.core.dom.SwitchExpression; 97 import org.eclipse.jdt.core.dom.SwitchStatement; 98 import org.eclipse.jdt.core.dom.ThisExpression; 99 import org.eclipse.jdt.core.dom.TryStatement; 100 import org.eclipse.jdt.core.dom.Type; 101 import org.eclipse.jdt.core.dom.TypeDeclaration; 102 import org.eclipse.jdt.core.dom.TypeMethodReference; 103 import org.eclipse.jdt.core.dom.TypeParameter; 104 import org.eclipse.jdt.core.dom.UnionType; 105 import org.eclipse.jdt.core.dom.VariableDeclaration; 106 import org.eclipse.jdt.core.dom.VariableDeclarationFragment; 107 import org.eclipse.jdt.core.dom.VariableDeclarationStatement; 108 import org.eclipse.jdt.core.dom.WhileStatement; 109 import org.eclipse.jdt.core.formatter.CodeFormatter; 110 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; 111 import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions; 112 import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions.Alignment; 113 import org.eclipse.jdt.internal.formatter.Token; 114 import org.eclipse.jdt.internal.formatter.Token.WrapMode; 115 import org.eclipse.jdt.internal.formatter.Token.WrapPolicy; 116 import org.eclipse.jdt.internal.formatter.TokenManager; 117 import org.eclipse.jdt.internal.formatter.TokenTraverser; 118 import org.eclipse.jface.text.IRegion; 119 120 public class WrapPreparator extends ASTVisitor { 121 122 /** 123 * Helper for common handling of all expressions that should be treated the same as {@link FieldAccess} 124 */ 125 private static class FieldAccessAdapter { 126 final Expression accessExpression; 127 FieldAccessAdapter(Expression expression)128 public FieldAccessAdapter(Expression expression) { 129 this.accessExpression = expression; 130 } 131 isFieldAccess(ASTNode expr)132 public static boolean isFieldAccess(ASTNode expr) { 133 return expr instanceof FieldAccess || expr instanceof QualifiedName || expr instanceof ThisExpression 134 || expr instanceof SuperFieldAccess; 135 } 136 getExpression()137 public Expression getExpression() { 138 if (this.accessExpression instanceof FieldAccess) 139 return ((FieldAccess) this.accessExpression).getExpression(); 140 if (this.accessExpression instanceof QualifiedName) 141 return ((QualifiedName) this.accessExpression).getQualifier(); 142 if (this.accessExpression instanceof ThisExpression) 143 return ((ThisExpression) this.accessExpression).getQualifier(); 144 if (this.accessExpression instanceof SuperFieldAccess) 145 return ((SuperFieldAccess) this.accessExpression).getQualifier(); 146 throw new AssertionError(); 147 } 148 getIdentifierIndex(TokenManager tm)149 public int getIdentifierIndex(TokenManager tm) { 150 if (this.accessExpression instanceof FieldAccess) 151 return tm.firstIndexIn(((FieldAccess) this.accessExpression).getName(), TokenNameIdentifier); 152 if (this.accessExpression instanceof QualifiedName) 153 return tm.firstIndexIn(((QualifiedName) this.accessExpression).getName(), TokenNameIdentifier); 154 if (this.accessExpression instanceof ThisExpression) 155 return tm.lastIndexIn(this.accessExpression, TokenNamethis); 156 if (this.accessExpression instanceof SuperFieldAccess) 157 return tm.lastIndexIn(this.accessExpression, TokenNamesuper); 158 throw new AssertionError(); 159 } 160 } 161 162 private static final Map<Operator, Integer> OPERATOR_PRECEDENCE; 163 private static final Map<Operator, ToIntFunction<DefaultCodeFormatterOptions>> OPERATOR_WRAPPING_OPTION; 164 private static final Map<Operator, Predicate<DefaultCodeFormatterOptions>> OPERATOR_WRAP_BEFORE_OPTION; 165 static { 166 HashMap<Operator, Integer> precedence = new HashMap<>(); 167 HashMap<Operator, ToIntFunction<DefaultCodeFormatterOptions>> wrappingOption = new HashMap<>(); 168 HashMap<Operator, Predicate<DefaultCodeFormatterOptions>> wrapBeforeOption = new HashMap<>(); 169 for (Operator op : Arrays.asList(Operator.TIMES, Operator.DIVIDE, Operator.REMAINDER)) { precedence.put(op, 1)170 precedence.put(op, 1); wrappingOption.put(op, o -> o.alignment_for_multiplicative_operator)171 wrappingOption.put(op, o -> o.alignment_for_multiplicative_operator); wrapBeforeOption.put(op, o -> o.wrap_before_multiplicative_operator)172 wrapBeforeOption.put(op, o -> o.wrap_before_multiplicative_operator); 173 } 174 for (Operator op : Arrays.asList(Operator.PLUS, Operator.MINUS)) { precedence.put(op, 2)175 precedence.put(op, 2); wrappingOption.put(op, o -> o.alignment_for_additive_operator)176 wrappingOption.put(op, o -> o.alignment_for_additive_operator); wrapBeforeOption.put(op, o -> o.wrap_before_additive_operator)177 wrapBeforeOption.put(op, o -> o.wrap_before_additive_operator); 178 } 179 for (Operator op : Arrays.asList(Operator.LEFT_SHIFT, Operator.RIGHT_SHIFT_SIGNED, 180 Operator.RIGHT_SHIFT_UNSIGNED)) { precedence.put(op, 3)181 precedence.put(op, 3); wrappingOption.put(op, o -> o.alignment_for_shift_operator)182 wrappingOption.put(op, o -> o.alignment_for_shift_operator); wrapBeforeOption.put(op, o -> o.wrap_before_shift_operator)183 wrapBeforeOption.put(op, o -> o.wrap_before_shift_operator); 184 } 185 for (Operator op : Arrays.asList(Operator.LESS, Operator.GREATER, Operator.LESS_EQUALS, 186 Operator.GREATER_EQUALS)) { precedence.put(op, 4)187 precedence.put(op, 4); wrappingOption.put(op, o -> o.alignment_for_relational_operator)188 wrappingOption.put(op, o -> o.alignment_for_relational_operator); wrapBeforeOption.put(op, o -> o.wrap_before_relational_operator)189 wrapBeforeOption.put(op, o -> o.wrap_before_relational_operator); 190 } 191 for (Operator op : Arrays.asList(Operator.EQUALS, Operator.NOT_EQUALS)) { precedence.put(op, 5)192 precedence.put(op, 5); wrappingOption.put(op, o -> o.alignment_for_relational_operator)193 wrappingOption.put(op, o -> o.alignment_for_relational_operator); wrapBeforeOption.put(op, o -> o.wrap_before_relational_operator)194 wrapBeforeOption.put(op, o -> o.wrap_before_relational_operator); 195 } 196 precedence.put(Operator.AND, 6)197 precedence.put(Operator.AND, 6); precedence.put(Operator.XOR, 7)198 precedence.put(Operator.XOR, 7); precedence.put(Operator.OR, 8)199 precedence.put(Operator.OR, 8); 200 for (Operator op : Arrays.asList(Operator.AND, Operator.XOR, Operator.OR)) { wrappingOption.put(op, o -> o.alignment_for_bitwise_operator)201 wrappingOption.put(op, o -> o.alignment_for_bitwise_operator); wrapBeforeOption.put(op, o -> o.wrap_before_bitwise_operator)202 wrapBeforeOption.put(op, o -> o.wrap_before_bitwise_operator); 203 } 204 precedence.put(Operator.CONDITIONAL_AND, 9)205 precedence.put(Operator.CONDITIONAL_AND, 9); precedence.put(Operator.CONDITIONAL_OR, 10)206 precedence.put(Operator.CONDITIONAL_OR, 10); 207 for (Operator op : Arrays.asList(Operator.CONDITIONAL_AND, Operator.CONDITIONAL_OR)) { wrappingOption.put(op, o -> o.alignment_for_logical_operator)208 wrappingOption.put(op, o -> o.alignment_for_logical_operator); wrapBeforeOption.put(op, o -> o.wrap_before_logical_operator)209 wrapBeforeOption.put(op, o -> o.wrap_before_logical_operator); 210 } 211 // ternary and assignment operators not relevant to infix expressions 212 213 OPERATOR_PRECEDENCE = Collections.unmodifiableMap(precedence); 214 OPERATOR_WRAPPING_OPTION = Collections.unmodifiableMap(wrappingOption); 215 OPERATOR_WRAP_BEFORE_OPTION = Collections.unmodifiableMap(wrapBeforeOption); 216 } 217 218 /** Penalty multiplier for wraps that are preferred */ 219 private final static float PREFERRED = 7f / 8; 220 221 final TokenManager tm; 222 final DefaultCodeFormatterOptions options; 223 final int kind; 224 225 final Aligner aligner; 226 227 /* 228 * temporary values used when calling {@link #handleWrap(int)} to avoid ArrayList initialization and long lists of 229 * parameters 230 */ 231 private List<Integer> wrapIndexes = new ArrayList<>(); 232 /** Indexes for wraps that shouldn't happen but should be indented if cannot be removed */ 233 private List<Integer> secondaryWrapIndexes = new ArrayList<>(); 234 private List<Float> wrapPenalties = new ArrayList<>(); 235 private int wrapParentIndex = -1; 236 private int wrapGroupEnd = -1; 237 238 private int currentDepth = 0; 239 WrapPreparator(TokenManager tokenManager, DefaultCodeFormatterOptions options, int kind)240 public WrapPreparator(TokenManager tokenManager, DefaultCodeFormatterOptions options, int kind) { 241 this.tm = tokenManager; 242 this.options = options; 243 this.kind = kind; 244 245 this.aligner = new Aligner(this.tm, this.options); 246 } 247 248 @Override preVisit2(ASTNode node)249 public boolean preVisit2(ASTNode node) { 250 this.currentDepth++; 251 252 assert this.wrapIndexes.isEmpty() && this.secondaryWrapIndexes.isEmpty() && this.wrapPenalties.isEmpty(); 253 assert this.wrapParentIndex == -1 && this.wrapGroupEnd == -1; 254 255 boolean isMalformed = (node.getFlags() & ASTNode.MALFORMED) != 0; 256 if (isMalformed) { 257 this.tm.addDisableFormatTokenPair(this.tm.firstTokenIn(node, -1), this.tm.lastTokenIn(node, -1)); 258 } 259 return !isMalformed; 260 } 261 262 @Override postVisit(ASTNode node)263 public void postVisit(ASTNode node) { 264 this.currentDepth--; 265 } 266 267 @Override visit(NormalAnnotation node)268 public boolean visit(NormalAnnotation node) { 269 int lParen = this.tm.firstIndexAfter(node.getTypeName(), TokenNameLPAREN); 270 int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); 271 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_annotation); 272 273 handleArguments(node.values(), this.options.alignment_for_arguments_in_annotation); 274 return true; 275 } 276 277 @Override visit(SingleMemberAnnotation node)278 public boolean visit(SingleMemberAnnotation node) { 279 int lParen = this.tm.firstIndexAfter(node.getTypeName(), TokenNameLPAREN); 280 int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); 281 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_annotation); 282 return true; 283 } 284 285 @Override visit(TypeDeclaration node)286 public boolean visit(TypeDeclaration node) { 287 Type superclassType = node.getSuperclassType(); 288 if (superclassType != null) { 289 this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); 290 this.wrapGroupEnd = this.tm.lastIndexIn(superclassType, -1); 291 this.wrapIndexes.add(this.tm.firstIndexBefore(superclassType, TokenNameextends)); 292 this.wrapIndexes.add(this.tm.firstIndexIn(superclassType, -1)); 293 handleWrap(this.options.alignment_for_superclass_in_type_declaration, PREFERRED); 294 } 295 296 List<Type> superInterfaceTypes = node.superInterfaceTypes(); 297 if (!superInterfaceTypes.isEmpty()) { 298 int implementsToken = node.isInterface() ? TokenNameextends : TokenNameimplements; 299 this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); 300 this.wrapIndexes.add(this.tm.firstIndexBefore(superInterfaceTypes.get(0), implementsToken)); 301 prepareElementsList(superInterfaceTypes, TokenNameCOMMA, -1); 302 handleWrap(this.options.alignment_for_superinterfaces_in_type_declaration, PREFERRED); 303 } 304 305 prepareElementsList(node.typeParameters(), TokenNameCOMMA, TokenNameLESS); 306 handleWrap(this.options.alignment_for_type_parameters); 307 308 this.aligner.handleAlign(node.bodyDeclarations()); 309 310 return true; 311 } 312 313 @Override visit(AnnotationTypeDeclaration node)314 public boolean visit(AnnotationTypeDeclaration node) { 315 this.aligner.handleAlign(node.bodyDeclarations()); 316 return true; 317 } 318 319 @Override visit(AnonymousClassDeclaration node)320 public boolean visit(AnonymousClassDeclaration node) { 321 this.aligner.handleAlign(node.bodyDeclarations()); 322 return true; 323 } 324 325 @Override visit(RecordDeclaration node)326 public boolean visit(RecordDeclaration node) { 327 int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); 328 List<SingleVariableDeclaration> components = node.recordComponents(); 329 int rParen = this.tm.firstIndexAfter( 330 components.isEmpty() ? node.getName() : components.get(components.size() - 1), TokenNameRPAREN); 331 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_record_declaration); 332 333 if (!components.isEmpty()) { 334 int wrappingOption = this.options.alignment_for_record_components; 335 this.wrapGroupEnd = this.tm.lastIndexIn(components.get(components.size() - 1), -1); 336 handleArguments(components, wrappingOption); 337 } 338 339 List<Type> superInterfaceTypes = node.superInterfaceTypes(); 340 if (!superInterfaceTypes.isEmpty()) { 341 this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); 342 this.wrapIndexes.add(this.tm.firstIndexBefore(superInterfaceTypes.get(0), TokenNameimplements)); 343 prepareElementsList(superInterfaceTypes, TokenNameCOMMA, -1); 344 handleWrap(this.options.alignment_for_superinterfaces_in_record_declaration, PREFERRED); 345 } 346 return true; 347 } 348 349 @Override visit(MethodDeclaration node)350 public boolean visit(MethodDeclaration node) { 351 if (!node.isCompactConstructor()) { 352 int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); 353 int rParen = node.getBody() == null ? this.tm.lastIndexIn(node, TokenNameRPAREN) 354 : this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); 355 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_declaration); 356 } 357 358 List<SingleVariableDeclaration> parameters = node.parameters(); 359 Type receiverType = node.getReceiverType(); 360 if (!parameters.isEmpty() || receiverType != null) { 361 if (receiverType != null) 362 this.wrapIndexes.add(this.tm.firstIndexIn(receiverType, -1)); 363 int wrappingOption = node.isConstructor() ? this.options.alignment_for_parameters_in_constructor_declaration 364 : this.options.alignment_for_parameters_in_method_declaration; 365 this.wrapGroupEnd = this.tm.lastIndexIn( 366 parameters.isEmpty() ? receiverType : parameters.get(parameters.size() - 1), -1); 367 handleArguments(parameters, wrappingOption); 368 } 369 370 List<Type> exceptionTypes = node.thrownExceptionTypes(); 371 if (!exceptionTypes.isEmpty()) { 372 int wrappingOption = node.isConstructor() 373 ? this.options.alignment_for_throws_clause_in_constructor_declaration 374 : this.options.alignment_for_throws_clause_in_method_declaration; 375 if ((wrappingOption & Alignment.M_INDENT_ON_COLUMN) == 0) 376 this.wrapParentIndex = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); 377 prepareElementsList(exceptionTypes, TokenNameCOMMA, TokenNameRPAREN); 378 // instead of the first exception type, wrap the "throws" token 379 this.wrapIndexes.set(0, this.tm.firstIndexBefore(exceptionTypes.get(0), TokenNamethrows)); 380 handleWrap(wrappingOption, 0.5f); 381 } 382 383 if (!node.isConstructor()) { 384 this.wrapParentIndex = this.tm.findFirstTokenInLine(this.tm.firstIndexIn(node.getName(), -1)); 385 while (this.tm.get(this.wrapParentIndex).isComment()) 386 this.wrapParentIndex++; 387 List<TypeParameter> typeParameters = node.typeParameters(); 388 if (!typeParameters.isEmpty()) 389 this.wrapIndexes.add(this.tm.firstIndexIn(typeParameters.get(0), -1)); 390 if (node.getReturnType2() != null) { 391 int returTypeIndex = this.tm.firstIndexIn(node.getReturnType2(), -1); 392 if (returTypeIndex != this.wrapParentIndex) 393 this.wrapIndexes.add(returTypeIndex); 394 } 395 this.wrapIndexes.add(this.tm.firstIndexIn(node.getName(), -1)); 396 this.wrapGroupEnd = this.tm.lastIndexIn(node.getName(), -1); 397 handleWrap(this.options.alignment_for_method_declaration); 398 } 399 400 prepareElementsList(node.typeParameters(), TokenNameCOMMA, TokenNameLESS); 401 handleWrap(this.options.alignment_for_type_parameters); 402 403 return true; 404 } 405 406 @Override visit(EnumDeclaration node)407 public boolean visit(EnumDeclaration node) { 408 List<EnumConstantDeclaration> enumConstants = node.enumConstants(); 409 int constantsEnd = -1; 410 if (!enumConstants.isEmpty()) { 411 for (EnumConstantDeclaration constant : enumConstants) 412 this.wrapIndexes.add(this.tm.firstIndexIn(constant, -1)); 413 this.wrapParentIndex = (this.options.alignment_for_enum_constants & Alignment.M_INDENT_ON_COLUMN) > 0 414 ? this.tm.firstIndexBefore(enumConstants.get(0), TokenNameLBRACE) 415 : this.tm.firstIndexIn(node, TokenNameenum); 416 this.wrapGroupEnd = constantsEnd = this.tm.lastIndexIn(enumConstants.get(enumConstants.size() - 1), -1); 417 handleWrap(this.options.alignment_for_enum_constants, node); 418 } 419 420 if (!this.options.join_wrapped_lines) { 421 // preserve a line break between the last comma and semicolon 422 int commaIndex = -1; 423 int i = constantsEnd > 0 ? constantsEnd : this.tm.firstIndexAfter(node.getName(), TokenNameLBRACE); 424 while (++i < this.tm.size()) { 425 Token t = this.tm.get(i); 426 if (t.isComment()) 427 continue; 428 if (t.tokenType == TokenNameCOMMA) { 429 commaIndex = i; 430 continue; 431 } 432 if (t.tokenType == TokenNameSEMICOLON && commaIndex >= 0 433 && this.tm.countLineBreaksBetween(this.tm.get(commaIndex), t) == 1) { 434 t.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, commaIndex, 0)); 435 } 436 break; 437 } 438 } 439 440 List<Type> superInterfaceTypes = node.superInterfaceTypes(); 441 if (!superInterfaceTypes.isEmpty()) { 442 this.wrapParentIndex = this.tm.lastIndexIn(node.getName(), -1); 443 this.wrapIndexes.add(this.tm.firstIndexBefore(superInterfaceTypes.get(0), TokenNameimplements)); 444 prepareElementsList(superInterfaceTypes, TokenNameCOMMA, -1); 445 handleWrap(this.options.alignment_for_superinterfaces_in_enum_declaration, PREFERRED); 446 } 447 448 this.aligner.handleAlign(node.bodyDeclarations()); 449 450 return true; 451 } 452 453 @Override visit(EnumConstantDeclaration node)454 public boolean visit(EnumConstantDeclaration node) { 455 int lParen = this.tm.firstIndexAfter(node.getName(), -1); 456 while (this.tm.get(lParen).isComment()) 457 lParen++; 458 if (this.tm.get(lParen).tokenType == TokenNameLPAREN) { 459 int rParen = node.getAnonymousClassDeclaration() == null ? this.tm.lastIndexIn(node, TokenNameRPAREN) 460 : this.tm.firstIndexBefore(node.getAnonymousClassDeclaration(), TokenNameRPAREN); 461 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_enum_constant_declaration); 462 } 463 464 handleArguments(node.arguments(), this.options.alignment_for_arguments_in_enum_constant); 465 AnonymousClassDeclaration anonymousClass = node.getAnonymousClassDeclaration(); 466 if (anonymousClass != null) { 467 forceContinuousWrapping(anonymousClass, this.tm.firstIndexIn(node.getName(), -1)); 468 } 469 return true; 470 } 471 472 @Override visit(Block node)473 public boolean visit(Block node) { 474 this.aligner.handleAlign(node); 475 return true; 476 } 477 478 @Override visit(MethodInvocation node)479 public boolean visit(MethodInvocation node) { 480 int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); 481 int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); 482 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); 483 484 handleArguments(node.arguments(), this.options.alignment_for_arguments_in_method_invocation); 485 handleTypeArguments(node.typeArguments()); 486 487 boolean isInvocationChainRoot = !(node.getParent() instanceof MethodInvocation) 488 || node.getLocationInParent() != MethodInvocation.EXPRESSION_PROPERTY; 489 if (isInvocationChainRoot) { 490 Expression expression = node; 491 MethodInvocation invocation = node; 492 while (expression instanceof MethodInvocation) { 493 invocation = (MethodInvocation) expression; 494 expression = invocation.getExpression(); 495 if (expression != null) { 496 this.wrapIndexes.add(this.tm.firstIndexBefore(invocation.getName(), TokenNameDOT)); 497 this.secondaryWrapIndexes.add(this.tm.firstIndexIn(invocation.getName(), TokenNameIdentifier)); 498 } 499 } 500 Collections.reverse(this.wrapIndexes); 501 this.wrapParentIndex = (expression != null) ? this.tm.lastIndexIn(expression, -1) 502 : this.tm.lastIndexIn(invocation, -1); 503 this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); 504 handleWrap(this.options.alignment_for_selector_in_method_invocation); 505 } 506 return true; 507 } 508 509 @Override visit(SuperMethodInvocation node)510 public boolean visit(SuperMethodInvocation node) { 511 int lParen = this.tm.firstIndexAfter(node.getName(), TokenNameLPAREN); 512 int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); 513 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); 514 515 handleArguments(node.arguments(), this.options.alignment_for_arguments_in_method_invocation); 516 handleTypeArguments(node.typeArguments()); 517 return true; 518 } 519 520 @Override visit(ClassInstanceCreation node)521 public boolean visit(ClassInstanceCreation node) { 522 int lParen = this.tm.firstIndexAfter(node.getType(), TokenNameLPAREN); 523 int rParen = node.getAnonymousClassDeclaration() == null ? this.tm.lastIndexIn(node, TokenNameRPAREN) 524 : this.tm.firstIndexBefore(node.getAnonymousClassDeclaration(), TokenNameRPAREN); 525 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); 526 527 AnonymousClassDeclaration anonymousClass = node.getAnonymousClassDeclaration(); 528 if (anonymousClass != null) { 529 forceContinuousWrapping(anonymousClass, this.tm.firstIndexIn(node, TokenNamenew)); 530 } 531 532 int wrappingOption = node.getExpression() != null 533 ? this.options.alignment_for_arguments_in_qualified_allocation_expression 534 : this.options.alignment_for_arguments_in_allocation_expression; 535 handleArguments(node.arguments(), wrappingOption); 536 537 handleTypeArguments(node.typeArguments()); 538 return true; 539 } 540 541 @Override visit(ConstructorInvocation node)542 public boolean visit(ConstructorInvocation node) { 543 int lParen = node.arguments().isEmpty() ? this.tm.lastIndexIn(node, TokenNameLPAREN) 544 : this.tm.firstIndexBefore((ASTNode) node.arguments().get(0), TokenNameLPAREN); 545 int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); 546 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); 547 548 handleArguments(node.arguments(), this.options.alignment_for_arguments_in_explicit_constructor_call); 549 handleTypeArguments(node.typeArguments()); 550 return true; 551 } 552 553 @Override visit(SuperConstructorInvocation node)554 public boolean visit(SuperConstructorInvocation node) { 555 int lParen = node.arguments().isEmpty() ? this.tm.lastIndexIn(node, TokenNameLPAREN) 556 : this.tm.firstIndexBefore((ASTNode) node.arguments().get(0), TokenNameLPAREN); 557 int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN); 558 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_method_invocation); 559 560 handleArguments(node.arguments(), this.options.alignment_for_arguments_in_explicit_constructor_call); 561 handleTypeArguments(node.typeArguments()); 562 return true; 563 } 564 565 @Override visit(FieldAccess node)566 public boolean visit(FieldAccess node) { 567 handleFieldAccess(node); 568 return true; 569 } 570 571 @Override visit(QualifiedName node)572 public boolean visit(QualifiedName node) { 573 handleFieldAccess(node); 574 return true; 575 } 576 577 @Override visit(ThisExpression node)578 public boolean visit(ThisExpression node) { 579 handleFieldAccess(node); 580 return true; 581 } 582 583 @Override visit(SuperFieldAccess node)584 public boolean visit(SuperFieldAccess node) { 585 handleFieldAccess(node); 586 return true; 587 } 588 handleFieldAccess(Expression node)589 private void handleFieldAccess(Expression node) { 590 boolean isAccessChainRoot = !FieldAccessAdapter.isFieldAccess(node.getParent()); 591 if (!isAccessChainRoot) 592 return; 593 594 Expression expression = node; 595 FieldAccessAdapter access = null; 596 while (FieldAccessAdapter.isFieldAccess(expression)) { 597 access = new FieldAccessAdapter(expression); 598 int nameIndex = access.getIdentifierIndex(this.tm); 599 // find a dot preceding the name, may not be there 600 for (int i = nameIndex - 1; i > this.tm.firstIndexIn(node, -1); i--) { 601 Token t = this.tm.get(i); 602 if (t.tokenType == TokenNameDOT) { 603 this.wrapIndexes.add(i); 604 this.secondaryWrapIndexes.add(nameIndex); 605 } 606 if (!t.isComment() && t.tokenType != TokenNamesuper) 607 break; 608 } 609 expression = access.getExpression(); 610 } 611 Collections.reverse(this.wrapIndexes); 612 this.wrapParentIndex = this.tm.lastIndexIn(expression != null ? expression : access.accessExpression, -1); 613 boolean isFollowedByInvocation = node.getParent() instanceof MethodInvocation 614 && node.getLocationInParent() == MethodInvocation.EXPRESSION_PROPERTY; 615 this.wrapGroupEnd = isFollowedByInvocation ? this.tm.lastIndexIn(node.getParent(), -1) 616 : new FieldAccessAdapter(node).getIdentifierIndex(this.tm); 617 // TODO need configuration for this, now only handles line breaks that cannot be removed 618 handleWrap(Alignment.M_NO_ALIGNMENT); 619 } 620 621 @Override visit(InfixExpression node)622 public boolean visit(InfixExpression node) { 623 Integer operatorPrecedence = OPERATOR_PRECEDENCE.get(node.getOperator()); 624 if (operatorPrecedence == null) 625 return true; 626 ASTNode parent = node.getParent(); 627 if ((parent instanceof InfixExpression) && samePrecedence(node, (InfixExpression) parent)) 628 return true; // this node has been handled higher in the AST 629 630 int wrappingOption = OPERATOR_WRAPPING_OPTION.get(node.getOperator()).applyAsInt(this.options); 631 boolean wrapBeforeOperator = OPERATOR_WRAP_BEFORE_OPTION.get(node.getOperator()).test(this.options); 632 if (this.tm.isStringConcatenation(node)) { 633 wrappingOption = this.options.alignment_for_string_concatenation; 634 wrapBeforeOperator = this.options.wrap_before_string_concatenation; 635 } 636 637 findTokensToWrap(node, wrapBeforeOperator, 0); 638 this.wrapParentIndex = this.wrapIndexes.remove(0); 639 this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); 640 if ((wrappingOption & Alignment.M_INDENT_ON_COLUMN) != 0 && this.wrapParentIndex > 0) 641 this.wrapParentIndex--; 642 for (int i = this.wrapParentIndex; i >= 0; i--) { 643 if (!this.tm.get(i).isComment()) { 644 this.wrapParentIndex = i; 645 break; 646 } 647 } 648 handleWrap(wrappingOption, !wrapBeforeOperator, node); 649 return true; 650 } 651 findTokensToWrap(InfixExpression node, boolean wrapBeforeOperator, int depth)652 private void findTokensToWrap(InfixExpression node, boolean wrapBeforeOperator, int depth) { 653 Expression left = node.getLeftOperand(); 654 if (left instanceof InfixExpression && samePrecedence(node, (InfixExpression) left)) { 655 findTokensToWrap((InfixExpression) left, wrapBeforeOperator, depth + 1); 656 } else if (this.wrapIndexes.isEmpty() // always add first operand, it will be taken as wrap parent 657 || !wrapBeforeOperator) { 658 this.wrapIndexes.add(this.tm.firstIndexIn(left, -1)); 659 } 660 661 Expression right = node.getRightOperand(); 662 List<Expression> extended = node.extendedOperands(); 663 for (int i = -1; i < extended.size(); i++) { 664 Expression operand = (i == -1) ? right : extended.get(i); 665 if (operand instanceof InfixExpression && samePrecedence(node, (InfixExpression) operand)) { 666 findTokensToWrap((InfixExpression) operand, wrapBeforeOperator, depth + 1); 667 } 668 int indexBefore = this.tm.firstIndexBefore(operand, -1); 669 while (this.tm.get(indexBefore).isComment()) 670 indexBefore--; 671 assert node.getOperator().toString().equals(this.tm.toString(indexBefore)); 672 int indexAfter = this.tm.firstIndexIn(operand, -1); 673 this.wrapIndexes.add(wrapBeforeOperator ? indexBefore : indexAfter); 674 this.secondaryWrapIndexes.add(wrapBeforeOperator ? indexAfter : indexBefore); 675 676 if (!this.options.join_wrapped_lines) { 677 // TODO there should be an option for never joining wraps on opposite side of the operator 678 if (wrapBeforeOperator) { 679 if (this.tm.countLineBreaksBetween(this.tm.get(indexAfter - 1), this.tm.get(indexAfter)) > 0) 680 this.wrapIndexes.add(indexAfter); 681 } else { 682 if (this.tm.countLineBreaksBetween(this.tm.get(indexBefore), this.tm.get(indexBefore - 1)) > 0) 683 this.wrapIndexes.add(indexBefore); 684 } 685 } 686 } 687 } 688 samePrecedence(InfixExpression expression1, InfixExpression expression2)689 private boolean samePrecedence(InfixExpression expression1, InfixExpression expression2) { 690 Integer precedence1 = OPERATOR_PRECEDENCE.get(expression1.getOperator()); 691 Integer precedence2 = OPERATOR_PRECEDENCE.get(expression2.getOperator()); 692 if (precedence1 == null || precedence2 == null) 693 return false; 694 return precedence1.equals(precedence2); 695 } 696 697 @Override visit(ConditionalExpression node)698 public boolean visit(ConditionalExpression node) { 699 boolean chainsMatter = (this.options.alignment_for_conditional_expression_chain 700 & Alignment.SPLIT_MASK) != Alignment.M_NO_ALIGNMENT; 701 boolean isNextInChain = node.getParent() instanceof ConditionalExpression 702 && node == ((ConditionalExpression) node.getParent()).getElseExpression(); 703 boolean isFirstInChain = node.getElseExpression() instanceof ConditionalExpression && !isNextInChain; 704 boolean wrapBefore = this.options.wrap_before_conditional_operator; 705 List<Integer> before = wrapBefore ? this.wrapIndexes : this.secondaryWrapIndexes; 706 List<Integer> after = wrapBefore ? this.secondaryWrapIndexes : this.wrapIndexes; 707 if (!chainsMatter || (!isFirstInChain && !isNextInChain)) { 708 before.add(this.tm.firstIndexAfter(node.getExpression(), TokenNameQUESTION)); 709 before.add(this.tm.firstIndexAfter(node.getThenExpression(), TokenNameCOLON)); 710 after.add(this.tm.firstIndexIn(node.getThenExpression(), -1)); 711 after.add(this.tm.firstIndexIn(node.getElseExpression(), -1)); 712 this.wrapParentIndex = this.tm.lastIndexIn(node.getExpression(), -1); 713 this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); 714 handleWrap(this.options.alignment_for_conditional_expression); 715 716 } else if (isFirstInChain) { 717 List<ConditionalExpression> chain = new ArrayList<>(); 718 chain.add(node); 719 ConditionalExpression next = node; 720 while (next.getElseExpression() instanceof ConditionalExpression) { 721 next = (ConditionalExpression) next.getElseExpression(); 722 chain.add(next); 723 } 724 725 for (ConditionalExpression conditional : chain) { 726 before.add(this.tm.firstIndexAfter(conditional.getThenExpression(), TokenNameCOLON)); 727 after.add(this.tm.firstIndexIn(conditional.getElseExpression(), -1)); 728 } 729 this.wrapParentIndex = this.tm.firstIndexIn(node.getExpression(), -1); 730 this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); 731 handleWrap(this.options.alignment_for_conditional_expression_chain); 732 733 this.currentDepth++; 734 for (ConditionalExpression conditional : chain) { 735 before.add(this.tm.firstIndexAfter(conditional.getExpression(), TokenNameQUESTION)); 736 after.add(this.tm.firstIndexIn(conditional.getThenExpression(), -1)); 737 this.wrapParentIndex = this.tm.firstIndexIn(conditional.getExpression(), -1); 738 this.wrapGroupEnd = this.tm.lastIndexIn(conditional.getThenExpression(), -1); 739 handleWrap(this.options.alignment_for_conditional_expression); 740 } 741 this.currentDepth--; 742 } 743 return true; 744 } 745 746 @Override visit(ArrayInitializer node)747 public boolean visit(ArrayInitializer node) { 748 List<Expression> expressions = node.expressions(); 749 if (!expressions.isEmpty()) { 750 prepareElementsList(expressions, TokenNameCOMMA, TokenNameLBRACE); 751 handleWrap(this.options.alignment_for_expressions_in_array_initializer, node); 752 } 753 int openingBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE); 754 Token openingBrace = this.tm.get(openingBraceIndex); 755 if (openingBrace.isNextLineOnWrap() && openingBrace.getWrapPolicy() == null && openingBraceIndex > 0) { 756 // add fake wrap policy to make sure the brace indentation is right 757 openingBrace.setWrapPolicy(new WrapPolicy(WrapMode.DISABLED, openingBraceIndex - 1, 0)); 758 } 759 if (!this.options.join_wrapped_lines 760 && !this.options.insert_new_line_before_closing_brace_in_array_initializer) { 761 // if there is a line break before the closing brace, formatter should treat it as a valid wrap to preserve 762 int closingBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE); 763 Token closingBrace = this.tm.get(closingBraceIndex); 764 if (this.tm.countLineBreaksBetween(this.tm.get(closingBraceIndex - 1), closingBrace) == 1) { 765 closingBrace.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, openingBraceIndex, 766 closingBraceIndex, 0, this.currentDepth, 1, true, false)); 767 } 768 } 769 return true; 770 } 771 772 @Override visit(Assignment node)773 public boolean visit(Assignment node) { 774 int rightSideIndex = this.tm.firstIndexIn(node.getRightHandSide(), -1); 775 if (this.tm.get(rightSideIndex).getLineBreaksBefore() > 0) 776 return true; // must be an array initializer in new line because of brace_position_for_array_initializer 777 778 int operatorIndex = this.tm.firstIndexBefore(node.getRightHandSide(), -1); 779 while (this.tm.get(operatorIndex).isComment()) 780 operatorIndex--; 781 assert node.getOperator().toString().equals(this.tm.toString(operatorIndex)); 782 783 this.wrapIndexes.add(this.options.wrap_before_assignment_operator ? operatorIndex : rightSideIndex); 784 this.secondaryWrapIndexes.add(this.options.wrap_before_assignment_operator ? rightSideIndex : operatorIndex); 785 this.wrapParentIndex = operatorIndex - 1; 786 this.wrapGroupEnd = this.tm.lastIndexIn(node.getRightHandSide(), -1); 787 handleWrap(this.options.alignment_for_assignment); 788 return true; 789 } 790 791 @Override visit(VariableDeclarationFragment node)792 public boolean visit(VariableDeclarationFragment node) { 793 if (node.getInitializer() == null) 794 return true; 795 int rightSideIndex = this.tm.firstIndexIn(node.getInitializer(), -1); 796 if (this.tm.get(rightSideIndex).getLineBreaksBefore() > 0) 797 return true; // must be an array initializer in new line because of brace_position_for_array_initializer 798 int equalIndex = this.tm.firstIndexBefore(node.getInitializer(), TokenNameEQUAL); 799 800 this.wrapIndexes.add(this.options.wrap_before_assignment_operator ? equalIndex : rightSideIndex); 801 this.secondaryWrapIndexes.add(this.options.wrap_before_assignment_operator ? rightSideIndex : equalIndex); 802 this.wrapParentIndex = equalIndex - 1; 803 this.wrapGroupEnd = this.tm.lastIndexIn(node.getInitializer(), -1); 804 handleWrap(this.options.alignment_for_assignment); 805 return true; 806 } 807 808 @Override visit(IfStatement node)809 public boolean visit(IfStatement node) { 810 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 811 int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); 812 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_if_while_statement); 813 814 Statement elseStatement = node.getElseStatement(); 815 boolean keepThenOnSameLine = this.options.keep_then_statement_on_same_line 816 || (this.options.keep_simple_if_on_one_line && elseStatement == null); 817 if (keepThenOnSameLine) 818 handleSimpleLoop(node.getThenStatement(), this.options.alignment_for_compact_if); 819 820 if (this.options.keep_else_statement_on_same_line && elseStatement != null) 821 handleSimpleLoop(elseStatement, this.options.alignment_for_compact_if); 822 return true; 823 } 824 825 @Override visit(ForStatement node)826 public boolean visit(ForStatement node) { 827 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 828 int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); 829 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_for_statement); 830 831 List<Expression> initializers = node.initializers(); 832 if (!initializers.isEmpty()) 833 this.wrapIndexes.add(this.tm.firstIndexIn(initializers.get(0), -1)); 834 if (node.getExpression() != null) 835 this.wrapIndexes.add(this.tm.firstIndexIn(node.getExpression(), -1)); 836 List<Expression> updaters = node.updaters(); 837 if (!updaters.isEmpty()) 838 this.wrapIndexes.add(this.tm.firstIndexIn(updaters.get(0), -1)); 839 if (!this.wrapIndexes.isEmpty()) { 840 this.wrapParentIndex = lParen; 841 this.wrapGroupEnd = rParen; 842 handleWrap(this.options.alignment_for_expressions_in_for_loop_header); 843 } 844 if (this.options.keep_simple_for_body_on_same_line) 845 handleSimpleLoop(node.getBody(), this.options.alignment_for_compact_loop); 846 return true; 847 } 848 849 @Override visit(EnhancedForStatement node)850 public boolean visit(EnhancedForStatement node) { 851 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 852 int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); 853 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_for_statement); 854 855 if (this.options.keep_simple_for_body_on_same_line) 856 handleSimpleLoop(node.getBody(), this.options.alignment_for_compact_loop); 857 return true; 858 } 859 860 @Override visit(WhileStatement node)861 public boolean visit(WhileStatement node) { 862 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 863 int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); 864 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_if_while_statement); 865 866 if (this.options.keep_simple_while_body_on_same_line) 867 handleSimpleLoop(node.getBody(), this.options.alignment_for_compact_loop); 868 return true; 869 } 870 handleSimpleLoop(Statement body, int wrappingOption)871 private void handleSimpleLoop(Statement body, int wrappingOption) { 872 if (!(body instanceof Block)) { 873 this.wrapIndexes.add(this.tm.firstIndexIn(body, -1)); 874 this.wrapParentIndex = this.tm.firstIndexBefore(body, TokenNameRPAREN); 875 this.wrapGroupEnd = this.tm.lastIndexIn(body, -1); 876 handleWrap(wrappingOption, body.getParent()); 877 878 body.accept(new ASTVisitor() { 879 @Override 880 public boolean visit(Block node) { 881 forceContinuousWrapping(node, WrapPreparator.this.tm.firstIndexIn(node, -1)); 882 return false; 883 } 884 }); 885 } 886 } 887 888 @Override endVisit(DoStatement node)889 public void endVisit(DoStatement node) { 890 if (this.options.keep_simple_do_while_body_on_same_line && !(node.getBody() instanceof Block)) { 891 int whileIndex = this.tm.firstIndexAfter(node.getBody(), TokenNamewhile); 892 this.wrapIndexes.add(whileIndex); 893 this.wrapParentIndex = this.tm.lastIndexIn(node.getBody(), -1); 894 this.wrapGroupEnd = this.tm.lastIndexIn(node, -1); 895 896 int alignment = this.options.alignment_for_compact_loop; 897 for (int i = this.tm.firstIndexIn(node, -1) + 1; i < whileIndex; i++) { 898 Token token = this.tm.get(i); 899 if (token.getLineBreaksBefore() > 0 || token.getLineBreaksAfter() > 0) 900 alignment |= Alignment.M_FORCE; 901 } 902 handleWrap(alignment, node); 903 } 904 } 905 906 @Override visit(TryStatement node)907 public boolean visit(TryStatement node) { 908 if (!node.resources().isEmpty()) { 909 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 910 int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); 911 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_try_clause); 912 } 913 prepareElementsList(node.resources(), TokenNameSEMICOLON, TokenNameLPAREN); 914 handleWrap(this.options.alignment_for_resources_in_try); 915 return true; 916 } 917 918 @Override visit(UnionType node)919 public boolean visit(UnionType node) { 920 List<Type> types = node.types(); 921 if (types.isEmpty()) 922 return true; 923 if (this.options.wrap_before_or_operator_multicatch) { 924 for (Type type : types) { 925 if (this.wrapIndexes.isEmpty()) { 926 this.wrapIndexes.add(this.tm.firstIndexIn(type, -1)); 927 } else { 928 this.wrapIndexes.add(this.tm.firstIndexBefore(type, TokenNameOR)); 929 this.secondaryWrapIndexes.add(this.tm.firstIndexIn(type, -1)); 930 } 931 } 932 this.wrapParentIndex = this.tm.firstIndexBefore(node, -1); 933 while (this.tm.get(this.wrapParentIndex).isComment()) 934 this.wrapParentIndex--; 935 this.wrapGroupEnd = this.tm.lastIndexIn(types.get(types.size() - 1), -1); 936 handleWrap(this.options.alignment_for_union_type_in_multicatch); 937 } else { 938 prepareElementsList(types, TokenNameOR, TokenNameLPAREN); 939 handleWrap(this.options.alignment_for_union_type_in_multicatch); 940 } 941 return true; 942 } 943 944 @Override visit(LambdaExpression node)945 public boolean visit(LambdaExpression node) { 946 int lParen = this.tm.firstIndexIn(node, -1); 947 if (this.tm.get(lParen).tokenType == TokenNameLPAREN) { 948 int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); 949 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_lambda_declaration); 950 } 951 if (node.getBody() instanceof Block) { 952 forceContinuousWrapping(node.getBody(), this.tm.firstIndexIn(node, -1)); 953 954 List<Statement> statements = ((Block) node.getBody()).statements(); 955 if (!statements.isEmpty()) { 956 int openBraceIndex = this.tm.firstIndexBefore(statements.get(0), TokenNameLBRACE); 957 int closeBraceIndex = this.tm.firstIndexAfter(statements.get(statements.size() - 1), TokenNameRBRACE); 958 boolean areKeptOnOneLine = statements.stream() 959 .allMatch(n -> this.tm.firstTokenIn(n, -1).getLineBreaksBefore() == 0); 960 if (areKeptOnOneLine) { 961 for (Statement statement : statements) 962 this.wrapIndexes.add(this.tm.firstIndexIn(statement, -1)); 963 this.wrapParentIndex = openBraceIndex; 964 this.wrapGroupEnd = closeBraceIndex; 965 handleWrap(Alignment.M_ONE_PER_LINE_SPLIT, node); 966 this.tm.get(closeBraceIndex).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, openBraceIndex, 967 closeBraceIndex, 0, this.currentDepth, 1, false, false)); 968 } 969 } 970 } 971 if (node.hasParentheses()) { 972 List<VariableDeclaration> parameters = node.parameters(); 973 // the legacy formatter didn't like wrapping lambda parameters, so neither do we 974 this.currentDepth++; 975 handleArguments(parameters, this.options.alignment_for_parameters_in_method_declaration); 976 this.currentDepth--; 977 } 978 return true; 979 } 980 981 @Override visit(FieldDeclaration node)982 public boolean visit(FieldDeclaration node) { 983 handleVariableDeclarations(node.fragments()); 984 return true; 985 } 986 987 @Override visit(VariableDeclarationStatement node)988 public boolean visit(VariableDeclarationStatement node) { 989 handleVariableDeclarations(node.fragments()); 990 return true; 991 } 992 993 @Override visit(ParameterizedType node)994 public boolean visit(ParameterizedType node) { 995 prepareElementsList(node.typeArguments(), TokenNameCOMMA, TokenNameLESS); 996 handleWrap(this.options.alignment_for_parameterized_type_references); 997 return true; 998 } 999 1000 @Override visit(TypeMethodReference node)1001 public boolean visit(TypeMethodReference node) { 1002 handleTypeArguments(node.typeArguments()); 1003 return true; 1004 } 1005 1006 @Override visit(ExpressionMethodReference node)1007 public boolean visit(ExpressionMethodReference node) { 1008 handleTypeArguments(node.typeArguments()); 1009 return true; 1010 } 1011 1012 @Override visit(SuperMethodReference node)1013 public boolean visit(SuperMethodReference node) { 1014 handleTypeArguments(node.typeArguments()); 1015 return true; 1016 } 1017 1018 @Override visit(CreationReference node)1019 public boolean visit(CreationReference node) { 1020 handleTypeArguments(node.typeArguments()); 1021 return true; 1022 } 1023 handleTypeArguments(List<Type> typeArguments)1024 private void handleTypeArguments(List<Type> typeArguments) { 1025 if (typeArguments.isEmpty()) 1026 return; 1027 prepareElementsList(typeArguments, TokenNameCOMMA, TokenNameLESS); 1028 handleWrap(this.options.alignment_for_type_arguments); 1029 } 1030 1031 @Override visit(ExportsDirective node)1032 public boolean visit(ExportsDirective node) { 1033 handleModuleStatement(node.modules(), TokenNameto); 1034 return true; 1035 } 1036 1037 @Override visit(OpensDirective node)1038 public boolean visit(OpensDirective node) { 1039 handleModuleStatement(node.modules(), TokenNameto); 1040 return true; 1041 } 1042 1043 @Override visit(ProvidesDirective node)1044 public boolean visit(ProvidesDirective node) { 1045 handleModuleStatement(node.implementations(), TokenNamewith); 1046 return true; 1047 } 1048 handleModuleStatement(List<Name> names, int joiningTokenType)1049 private void handleModuleStatement(List<Name> names, int joiningTokenType) { 1050 if (names.isEmpty()) 1051 return; 1052 int joiningTokenIndex = this.tm.firstIndexBefore(names.get(0), joiningTokenType); 1053 this.wrapParentIndex = this.tm.firstIndexBefore(names.get(0), TokenNameIdentifier); 1054 this.wrapIndexes.add(joiningTokenIndex); 1055 prepareElementsList(names, TokenNameCOMMA, -1); 1056 handleWrap(this.options.alignment_for_module_statements, PREFERRED); 1057 } 1058 1059 @Override visit(CatchClause node)1060 public boolean visit(CatchClause node) { 1061 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 1062 int rParen = this.tm.firstIndexBefore(node.getBody(), TokenNameRPAREN); 1063 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_catch_clause); 1064 return true; 1065 } 1066 1067 @Override visit(SwitchStatement node)1068 public boolean visit(SwitchStatement node) { 1069 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 1070 int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); 1071 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_switch_statement); 1072 return true; 1073 } 1074 1075 @Override visit(SwitchExpression node)1076 public boolean visit(SwitchExpression node) { 1077 int lParen = this.tm.firstIndexIn(node, TokenNameLPAREN); 1078 int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); 1079 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_switch_statement); 1080 return true; 1081 } 1082 1083 @Override visit(DoStatement node)1084 public boolean visit(DoStatement node) { 1085 int lParen = this.tm.firstIndexBefore(node.getExpression(), TokenNameLPAREN); 1086 int rParen = this.tm.firstIndexAfter(node.getExpression(), TokenNameRPAREN); 1087 handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_if_while_statement); 1088 return true; 1089 } 1090 1091 /** 1092 * Makes sure all new lines within given node will have wrap policy so that 1093 * wrap executor will fix their indentation if necessary. 1094 */ forceContinuousWrapping(ASTNode node, int parentIndex)1095 void forceContinuousWrapping(ASTNode node, int parentIndex) { 1096 int parentIndent = this.tm.get(parentIndex).getIndent(); 1097 int indentChange = -parentIndent; 1098 int lineStart = this.tm.findFirstTokenInLine(parentIndex); 1099 for (int i = parentIndex; i >= lineStart; i--) { 1100 int align = this.tm.get(i).getAlign(); 1101 if (align > 0) { 1102 indentChange = -2 * parentIndent + align; 1103 break; 1104 } 1105 } 1106 1107 Token previous = null; 1108 int from = this.tm.firstIndexIn(node, -1); 1109 int to = this.tm.lastIndexIn(node, -1); 1110 for (int i = from; i <= to; i++) { 1111 Token token = this.tm.get(i); 1112 if ((token.getLineBreaksBefore() > 0 || (previous != null && previous.getLineBreaksAfter() > 0)) 1113 && (token.getWrapPolicy() == null || token.getWrapPolicy().wrapMode == WrapMode.BLOCK_INDENT)) { 1114 int extraIndent = token.getIndent() + indentChange; 1115 token.setWrapPolicy(new WrapPolicy(WrapMode.BLOCK_INDENT, parentIndex, extraIndent)); 1116 token.setIndent(parentIndent + extraIndent); 1117 } 1118 previous = token; 1119 } 1120 } 1121 handleVariableDeclarations(List<VariableDeclarationFragment> fragments)1122 private void handleVariableDeclarations(List<VariableDeclarationFragment> fragments) { 1123 if (fragments.size() > 1) { 1124 this.wrapParentIndex = this.tm.firstIndexIn(fragments.get(0), -1); 1125 prepareElementsList(fragments, TokenNameCOMMA, -1); 1126 this.wrapIndexes.remove(0); 1127 handleWrap(this.options.alignment_for_multiple_fields); 1128 } 1129 } 1130 handleArguments(List<? extends ASTNode> arguments, int wrappingOption)1131 private void handleArguments(List<? extends ASTNode> arguments, int wrappingOption) { 1132 this.wrapPenalties.add(1 / PREFERRED); 1133 prepareElementsList(arguments, TokenNameCOMMA, TokenNameLPAREN); 1134 handleWrap(wrappingOption); 1135 } 1136 prepareElementsList(List<? extends ASTNode> elements, int separatorType, int wrapParentType)1137 private void prepareElementsList(List<? extends ASTNode> elements, int separatorType, int wrapParentType) { 1138 for (int i = 0; i < elements.size(); i++) { 1139 ASTNode element = elements.get(i); 1140 this.wrapIndexes.add(this.tm.firstIndexIn(element, -1)); 1141 if (i > 0) 1142 this.secondaryWrapIndexes.add(this.tm.firstIndexBefore(element, separatorType)); 1143 } 1144 // wrapIndexes may have been filled with additional values even if arguments is empty 1145 if (!this.wrapIndexes.isEmpty()) { 1146 Token firstToken = this.tm.get(this.wrapIndexes.get(0)); 1147 if (this.wrapParentIndex < 0) 1148 this.wrapParentIndex = this.tm.findIndex(firstToken.originalStart - 1, wrapParentType, false); 1149 if (!elements.isEmpty() && this.wrapGroupEnd < 0) 1150 this.wrapGroupEnd = this.tm.lastIndexIn(elements.get(elements.size() - 1), -1); 1151 } 1152 } 1153 handleWrap(int wrappingOption)1154 private void handleWrap(int wrappingOption) { 1155 handleWrap(wrappingOption, null); 1156 } 1157 handleWrap(int wrappingOption, float firstPenaltyMultiplier)1158 private void handleWrap(int wrappingOption, float firstPenaltyMultiplier) { 1159 this.wrapPenalties.add(firstPenaltyMultiplier); 1160 handleWrap(wrappingOption, null); 1161 } 1162 handleWrap(int wrappingOption, ASTNode parentNode)1163 private void handleWrap(int wrappingOption, ASTNode parentNode) { 1164 handleWrap(wrappingOption, true, parentNode); 1165 } 1166 handleWrap(int wrappingOption, boolean wrapPreceedingComments, ASTNode parentNode)1167 private void handleWrap(int wrappingOption, boolean wrapPreceedingComments, ASTNode parentNode) { 1168 doHandleWrap(wrappingOption, wrapPreceedingComments, parentNode); 1169 this.wrapIndexes.clear(); 1170 this.secondaryWrapIndexes.clear(); 1171 this.wrapPenalties.clear(); 1172 this.wrapParentIndex = this.wrapGroupEnd = -1; 1173 } 1174 doHandleWrap(int wrappingOption, boolean wrapPreceedingComments, ASTNode parentNode)1175 private void doHandleWrap(int wrappingOption, boolean wrapPreceedingComments, ASTNode parentNode) { 1176 if (this.wrapIndexes.isEmpty()) 1177 return; 1178 assert this.wrapParentIndex >= 0 && this.wrapParentIndex < this.wrapIndexes.get(0); 1179 assert this.wrapGroupEnd >= this.wrapIndexes.get(this.wrapIndexes.size() - 1); 1180 1181 while (this.tm.get(this.wrapParentIndex).isComment() && this.wrapParentIndex > 0) 1182 this.wrapParentIndex--; 1183 1184 float penalty = this.wrapPenalties.isEmpty() ? 1 : this.wrapPenalties.get(0); 1185 WrapPolicy policy = getWrapPolicy(wrappingOption, penalty, true, parentNode); 1186 1187 WrapPolicy existing = this.tm.get(this.wrapIndexes.get(0)).getWrapPolicy(); 1188 if (existing != null && existing.wrapMode == WrapMode.TOP_PRIORITY) { 1189 // SEPARATE_LINES_IF_WRAPPED 1190 assert existing.wrapParentIndex == this.wrapParentIndex; 1191 this.wrapGroupEnd = existing.groupEndIndex; 1192 policy = new WrapPolicy(WrapMode.TOP_PRIORITY, policy.wrapParentIndex, this.wrapGroupEnd, policy.extraIndent, 1193 policy.structureDepth, policy.penaltyMultiplier, true, policy.indentOnColumn); 1194 } 1195 1196 setTokenWrapPolicy(0, policy, true); 1197 1198 for (int i = 1; i < this.wrapIndexes.size(); i++) { 1199 penalty = this.wrapPenalties.size() > i ? this.wrapPenalties.get(i) : 1; 1200 if (penalty != policy.penaltyMultiplier || i == 1) 1201 policy = getWrapPolicy(wrappingOption, penalty, false, parentNode); 1202 setTokenWrapPolicy(i, policy, wrapPreceedingComments); 1203 } 1204 1205 if (!this.secondaryWrapIndexes.isEmpty()) { 1206 int optionNoAlignment = (wrappingOption & ~Alignment.SPLIT_MASK) | Alignment.M_NO_ALIGNMENT; 1207 policy = getWrapPolicy(optionNoAlignment, 1, false, parentNode); 1208 for (int index : this.secondaryWrapIndexes) { 1209 Token token = this.tm.get(index); 1210 if (token.getWrapPolicy() == null) 1211 token.setWrapPolicy(policy); 1212 } 1213 } 1214 } 1215 setTokenWrapPolicy(int wrapIndexesIndex, WrapPolicy policy, boolean wrapPreceedingComments)1216 private void setTokenWrapPolicy(int wrapIndexesIndex, WrapPolicy policy, boolean wrapPreceedingComments) { 1217 int index = this.wrapIndexes.get(wrapIndexesIndex); 1218 if (wrapPreceedingComments) { 1219 for (int i = index - 1; i >= 0; i--) { 1220 Token previous = this.tm.get(i); 1221 if (!previous.isComment()) 1222 break; 1223 if (previous.getWrapPolicy() == WrapPolicy.FORCE_FIRST_COLUMN) 1224 break; 1225 if (previous.getLineBreaksAfter() == 0 && i == index - 1) 1226 index = i; 1227 if (previous.getLineBreaksBefore() > 0) 1228 previous.setWrapPolicy(policy); 1229 } 1230 this.wrapIndexes.set(wrapIndexesIndex, index); 1231 } 1232 1233 Token token = this.tm.get(index); 1234 if (token.getWrapPolicy() == WrapPolicy.DISABLE_WRAP) 1235 return; 1236 1237 token.setWrapPolicy(policy); 1238 if (policy.wrapMode == WrapMode.FORCE) { 1239 token.breakBefore(); 1240 } else if (this.options.join_wrapped_lines && token.tokenType == TokenNameCOMMENT_BLOCK) { 1241 // allow wrap preparator to decide if this comment should be wrapped 1242 token.clearLineBreaksBefore(); 1243 } 1244 } 1245 getWrapPolicy(int wrappingOption, float penaltyMultiplier, boolean isFirst, ASTNode parentNode)1246 private WrapPolicy getWrapPolicy(int wrappingOption, float penaltyMultiplier, boolean isFirst, ASTNode parentNode) { 1247 assert this.wrapParentIndex >= 0 && this.wrapGroupEnd >= 0; 1248 int extraIndent = this.options.continuation_indentation; 1249 boolean indentOnColumn = (wrappingOption & Alignment.M_INDENT_ON_COLUMN) != 0; 1250 boolean isForceWrap = (wrappingOption & Alignment.M_FORCE) != 0; 1251 boolean isAlreadyWrapped = false; 1252 if (indentOnColumn) { 1253 extraIndent = 0; 1254 } else if (parentNode instanceof EnumDeclaration) { 1255 // special behavior for compatibility with legacy formatter 1256 extraIndent = ((wrappingOption & Alignment.M_INDENT_BY_ONE) != 0) ? 2 : 1; 1257 if (!this.options.indent_body_declarations_compare_to_enum_declaration_header) 1258 extraIndent--; 1259 isAlreadyWrapped = isFirst; 1260 } else if (parentNode instanceof IfStatement || parentNode instanceof ForStatement 1261 || parentNode instanceof EnhancedForStatement || parentNode instanceof WhileStatement) { 1262 extraIndent = 1; 1263 this.wrapParentIndex = this.tm.firstIndexIn(parentNode, -1); // only if !indoentOnColumn 1264 } else if (parentNode instanceof DoStatement) { 1265 extraIndent = 0; 1266 this.wrapParentIndex = this.tm.firstIndexIn(parentNode, -1); // only if !indoentOnColumn 1267 } else if (parentNode instanceof LambdaExpression) { 1268 extraIndent = 1; 1269 } else if ((wrappingOption & Alignment.M_INDENT_BY_ONE) != 0) { 1270 extraIndent = 1; 1271 } else if (parentNode instanceof ArrayInitializer) { 1272 extraIndent = this.options.continuation_indentation_for_array_initializer; 1273 isAlreadyWrapped = isFirst && this.options.insert_new_line_after_opening_brace_in_array_initializer; 1274 } 1275 1276 WrapMode wrapMode = WrapMode.WHERE_NECESSARY; 1277 boolean isTopPriority = false; 1278 switch (wrappingOption & Alignment.SPLIT_MASK) { 1279 case Alignment.M_NO_ALIGNMENT: 1280 wrapMode = WrapMode.DISABLED; 1281 isForceWrap = false; 1282 break; 1283 case Alignment.M_COMPACT_FIRST_BREAK_SPLIT: 1284 isTopPriority = isFirst; 1285 isForceWrap &= isFirst; 1286 break; 1287 case Alignment.M_ONE_PER_LINE_SPLIT: 1288 isTopPriority = true; 1289 break; 1290 case Alignment.M_NEXT_SHIFTED_SPLIT: 1291 isTopPriority = true; 1292 if (!isFirst) 1293 extraIndent++; 1294 break; 1295 case Alignment.M_NEXT_PER_LINE_SPLIT: 1296 isTopPriority = !isFirst; 1297 isForceWrap &= !isFirst; 1298 break; 1299 } 1300 1301 if (isForceWrap) { 1302 wrapMode = WrapMode.FORCE; 1303 } else if (isAlreadyWrapped) { 1304 wrapMode = WrapMode.DISABLED; // to avoid triggering top priority wrapping 1305 } else if (isTopPriority) { 1306 wrapMode = WrapMode.TOP_PRIORITY; 1307 } 1308 extraIndent *= this.options.indentation_size; 1309 return new WrapPolicy(wrapMode, this.wrapParentIndex, this.wrapGroupEnd, extraIndent, this.currentDepth, 1310 penaltyMultiplier, isFirst, indentOnColumn); 1311 } 1312 finishUp(ASTNode astRoot, List<IRegion> regions)1313 public void finishUp(ASTNode astRoot, List<IRegion> regions) { 1314 preserveExistingLineBreaks(); 1315 applyBreaksOutsideRegions(regions); 1316 new WrapExecutor(this.tm, this.options, regions).executeWraps(); 1317 this.aligner.alignComments(); 1318 wrapComments(); 1319 fixEnumConstantIndents(astRoot); 1320 } 1321 preserveExistingLineBreaks()1322 private void preserveExistingLineBreaks() { 1323 // normally n empty lines = n+1 line breaks, but not at the file start and end 1324 Token first = this.tm.get(0); 1325 int startingBreaks = first.getLineBreaksBefore(); 1326 first.clearLineBreaksBefore(); 1327 first.putLineBreaksBefore(startingBreaks - 1); 1328 1329 this.tm.traverse(0, new TokenTraverser() { 1330 boolean join_wrapped_lines = WrapPreparator.this.options.join_wrapped_lines; 1331 1332 @Override 1333 protected boolean token(Token token, int index) { 1334 int lineBreaks = getLineBreaksToPreserve(getPrevious(), token); 1335 if (lineBreaks > 1 || (!this.join_wrapped_lines && token.isWrappable()) || index == 0) 1336 token.putLineBreaksBefore(lineBreaks); 1337 return true; 1338 } 1339 1340 }); 1341 1342 Token last = this.tm.get(this.tm.size() - 1); 1343 last.clearLineBreaksAfter(); 1344 int endingBreaks = getLineBreaksToPreserve(last, null); 1345 if (endingBreaks > 0) { 1346 last.putLineBreaksAfter(endingBreaks); 1347 } else if ((this.kind & (CodeFormatter.K_COMPILATION_UNIT | CodeFormatter.K_MODULE_INFO)) != 0 1348 && this.options.insert_new_line_at_end_of_file_if_missing) { 1349 last.breakAfter(); 1350 } 1351 } 1352 getLineBreaksToPreserve(Token token1, Token token2)1353 int getLineBreaksToPreserve(Token token1, Token token2) { 1354 if ((token1 != null && !token1.isPreserveLineBreaksAfter()) 1355 || (token2 != null && !token2.isPreserveLineBreaksBefore())) { 1356 return 0; 1357 } 1358 if (token1 != null) { 1359 List<Token> structure = token1.getInternalStructure(); 1360 if (structure != null && !structure.isEmpty()) 1361 token1 = structure.get(structure.size() - 1); 1362 } 1363 if (token2 != null) { 1364 List<Token> structure = token2.getInternalStructure(); 1365 if (structure != null && !structure.isEmpty()) 1366 token2 = structure.get(0); 1367 } 1368 int lineBreaks = WrapPreparator.this.tm.countLineBreaksBetween(token1, token2); 1369 int toPreserve = this.options.number_of_empty_lines_to_preserve; 1370 if (token1 != null && token2 != null) 1371 toPreserve++; // n empty lines = n+1 line breaks, except for file start and end 1372 return Math.min(lineBreaks, toPreserve); 1373 } 1374 applyBreaksOutsideRegions(List<IRegion> regions)1375 private void applyBreaksOutsideRegions(List<IRegion> regions) { 1376 String source = this.tm.getSource(); 1377 int previousRegionEnd = 0; 1378 for (IRegion region : regions) { 1379 int index = this.tm.findIndex(previousRegionEnd, -1, true); 1380 Token token = this.tm.get(index); 1381 if (this.tm.countLineBreaksBetween(source, previousRegionEnd, 1382 Math.min(token.originalStart, region.getOffset())) > 0) 1383 token.breakBefore(); 1384 for (index++; index < this.tm.size(); index++) { 1385 Token next = this.tm.get(index); 1386 if (next.originalStart > region.getOffset()) { 1387 if (this.tm.countLineBreaksBetween(source, token.originalEnd, region.getOffset()) > 0) 1388 next.breakBefore(); 1389 break; 1390 } 1391 if (this.tm.countLineBreaksBetween(token, next) > 0) 1392 next.breakBefore(); 1393 token = next; 1394 } 1395 previousRegionEnd = region.getOffset() + region.getLength() - 1; 1396 } 1397 } 1398 wrapComments()1399 private void wrapComments() { 1400 CommentWrapExecutor commentWrapper = new CommentWrapExecutor(this.tm, this.options); 1401 boolean isNLSTagInLine = false; 1402 for (int i = 0; i < this.tm.size(); i++) { 1403 Token token = this.tm.get(i); 1404 if (token.getLineBreaksBefore() > 0 || token.getLineBreaksAfter() > 0) 1405 isNLSTagInLine = false; 1406 if (token.hasNLSTag()) { 1407 assert token.tokenType == TokenNameStringLiteral; 1408 isNLSTagInLine = true; 1409 } 1410 List<Token> structure = token.getInternalStructure(); 1411 if (token.isComment() && structure != null && !structure.isEmpty() && !isNLSTagInLine) { 1412 int startPosition = this.tm.getPositionInLine(i); 1413 if (token.tokenType == TokenNameCOMMENT_LINE) { 1414 commentWrapper.wrapLineComment(token, startPosition); 1415 } else { 1416 assert token.tokenType == TokenNameCOMMENT_BLOCK || token.tokenType == TokenNameCOMMENT_JAVADOC; 1417 commentWrapper.wrapMultiLineComment(token, startPosition, false, false); 1418 } 1419 } 1420 } 1421 } 1422 fixEnumConstantIndents(ASTNode astRoot)1423 private void fixEnumConstantIndents(ASTNode astRoot) { 1424 if (this.options.use_tabs_only_for_leading_indentations) { 1425 // enum constants should be indented like other declarations, not like wrapped elements 1426 astRoot.accept(new ASTVisitor() { 1427 1428 @Override 1429 public boolean visit(EnumConstantDeclaration node) { 1430 WrapPreparator.this.tm.firstTokenIn(node, -1).setWrapPolicy(null); 1431 return true; 1432 } 1433 }); 1434 } 1435 } 1436 handleParenthesesPositions(int openingParenIndex, int closingParenIndex, String positionsSetting)1437 private void handleParenthesesPositions(int openingParenIndex, int closingParenIndex, String positionsSetting) { 1438 boolean isEmpty = openingParenIndex + 1 == closingParenIndex; 1439 switch (positionsSetting) { 1440 case DefaultCodeFormatterConstants.COMMON_LINES: 1441 // nothing to do 1442 break; 1443 case DefaultCodeFormatterConstants.SEPARATE_LINES_IF_WRAPPED: 1444 if (isEmpty) 1445 break; 1446 this.tm.get(openingParenIndex + 1).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, 1447 openingParenIndex, closingParenIndex, this.options.indentation_size, 1, 1, true, false)); 1448 this.tm.get(closingParenIndex).setWrapPolicy(new WrapPolicy(WrapMode.TOP_PRIORITY, 1449 openingParenIndex, closingParenIndex, 0, 1, 1, false, false)); 1450 break; 1451 case DefaultCodeFormatterConstants.SEPARATE_LINES_IF_NOT_EMPTY: 1452 if (isEmpty) 1453 break; 1454 //$FALL-THROUGH$ 1455 case DefaultCodeFormatterConstants.SEPARATE_LINES: 1456 case DefaultCodeFormatterConstants.PRESERVE_POSITIONS: 1457 boolean always = !positionsSetting.equals(DefaultCodeFormatterConstants.PRESERVE_POSITIONS); 1458 Token afterOpening = this.tm.get(openingParenIndex + 1); 1459 if (always || this.tm.countLineBreaksBetween(this.tm.get(openingParenIndex), afterOpening) > 0) { 1460 afterOpening.setWrapPolicy( 1461 new WrapPolicy(WrapMode.WHERE_NECESSARY, openingParenIndex, this.options.indentation_size)); 1462 afterOpening.breakBefore(); 1463 } 1464 Token closingParen = this.tm.get(closingParenIndex); 1465 if (always || this.tm.countLineBreaksBetween(this.tm.get(closingParenIndex - 1), closingParen) > 0) { 1466 closingParen.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, openingParenIndex, 0)); 1467 closingParen.breakBefore(); 1468 } 1469 break; 1470 default: 1471 throw new IllegalArgumentException("Unrecognized parentheses positions setting: " + positionsSetting); //$NON-NLS-1$ 1472 } 1473 } 1474 } 1475