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