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