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