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