001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
393    @SuppressWarnings("deprecation")
394    private List<Integer> getEmptyLines(DetailAST ast) {
395        final DetailAST lastToken = ast.getLastChild().getLastChild();
396        int lastTokenLineNo = 0;
397        if (lastToken != null) {
398            // -1 as count starts from 0
399            // -2 as last token line cannot be empty, because it is a RCURLY
400            lastTokenLineNo = lastToken.getLineNo() - 2;
401        }
402        final List<Integer> emptyLines = new ArrayList<>();
403        final FileContents fileContents = getFileContents();
404
405        for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
406            if (fileContents.lineIsBlank(lineNo)) {
407                emptyLines.add(lineNo);
408            }
409        }
410        return emptyLines;
411    }
412
413    /**
414     * Get list of empty lines to log.
415     *
416     * @param emptyLines list of empty lines.
417     * @return list of empty lines to log.
418     */
419    private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) {
420        final List<Integer> emptyLinesToLog = new ArrayList<>();
421        int previousEmptyLineNo = -1;
422        for (int emptyLineNo : emptyLines) {
423            if (previousEmptyLineNo + 1 == emptyLineNo) {
424                emptyLinesToLog.add(previousEmptyLineNo);
425            }
426            previousEmptyLineNo = emptyLineNo;
427        }
428        return emptyLinesToLog;
429    }
430
431    /**
432     * Whether the token has not allowed multiple empty lines before.
433     *
434     * @param ast the ast to check.
435     * @return true if the token has not allowed multiple empty lines before.
436     */
437    private boolean hasMultipleLinesBefore(DetailAST ast) {
438        return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast))
439                && hasNotAllowedTwoEmptyLinesBefore(ast);
440    }
441
442    /**
443     * Process Package.
444     *
445     * @param ast token
446     * @param nextToken next token
447     */
448    private void processPackage(DetailAST ast, DetailAST nextToken) {
449        if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
450            if (CheckUtil.isPackageInfo(getFilePath())) {
451                if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) {
452                    log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
453                }
454            }
455            else {
456                log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
457            }
458        }
459        if (isLineEmptyAfterPackage(ast)) {
460            final DetailAST elementAst = getViolationAstForPackage(ast);
461            log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText());
462        }
463        else if (!hasEmptyLineAfter(ast)) {
464            log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
465        }
466    }
467
468    /**
469     * Checks if there is another element at next line of package declaration.
470     *
471     * @param ast Package ast.
472     * @return true, if there is an element.
473     */
474    private static boolean isLineEmptyAfterPackage(DetailAST ast) {
475        DetailAST nextElement = ast;
476        final int lastChildLineNo = ast.getLastChild().getLineNo();
477        while (nextElement.getLineNo() < lastChildLineNo + 1
478                && nextElement.getNextSibling() != null) {
479            nextElement = nextElement.getNextSibling();
480        }
481        return nextElement.getLineNo() == lastChildLineNo + 1;
482    }
483
484    /**
485     * Gets the Ast on which violation is to be given for package declaration.
486     *
487     * @param ast Package ast.
488     * @return Violation ast.
489     */
490    private static DetailAST getViolationAstForPackage(DetailAST ast) {
491        DetailAST nextElement = ast;
492        final int lastChildLineNo = ast.getLastChild().getLineNo();
493        while (nextElement.getLineNo() < lastChildLineNo + 1) {
494            nextElement = nextElement.getNextSibling();
495        }
496        return nextElement;
497    }
498
499    /**
500     * Process Import.
501     *
502     * @param ast token
503     * @param nextToken next token
504     */
505    private void processImport(DetailAST ast, DetailAST nextToken) {
506        if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT)
507            && !hasEmptyLineAfter(ast)) {
508            log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
509        }
510    }
511
512    /**
513     * Process Variable.
514     *
515     * @param ast token
516     * @param nextToken next Token
517     */
518    private void processVariableDef(DetailAST ast, DetailAST nextToken) {
519        if (isTypeField(ast) && !hasEmptyLineAfter(ast)
520                && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
521            log(nextToken, MSG_SHOULD_BE_SEPARATED,
522                    nextToken.getText());
523        }
524    }
525
526    /**
527     * Checks whether token placement violates policy of empty line between fields.
528     *
529     * @param detailAST token to be analyzed
530     * @return true if policy is violated and warning should be raised; false otherwise
531     */
532    private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
533        return detailAST.getType() != TokenTypes.RCURLY
534                && (!allowNoEmptyLineBetweenFields
535                    || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF));
536    }
537
538    /**
539     * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
540     *
541     * @param token DetailAST token
542     * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
543     */
544    private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
545        return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
546                && isPrePreviousLineEmpty(token);
547    }
548
549    /**
550     * Check if group of comments located right before token has more than one previous empty line.
551     *
552     * @param token DetailAST token
553     */
554    private void checkComments(DetailAST token) {
555        if (!allowMultipleEmptyLines) {
556            if (TokenUtil.isOfType(token,
557                TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
558                TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) {
559                DetailAST previousNode = token.getPreviousSibling();
560                while (isCommentInBeginningOfLine(previousNode)) {
561                    if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) {
562                        log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText());
563                    }
564                    previousNode = previousNode.getPreviousSibling();
565                }
566            }
567            else {
568                checkCommentsInsideToken(token);
569            }
570        }
571    }
572
573    /**
574     * Check if group of comments located at the start of token has more than one previous empty
575     * line.
576     *
577     * @param token DetailAST token
578     */
579    private void checkCommentsInsideToken(DetailAST token) {
580        final List<DetailAST> childNodes = new LinkedList<>();
581        DetailAST childNode = token.getLastChild();
582        while (childNode != null) {
583            if (childNode.getType() == TokenTypes.MODIFIERS) {
584                for (DetailAST node = token.getFirstChild().getLastChild();
585                         node != null;
586                         node = node.getPreviousSibling()) {
587                    if (isCommentInBeginningOfLine(node)) {
588                        childNodes.add(node);
589                    }
590                }
591            }
592            else if (isCommentInBeginningOfLine(childNode)) {
593                childNodes.add(childNode);
594            }
595            childNode = childNode.getPreviousSibling();
596        }
597        for (DetailAST node : childNodes) {
598            if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) {
599                log(node, MSG_MULTIPLE_LINES, node.getText());
600            }
601        }
602    }
603
604    /**
605     * Checks if a token has empty pre-previous line.
606     *
607     * @param token DetailAST token.
608     * @return true, if token has empty lines before.
609     */
610    private boolean isPrePreviousLineEmpty(DetailAST token) {
611        boolean result = false;
612        final int lineNo = token.getLineNo();
613        // 3 is the number of the pre-previous line because the numbering starts from zero.
614        final int number = 3;
615        if (lineNo >= number) {
616            final String prePreviousLine = getLine(lineNo - number);
617            result = CommonUtil.isBlank(prePreviousLine);
618        }
619        return result;
620    }
621
622    /**
623     * Checks if token have empty line after.
624     *
625     * @param token token.
626     * @return true if token have empty line after.
627     */
628    private boolean hasEmptyLineAfter(DetailAST token) {
629        DetailAST lastToken = token.getLastChild().getLastChild();
630        if (lastToken == null) {
631            lastToken = token.getLastChild();
632        }
633        DetailAST nextToken = token.getNextSibling();
634        if (TokenUtil.isCommentType(nextToken.getType())) {
635            nextToken = nextToken.getNextSibling();
636        }
637        // Start of the next token
638        final int nextBegin = nextToken.getLineNo();
639        // End of current token.
640        final int currentEnd = lastToken.getLineNo();
641        return hasEmptyLine(currentEnd + 1, nextBegin - 1);
642    }
643
644    /**
645     * Finds comment in next sibling of given packageDef.
646     *
647     * @param packageDef token to check
648     * @return comment under the token
649     */
650    private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) {
651        return Optional.ofNullable(packageDef.getNextSibling())
652            .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS))
653            .map(DetailAST::getFirstChild)
654            .filter(token -> TokenUtil.isCommentType(token.getType()))
655            .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1);
656    }
657
658    /**
659     * Checks, whether there are empty lines within the specified line range. Line numbering is
660     * started from 1 for parameter values
661     *
662     * @param startLine number of the first line in the range
663     * @param endLine number of the second line in the range
664     * @return {@code true} if found any blank line within the range, {@code false}
665     *         otherwise
666     */
667    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
668    @SuppressWarnings("deprecation")
669    private boolean hasEmptyLine(int startLine, int endLine) {
670        // Initial value is false - blank line not found
671        boolean result = false;
672        final FileContents fileContents = getFileContents();
673        for (int line = startLine; line <= endLine; line++) {
674            // Check, if the line is blank. Lines are numbered from 0, so subtract 1
675            if (fileContents.lineIsBlank(line - 1)) {
676                result = true;
677                break;
678            }
679        }
680        return result;
681    }
682
683    /**
684     * Checks if a token has an empty line before.
685     *
686     * @param token token.
687     * @return true, if token have empty line before.
688     */
689    private boolean hasEmptyLineBefore(DetailAST token) {
690        boolean result = false;
691        final int lineNo = token.getLineNo();
692        if (lineNo != 1) {
693            // [lineNo - 2] is the number of the previous line as the numbering starts from zero.
694            final String lineBefore = getLine(lineNo - 2);
695            result = CommonUtil.isBlank(lineBefore);
696        }
697        return result;
698    }
699
700    /**
701     * Check if token is comment, which starting in beginning of line.
702     *
703     * @param comment comment token for check.
704     * @return true, if token is comment, which starting in beginning of line.
705     */
706    private boolean isCommentInBeginningOfLine(DetailAST comment) {
707        // comment.getLineNo() - 1 is the number of the previous line as the numbering starts
708        // from zero.
709        boolean result = false;
710        if (comment != null) {
711            final String lineWithComment = getLine(comment.getLineNo() - 1).trim();
712            result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*");
713        }
714        return result;
715    }
716
717    /**
718     * Check if token is preceded by javadoc comment.
719     *
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     * If variable definition is a type field.
735     *
736     * @param variableDef variable definition.
737     * @return true variable definition is a type field.
738     */
739    private static boolean isTypeField(DetailAST variableDef) {
740        return TokenUtil.isTypeDeclaration(variableDef.getParent().getParent().getType());
741    }
742
743}