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