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