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