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