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