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