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