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