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