View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.Set;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
35  
36  /**
37   * <div>
38   * Checks for empty line separators after,
39   * fields, constructors, methods, nested classes,
40   * static initializers and instance initializers.
41   * </div>
42   *
43   * <p>
44   * For package declaration it checks for both before and after
45   * line separators and for import declarations it checks for
46   * empty line separator after last import declaration.
47   * </p>
48   *
49   * <p>
50   * Checks for empty line separators after not only statements but
51   * implementation and documentation comments and blocks as well.
52   * </p>
53   *
54   * <p>
55   * ATTENTION: empty line separator is required between token siblings,
56   * not after line where token is found.
57   * If token does not have a sibling of the same type, then empty line
58   * is required at its end (for example for CLASS_DEF it is after '}').
59   * Also, trailing comments are skipped.
60   * </p>
61   *
62   * @since 5.8
63   */
64  @StatelessCheck
65  public class EmptyLineSeparatorCheck extends AbstractCheck {
66  
67      /**
68       * A key is pointing to the warning message empty.line.separator in "messages.properties"
69       * file.
70       */
71      public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
72  
73      /**
74       * A key is pointing to the warning message empty.line.separator.multiple.lines
75       *  in "messages.properties"
76       * file.
77       */
78      public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
79  
80      /**
81       * A key is pointing to the warning message empty.line.separator.lines.after
82       * in "messages.properties" file.
83       */
84      public static final String MSG_MULTIPLE_LINES_AFTER =
85              "empty.line.separator.multiple.lines.after";
86  
87      /**
88       * A key is pointing to the warning message empty.line.separator.multiple.lines.inside
89       * in "messages.properties" file.
90       */
91      public static final String MSG_MULTIPLE_LINES_INSIDE =
92              "empty.line.separator.multiple.lines.inside";
93  
94      /** Tokens for which preceding comment lines (if any) are checked via previous siblings. */
95      private static final Set<Integer> TOKENS_TO_CHECK_FOR_PRECEDING_COMMENTS = Set.of(
96          TokenTypes.PACKAGE_DEF,
97          TokenTypes.IMPORT,
98          TokenTypes.STATIC_IMPORT,
99          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 }