001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.whitespace; 021 022import java.util.ArrayList; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Optional; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 034import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 035 036/** 037 * <div> 038 * Checks for empty line separators before package, all import declarations, 039 * fields, constructors, methods, nested classes, 040 * static initializers and instance initializers. 041 * </div> 042 * 043 * <p> 044 * Checks for empty line separators before not only statements but 045 * implementation and documentation comments and blocks as well. 046 * </p> 047 * 048 * <p> 049 * ATTENTION: empty line separator is required between token siblings, 050 * not after line where token is found. 051 * If token does not have a sibling of the same type, then empty line 052 * is required at its end (for example for CLASS_DEF it is after '}'). 053 * Also, trailing comments are skipped. 054 * </p> 055 * <ul> 056 * <li> 057 * Property {@code allowMultipleEmptyLines} - Allow multiple empty lines between class members. 058 * Type is {@code boolean}. 059 * Default value is {@code true}. 060 * </li> 061 * <li> 062 * Property {@code allowMultipleEmptyLinesInsideClassMembers} - Allow multiple 063 * empty lines inside class members. 064 * Type is {@code boolean}. 065 * Default value is {@code true}. 066 * </li> 067 * <li> 068 * Property {@code allowNoEmptyLineBetweenFields} - Allow no empty line between fields. 069 * Type is {@code boolean}. 070 * Default value is {@code false}. 071 * </li> 072 * <li> 073 * Property {@code tokens} - tokens to check 074 * Type is {@code java.lang.String[]}. 075 * Validation type is {@code tokenSet}. 076 * Default value is: 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 078 * PACKAGE_DEF</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IMPORT"> 080 * IMPORT</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_IMPORT"> 082 * STATIC_IMPORT</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 084 * CLASS_DEF</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 086 * INTERFACE_DEF</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 088 * ENUM_DEF</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 090 * STATIC_INIT</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT"> 092 * INSTANCE_INIT</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 094 * METHOD_DEF</a>, 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 096 * CTOR_DEF</a>, 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 098 * VARIABLE_DEF</a>, 099 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 100 * RECORD_DEF</a>, 101 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 102 * COMPACT_CTOR_DEF</a>. 103 * </li> 104 * </ul> 105 * 106 * <p> 107 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 108 * </p> 109 * 110 * <p> 111 * Violation Message Keys: 112 * </p> 113 * <ul> 114 * <li> 115 * {@code empty.line.separator} 116 * </li> 117 * <li> 118 * {@code empty.line.separator.multiple.lines} 119 * </li> 120 * <li> 121 * {@code empty.line.separator.multiple.lines.after} 122 * </li> 123 * <li> 124 * {@code empty.line.separator.multiple.lines.inside} 125 * </li> 126 * </ul> 127 * 128 * @since 5.8 129 */ 130@StatelessCheck 131public class EmptyLineSeparatorCheck extends AbstractCheck { 132 133 /** 134 * A key is pointing to the warning message empty.line.separator in "messages.properties" 135 * file. 136 */ 137 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator"; 138 139 /** 140 * A key is pointing to the warning message empty.line.separator.multiple.lines 141 * in "messages.properties" 142 * file. 143 */ 144 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines"; 145 146 /** 147 * A key is pointing to the warning message empty.line.separator.lines.after 148 * in "messages.properties" file. 149 */ 150 public static final String MSG_MULTIPLE_LINES_AFTER = 151 "empty.line.separator.multiple.lines.after"; 152 153 /** 154 * A key is pointing to the warning message empty.line.separator.multiple.lines.inside 155 * in "messages.properties" file. 156 */ 157 public static final String MSG_MULTIPLE_LINES_INSIDE = 158 "empty.line.separator.multiple.lines.inside"; 159 160 /** Allow no empty line between fields. */ 161 private boolean allowNoEmptyLineBetweenFields; 162 163 /** Allow multiple empty lines between class members. */ 164 private boolean allowMultipleEmptyLines = true; 165 166 /** Allow multiple empty lines inside class members. */ 167 private boolean allowMultipleEmptyLinesInsideClassMembers = true; 168 169 /** 170 * Setter to allow no empty line between fields. 171 * 172 * @param allow 173 * User's value. 174 * @since 5.8 175 */ 176 public final void setAllowNoEmptyLineBetweenFields(boolean allow) { 177 allowNoEmptyLineBetweenFields = allow; 178 } 179 180 /** 181 * Setter to allow multiple empty lines between class members. 182 * 183 * @param allow User's value. 184 * @since 6.3 185 */ 186 public void setAllowMultipleEmptyLines(boolean allow) { 187 allowMultipleEmptyLines = allow; 188 } 189 190 /** 191 * Setter to allow multiple empty lines inside class members. 192 * 193 * @param allow User's value. 194 * @since 6.18 195 */ 196 public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) { 197 allowMultipleEmptyLinesInsideClassMembers = allow; 198 } 199 200 @Override 201 public boolean isCommentNodesRequired() { 202 return true; 203 } 204 205 @Override 206 public int[] getDefaultTokens() { 207 return getAcceptableTokens(); 208 } 209 210 @Override 211 public int[] getAcceptableTokens() { 212 return new int[] { 213 TokenTypes.PACKAGE_DEF, 214 TokenTypes.IMPORT, 215 TokenTypes.STATIC_IMPORT, 216 TokenTypes.CLASS_DEF, 217 TokenTypes.INTERFACE_DEF, 218 TokenTypes.ENUM_DEF, 219 TokenTypes.STATIC_INIT, 220 TokenTypes.INSTANCE_INIT, 221 TokenTypes.METHOD_DEF, 222 TokenTypes.CTOR_DEF, 223 TokenTypes.VARIABLE_DEF, 224 TokenTypes.RECORD_DEF, 225 TokenTypes.COMPACT_CTOR_DEF, 226 }; 227 } 228 229 @Override 230 public int[] getRequiredTokens() { 231 return CommonUtil.EMPTY_INT_ARRAY; 232 } 233 234 @Override 235 public void visitToken(DetailAST ast) { 236 checkComments(ast); 237 if (hasMultipleLinesBefore(ast)) { 238 log(ast, MSG_MULTIPLE_LINES, ast.getText()); 239 } 240 if (!allowMultipleEmptyLinesInsideClassMembers) { 241 processMultipleLinesInside(ast); 242 } 243 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 244 checkCommentInModifiers(ast); 245 } 246 DetailAST nextToken = ast.getNextSibling(); 247 while (nextToken != null && TokenUtil.isCommentType(nextToken.getType())) { 248 nextToken = nextToken.getNextSibling(); 249 } 250 if (nextToken != null) { 251 checkToken(ast, nextToken); 252 } 253 } 254 255 /** 256 * Checks that token and next token are separated. 257 * 258 * @param ast token to validate 259 * @param nextToken next sibling of the token 260 */ 261 private void checkToken(DetailAST ast, DetailAST nextToken) { 262 final int astType = ast.getType(); 263 264 switch (astType) { 265 case TokenTypes.VARIABLE_DEF -> processVariableDef(ast, nextToken); 266 267 case TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT -> processImport(ast, nextToken); 268 269 case TokenTypes.PACKAGE_DEF -> processPackage(ast, nextToken); 270 271 default -> { 272 if (nextToken.getType() == TokenTypes.RCURLY) { 273 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) { 274 final DetailAST result = getLastElementBeforeEmptyLines( 275 ast, nextToken.getLineNo() 276 ); 277 log(result, MSG_MULTIPLE_LINES_AFTER, result.getText()); 278 } 279 } 280 else if (!hasEmptyLineAfter(ast)) { 281 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 282 } 283 } 284 } 285 } 286 287 /** 288 * Checks that packageDef token is separated from comment in modifiers. 289 * 290 * @param packageDef package def token 291 */ 292 private void checkCommentInModifiers(DetailAST packageDef) { 293 final Optional<DetailAST> comment = findCommentUnder(packageDef); 294 comment.ifPresent(commentValue -> { 295 log(commentValue, MSG_SHOULD_BE_SEPARATED, commentValue.getText()); 296 }); 297 } 298 299 /** 300 * Log violation in case there are multiple empty lines inside constructor, 301 * initialization block or method. 302 * 303 * @param ast the ast to check. 304 */ 305 private void processMultipleLinesInside(DetailAST ast) { 306 final int astType = ast.getType(); 307 if (isClassMemberBlock(astType)) { 308 final List<Integer> emptyLines = getEmptyLines(ast); 309 final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines); 310 for (Integer lineNo : emptyLinesToLog) { 311 log(getLastElementBeforeEmptyLines(ast, lineNo), MSG_MULTIPLE_LINES_INSIDE); 312 } 313 } 314 } 315 316 /** 317 * Returns the element after which empty lines exist. 318 * 319 * @param ast the ast to check. 320 * @param line the empty line which gives violation. 321 * @return The DetailAST after which empty lines are present. 322 */ 323 private static DetailAST getLastElementBeforeEmptyLines(DetailAST ast, int line) { 324 DetailAST result = ast; 325 if (ast.getFirstChild().getLineNo() <= line) { 326 result = ast.getFirstChild(); 327 while (result.getNextSibling() != null 328 && result.getNextSibling().getLineNo() <= line) { 329 result = result.getNextSibling(); 330 } 331 if (result.hasChildren()) { 332 result = getLastElementBeforeEmptyLines(result, line); 333 } 334 } 335 336 if (result.getNextSibling() != null) { 337 final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling()); 338 if (postFixNode.isPresent()) { 339 // A post fix AST will always have a sibling METHOD CALL 340 // METHOD CALL will at least have two children 341 // The first child is DOT in case of POSTFIX which have at least 2 children 342 // First child of DOT again puts us back to normal AST tree which will 343 // recurse down below from here 344 final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow(); 345 result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line); 346 } 347 } 348 return result; 349 } 350 351 /** 352 * Gets postfix Node from AST if present. 353 * 354 * @param ast the AST used to get postfix Node. 355 * @return Optional postfix node. 356 */ 357 private static Optional<DetailAST> getPostFixNode(DetailAST ast) { 358 Optional<DetailAST> result = Optional.empty(); 359 if (ast.getType() == TokenTypes.EXPR 360 // EXPR always has at least one child 361 && ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) { 362 // METHOD CALL always has at two least child 363 final DetailAST node = ast.getFirstChild().getFirstChild(); 364 if (node.getType() == TokenTypes.DOT) { 365 result = Optional.of(node); 366 } 367 } 368 return result; 369 } 370 371 /** 372 * Whether the AST is a class member block. 373 * 374 * @param astType the AST to check. 375 * @return true if the AST is a class member block. 376 */ 377 private static boolean isClassMemberBlock(int astType) { 378 return TokenUtil.isOfType(astType, 379 TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.METHOD_DEF, 380 TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF); 381 } 382 383 /** 384 * Get list of empty lines. 385 * 386 * @param ast the ast to check. 387 * @return list of line numbers for empty lines. 388 */ 389 private List<Integer> getEmptyLines(DetailAST ast) { 390 final DetailAST lastToken = ast.getLastChild().getLastChild(); 391 int lastTokenLineNo = 0; 392 if (lastToken != null) { 393 // -1 as count starts from 0 394 // -2 as last token line cannot be empty, because it is a RCURLY 395 lastTokenLineNo = lastToken.getLineNo() - 2; 396 } 397 final List<Integer> emptyLines = new ArrayList<>(); 398 399 for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) { 400 if (CommonUtil.isBlank(getLine(lineNo))) { 401 emptyLines.add(lineNo); 402 } 403 } 404 return emptyLines; 405 } 406 407 /** 408 * Get list of empty lines to log. 409 * 410 * @param emptyLines list of empty lines. 411 * @return list of empty lines to log. 412 */ 413 private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) { 414 final List<Integer> emptyLinesToLog = new ArrayList<>(); 415 int previousEmptyLineNo = -1; 416 for (int emptyLineNo : emptyLines) { 417 if (previousEmptyLineNo + 1 == emptyLineNo) { 418 emptyLinesToLog.add(previousEmptyLineNo); 419 } 420 previousEmptyLineNo = emptyLineNo; 421 } 422 return emptyLinesToLog; 423 } 424 425 /** 426 * Whether the token has not allowed multiple empty lines before. 427 * 428 * @param ast the ast to check. 429 * @return true if the token has not allowed multiple empty lines before. 430 */ 431 private boolean hasMultipleLinesBefore(DetailAST ast) { 432 return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast)) 433 && hasNotAllowedTwoEmptyLinesBefore(ast); 434 } 435 436 /** 437 * Process Package. 438 * 439 * @param ast token 440 * @param nextToken next token 441 */ 442 private void processPackage(DetailAST ast, DetailAST nextToken) { 443 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 444 if (CheckUtil.isPackageInfo(getFilePath())) { 445 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) { 446 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText()); 447 } 448 } 449 else { 450 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText()); 451 } 452 } 453 if (isLineEmptyAfterPackage(ast)) { 454 final DetailAST elementAst = getViolationAstForPackage(ast); 455 log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText()); 456 } 457 else if (!hasEmptyLineAfter(ast)) { 458 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 459 } 460 } 461 462 /** 463 * Checks if there is another element at next line of package declaration. 464 * 465 * @param ast Package ast. 466 * @return true, if there is an element. 467 */ 468 private static boolean isLineEmptyAfterPackage(DetailAST ast) { 469 DetailAST nextElement = ast; 470 final int lastChildLineNo = ast.getLastChild().getLineNo(); 471 while (nextElement.getLineNo() < lastChildLineNo + 1 472 && nextElement.getNextSibling() != null) { 473 nextElement = nextElement.getNextSibling(); 474 } 475 return nextElement.getLineNo() == lastChildLineNo + 1; 476 } 477 478 /** 479 * Gets the Ast on which violation is to be given for package declaration. 480 * 481 * @param ast Package ast. 482 * @return Violation ast. 483 */ 484 private static DetailAST getViolationAstForPackage(DetailAST ast) { 485 DetailAST nextElement = ast; 486 final int lastChildLineNo = ast.getLastChild().getLineNo(); 487 while (nextElement.getLineNo() < lastChildLineNo + 1) { 488 nextElement = nextElement.getNextSibling(); 489 } 490 return nextElement; 491 } 492 493 /** 494 * Process Import. 495 * 496 * @param ast token 497 * @param nextToken next token 498 */ 499 private void processImport(DetailAST ast, DetailAST nextToken) { 500 if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT) 501 && !hasEmptyLineAfter(ast)) { 502 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 503 } 504 } 505 506 /** 507 * Process Variable. 508 * 509 * @param ast token 510 * @param nextToken next Token 511 */ 512 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 513 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 514 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 515 log(nextToken, MSG_SHOULD_BE_SEPARATED, 516 nextToken.getText()); 517 } 518 } 519 520 /** 521 * Checks whether token placement violates policy of empty line between fields. 522 * 523 * @param detailAST token to be analyzed 524 * @return true if policy is violated and warning should be raised; false otherwise 525 */ 526 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 527 return detailAST.getType() != TokenTypes.RCURLY 528 && (!allowNoEmptyLineBetweenFields 529 || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF)); 530 } 531 532 /** 533 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 534 * 535 * @param token DetailAST token 536 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 537 */ 538 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 539 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 540 && isPrePreviousLineEmpty(token); 541 } 542 543 /** 544 * Check if group of comments located right before token has more than one previous empty line. 545 * 546 * @param token DetailAST token 547 */ 548 private void checkComments(DetailAST token) { 549 if (!allowMultipleEmptyLines) { 550 if (TokenUtil.isOfType(token, 551 TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, 552 TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) { 553 DetailAST previousNode = token.getPreviousSibling(); 554 while (isCommentInBeginningOfLine(previousNode)) { 555 if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) { 556 log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText()); 557 } 558 previousNode = previousNode.getPreviousSibling(); 559 } 560 } 561 else { 562 checkCommentsInsideToken(token); 563 } 564 } 565 } 566 567 /** 568 * Check if group of comments located at the start of token has more than one previous empty 569 * line. 570 * 571 * @param token DetailAST token 572 */ 573 private void checkCommentsInsideToken(DetailAST token) { 574 final List<DetailAST> childNodes = new LinkedList<>(); 575 DetailAST childNode = token.getLastChild(); 576 while (childNode != null) { 577 if (childNode.getType() == TokenTypes.MODIFIERS) { 578 for (DetailAST node = token.getFirstChild().getLastChild(); 579 node != null; 580 node = node.getPreviousSibling()) { 581 if (isCommentInBeginningOfLine(node)) { 582 childNodes.add(node); 583 } 584 } 585 } 586 else if (isCommentInBeginningOfLine(childNode)) { 587 childNodes.add(childNode); 588 } 589 childNode = childNode.getPreviousSibling(); 590 } 591 for (DetailAST node : childNodes) { 592 if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) { 593 log(node, MSG_MULTIPLE_LINES, node.getText()); 594 } 595 } 596 } 597 598 /** 599 * Checks if a token has empty pre-previous line. 600 * 601 * @param token DetailAST token. 602 * @return true, if token has empty lines before. 603 */ 604 private boolean isPrePreviousLineEmpty(DetailAST token) { 605 boolean result = false; 606 final int lineNo = token.getLineNo(); 607 // 3 is the number of the pre-previous line because the numbering starts from zero. 608 final int number = 3; 609 if (lineNo >= number) { 610 final String prePreviousLine = getLine(lineNo - number); 611 612 result = CommonUtil.isBlank(prePreviousLine); 613 final boolean previousLineIsEmpty = CommonUtil.isBlank(getLine(lineNo - 2)); 614 615 if (previousLineIsEmpty && result) { 616 result = true; 617 } 618 else if (token.findFirstToken(TokenTypes.TYPE) != null) { 619 result = isTwoPrecedingPreviousLinesFromCommentEmpty(token); 620 } 621 } 622 return result; 623 624 } 625 626 /** 627 * Checks if token has two preceding lines empty, starting from its describing comment. 628 * 629 * @param token token checked. 630 * @return true, if both previous and pre-previous lines from dependent comment are empty 631 */ 632 private boolean isTwoPrecedingPreviousLinesFromCommentEmpty(DetailAST token) { 633 boolean upToPrePreviousLinesEmpty = false; 634 635 for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild(); 636 typeChild != null; typeChild = typeChild.getPreviousSibling()) { 637 638 if (isTokenNotOnPreviousSiblingLines(typeChild, token)) { 639 640 final String commentBeginningPreviousLine = 641 getLine(typeChild.getLineNo() - 2); 642 final String commentBeginningPrePreviousLine = 643 getLine(typeChild.getLineNo() - 3); 644 645 if (CommonUtil.isBlank(commentBeginningPreviousLine) 646 && CommonUtil.isBlank(commentBeginningPrePreviousLine)) { 647 upToPrePreviousLinesEmpty = true; 648 break; 649 } 650 651 } 652 653 } 654 655 return upToPrePreviousLinesEmpty; 656 } 657 658 /** 659 * Checks if token is not placed on the realm of previous sibling of token's parent. 660 * 661 * @param token token checked. 662 * @param parentToken parent token. 663 * @return true, if child token doesn't occupy parent token's previous sibling's realm. 664 */ 665 private static boolean isTokenNotOnPreviousSiblingLines(DetailAST token, 666 DetailAST parentToken) { 667 DetailAST previousSibling = parentToken.getPreviousSibling(); 668 for (DetailAST astNode = previousSibling; astNode != null; 669 astNode = astNode.getLastChild()) { 670 previousSibling = astNode; 671 } 672 673 return token.getLineNo() != previousSibling.getLineNo(); 674 } 675 676 /** 677 * Checks if token have empty line after. 678 * 679 * @param token token. 680 * @return true if token have empty line after. 681 */ 682 private boolean hasEmptyLineAfter(DetailAST token) { 683 DetailAST lastToken = token.getLastChild().getLastChild(); 684 if (lastToken == null) { 685 lastToken = token.getLastChild(); 686 } 687 DetailAST nextToken = token.getNextSibling(); 688 if (TokenUtil.isCommentType(nextToken.getType())) { 689 nextToken = nextToken.getNextSibling(); 690 } 691 // Start of the next token 692 final int nextBegin = nextToken.getLineNo(); 693 // End of current token. 694 final int currentEnd = lastToken.getLineNo(); 695 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 696 } 697 698 /** 699 * Finds comment in next sibling of given packageDef. 700 * 701 * @param packageDef token to check 702 * @return comment under the token 703 */ 704 private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) { 705 return Optional.ofNullable(packageDef.getNextSibling()) 706 .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS)) 707 .map(DetailAST::getFirstChild) 708 .filter(token -> TokenUtil.isCommentType(token.getType())) 709 .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1); 710 } 711 712 /** 713 * Checks, whether there are empty lines within the specified line range. Line numbering is 714 * started from 1 for parameter values 715 * 716 * @param startLine number of the first line in the range 717 * @param endLine number of the second line in the range 718 * @return {@code true} if found any blank line within the range, {@code false} 719 * otherwise 720 */ 721 private boolean hasEmptyLine(int startLine, int endLine) { 722 // Initial value is false - blank line not found 723 boolean result = false; 724 for (int line = startLine; line <= endLine; line++) { 725 // Check, if the line is blank. Lines are numbered from 0, so subtract 1 726 if (CommonUtil.isBlank(getLine(line - 1))) { 727 result = true; 728 break; 729 } 730 } 731 return result; 732 } 733 734 /** 735 * Checks if a token has an empty line before. 736 * 737 * @param token token. 738 * @return true, if token have empty line before. 739 */ 740 private boolean hasEmptyLineBefore(DetailAST token) { 741 boolean result = false; 742 final int lineNo = token.getLineNo(); 743 if (lineNo != 1) { 744 // [lineNo - 2] is the number of the previous line as the numbering starts from zero. 745 final String lineBefore = getLine(lineNo - 2); 746 747 if (CommonUtil.isBlank(lineBefore)) { 748 result = true; 749 } 750 else if (token.findFirstToken(TokenTypes.TYPE) != null) { 751 for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild(); 752 typeChild != null && !result && typeChild.getLineNo() > 1; 753 typeChild = typeChild.getPreviousSibling()) { 754 755 final String commentBeginningPreviousLine = 756 getLine(typeChild.getLineNo() - 2); 757 result = CommonUtil.isBlank(commentBeginningPreviousLine); 758 759 } 760 } 761 } 762 return result; 763 } 764 765 /** 766 * Check if token is comment, which starting in beginning of line. 767 * 768 * @param comment comment token for check. 769 * @return true, if token is comment, which starting in beginning of line. 770 */ 771 private boolean isCommentInBeginningOfLine(DetailAST comment) { 772 // comment.getLineNo() - 1 is the number of the previous line as the numbering starts 773 // from zero. 774 boolean result = false; 775 if (comment != null) { 776 final String lineWithComment = getLine(comment.getLineNo() - 1).trim(); 777 result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*"); 778 } 779 return result; 780 } 781 782 /** 783 * Check if token is preceded by javadoc comment. 784 * 785 * @param token token for check. 786 * @return true, if token is preceded by javadoc comment. 787 */ 788 private static boolean isPrecededByJavadoc(DetailAST token) { 789 boolean result = false; 790 final DetailAST previous = token.getPreviousSibling(); 791 if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN 792 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) { 793 result = true; 794 } 795 return result; 796 } 797 798 /** 799 * If variable definition is a type field. 800 * 801 * @param variableDef variable definition. 802 * @return true variable definition is a type field. 803 */ 804 private static boolean isTypeField(DetailAST variableDef) { 805 return TokenUtil.isTypeDeclaration(variableDef.getParent().getParent().getType()); 806 } 807 808}