001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.FileContents;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
032
033/**
034 * Checks for empty line separators after header, package, all import declarations,
035 * fields, constructors, methods, nested classes,
036 * static initializers and instance initializers.
037 *
038 * <p> By default the check will check the following statements:
039 *  {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF},
040 *  {@link TokenTypes#IMPORT IMPORT},
041 *  {@link TokenTypes#STATIC_IMPORT STATIC_IMPORT},
042 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
043 *  {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF},
044 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
045 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT},
046 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
047 *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
048 *  {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}.
049 * </p>
050 *
051 * <p>
052 * Example of declarations without empty line separator:
053 * </p>
054 *
055 * <pre>
056 * ///////////////////////////////////////////////////
057 * //HEADER
058 * ///////////////////////////////////////////////////
059 * package com.puppycrawl.tools.checkstyle.whitespace;
060 * import java.io.Serializable;
061 * class Foo
062 * {
063 *     public static final int FOO_CONST = 1;
064 *     public void foo() {} //should be separated from previous statement.
065 * }
066 * </pre>
067 *
068 * <p> An example of how to configure the check with default parameters is:
069 * </p>
070 *
071 * <pre>
072 * &lt;module name="EmptyLineSeparator"/&gt;
073 * </pre>
074 *
075 * <p>
076 * Example of declarations with empty line separator
077 * that is expected by the Check by default:
078 * </p>
079 *
080 * <pre>
081 * ///////////////////////////////////////////////////
082 * //HEADER
083 * ///////////////////////////////////////////////////
084 *
085 * package com.puppycrawl.tools.checkstyle.whitespace;
086 *
087 * import java.io.Serializable;
088 *
089 * class Foo
090 * {
091 *     public static final int FOO_CONST = 1;
092 *
093 *     public void foo() {}
094 * }
095 * </pre>
096 * <p> An example how to check empty line after
097 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and
098 * {@link TokenTypes#METHOD_DEF METHOD_DEF}:
099 * </p>
100 *
101 * <pre>
102 * &lt;module name="EmptyLineSeparator"&gt;
103 *    &lt;property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/&gt;
104 * &lt;/module&gt;
105 * </pre>
106 *
107 * <p>
108 * An example how to allow no empty line between fields:
109 * </p>
110 * <pre>
111 * &lt;module name="EmptyLineSeparator"&gt;
112 *    &lt;property name="allowNoEmptyLineBetweenFields" value="true"/&gt;
113 * &lt;/module&gt;
114 * </pre>
115 *
116 * <p>
117 * Example of declarations with multiple empty lines between class members (allowed by default):
118 * </p>
119 *
120 * <pre>
121 * ///////////////////////////////////////////////////
122 * //HEADER
123 * ///////////////////////////////////////////////////
124 *
125 *
126 * package com.puppycrawl.tools.checkstyle.whitespace;
127 *
128 *
129 *
130 * import java.io.Serializable;
131 *
132 *
133 * class Foo
134 * {
135 *     public static final int FOO_CONST = 1;
136 *
137 *
138 *
139 *     public void foo() {}
140 * }
141 * </pre>
142 * <p>
143 * An example how to disallow multiple empty lines between class members:
144 * </p>
145 * <pre>
146 * &lt;module name="EmptyLineSeparator"&gt;
147 *    &lt;property name="allowMultipleEmptyLines" value="false"/&gt;
148 * &lt;/module&gt;
149 * </pre>
150 *
151 * <p>
152 * An example how to disallow multiple empty line inside methods, constructors, etc.:
153 * </p>
154 * <pre>
155 * &lt;module name="EmptyLineSeparator"&gt;
156 *    &lt;property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/&gt;
157 * &lt;/module&gt;
158 * </pre>
159 *
160 * <p> The check is valid only for statements that have body:
161 * {@link TokenTypes#CLASS_DEF},
162 * {@link TokenTypes#INTERFACE_DEF},
163 * {@link TokenTypes#ENUM_DEF},
164 * {@link TokenTypes#STATIC_INIT},
165 * {@link TokenTypes#INSTANCE_INIT},
166 * {@link TokenTypes#METHOD_DEF},
167 * {@link TokenTypes#CTOR_DEF}
168 * </p>
169 * <p>
170 * Example of declarations with multiple empty lines inside method:
171 * </p>
172 *
173 * <pre>
174 * ///////////////////////////////////////////////////
175 * //HEADER
176 * ///////////////////////////////////////////////////
177 *
178 * package com.puppycrawl.tools.checkstyle.whitespace;
179 *
180 * class Foo
181 * {
182 *
183 *     public void foo() {
184 *
185 *
186 *          System.out.println(1); // violation since method has 2 empty lines subsequently
187 *     }
188 * }
189 * </pre>
190 */
191@StatelessCheck
192public class EmptyLineSeparatorCheck extends AbstractCheck {
193
194    /**
195     * A key is pointing to the warning message empty.line.separator in "messages.properties"
196     * file.
197     */
198    public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
199
200    /**
201     * A key is pointing to the warning message empty.line.separator.multiple.lines
202     *  in "messages.properties"
203     * file.
204     */
205    public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
206
207    /**
208     * A key is pointing to the warning message empty.line.separator.lines.after
209     * in "messages.properties" file.
210     */
211    public static final String MSG_MULTIPLE_LINES_AFTER =
212            "empty.line.separator.multiple.lines.after";
213
214    /**
215     * A key is pointing to the warning message empty.line.separator.multiple.lines.inside
216     * in "messages.properties" file.
217     */
218    public static final String MSG_MULTIPLE_LINES_INSIDE =
219            "empty.line.separator.multiple.lines.inside";
220
221    /** Allows no empty line between fields. */
222    private boolean allowNoEmptyLineBetweenFields;
223
224    /** Allows multiple empty lines between class members. */
225    private boolean allowMultipleEmptyLines = true;
226
227    /** Allows multiple empty lines inside class members. */
228    private boolean allowMultipleEmptyLinesInsideClassMembers = true;
229
230    /**
231     * Allow no empty line between fields.
232     * @param allow
233     *        User's value.
234     */
235    public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
236        allowNoEmptyLineBetweenFields = allow;
237    }
238
239    /**
240     * Allow multiple empty lines between class members.
241     * @param allow User's value.
242     */
243    public void setAllowMultipleEmptyLines(boolean allow) {
244        allowMultipleEmptyLines = allow;
245    }
246
247    /**
248     * Allow multiple empty lines inside class members.
249     * @param allow User's value.
250     */
251    public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) {
252        allowMultipleEmptyLinesInsideClassMembers = allow;
253    }
254
255    @Override
256    public boolean isCommentNodesRequired() {
257        return true;
258    }
259
260    @Override
261    public int[] getDefaultTokens() {
262        return getAcceptableTokens();
263    }
264
265    @Override
266    public int[] getAcceptableTokens() {
267        return new int[] {
268            TokenTypes.PACKAGE_DEF,
269            TokenTypes.IMPORT,
270            TokenTypes.STATIC_IMPORT,
271            TokenTypes.CLASS_DEF,
272            TokenTypes.INTERFACE_DEF,
273            TokenTypes.ENUM_DEF,
274            TokenTypes.STATIC_INIT,
275            TokenTypes.INSTANCE_INIT,
276            TokenTypes.METHOD_DEF,
277            TokenTypes.CTOR_DEF,
278            TokenTypes.VARIABLE_DEF,
279        };
280    }
281
282    @Override
283    public int[] getRequiredTokens() {
284        return CommonUtil.EMPTY_INT_ARRAY;
285    }
286
287    @Override
288    public void visitToken(DetailAST ast) {
289        if (hasMultipleLinesBefore(ast)) {
290            log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
291        }
292        if (!allowMultipleEmptyLinesInsideClassMembers) {
293            processMultipleLinesInside(ast);
294        }
295
296        DetailAST nextToken = ast.getNextSibling();
297        while (nextToken != null && isComment(nextToken)) {
298            nextToken = nextToken.getNextSibling();
299        }
300        if (nextToken != null) {
301            final int astType = ast.getType();
302            switch (astType) {
303                case TokenTypes.VARIABLE_DEF:
304                    processVariableDef(ast, nextToken);
305                    break;
306                case TokenTypes.IMPORT:
307                case TokenTypes.STATIC_IMPORT:
308                    processImport(ast, nextToken);
309                    break;
310                case TokenTypes.PACKAGE_DEF:
311                    processPackage(ast, nextToken);
312                    break;
313                default:
314                    if (nextToken.getType() == TokenTypes.RCURLY) {
315                        if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
316                            log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText());
317                        }
318                    }
319                    else if (!hasEmptyLineAfter(ast)) {
320                        log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
321                            nextToken.getText());
322                    }
323            }
324        }
325    }
326
327    /**
328     * Log violation in case there are multiple empty lines inside constructor,
329     * initialization block or method.
330     * @param ast the ast to check.
331     */
332    private void processMultipleLinesInside(DetailAST ast) {
333        final int astType = ast.getType();
334        if (isClassMemberBlock(astType)) {
335            final List<Integer> emptyLines = getEmptyLines(ast);
336            final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
337
338            for (Integer lineNo : emptyLinesToLog) {
339                // Checkstyle counts line numbers from 0 but IDE from 1
340                log(lineNo + 1, MSG_MULTIPLE_LINES_INSIDE);
341            }
342        }
343    }
344
345    /**
346     * Whether the AST is a class member block.
347     * @param astType the AST to check.
348     * @return true if the AST is a class member block.
349     */
350    private static boolean isClassMemberBlock(int astType) {
351        return astType == TokenTypes.STATIC_INIT
352                || astType == TokenTypes.INSTANCE_INIT
353                || astType == TokenTypes.METHOD_DEF
354                || astType == TokenTypes.CTOR_DEF;
355    }
356
357    /**
358     * Get list of empty lines.
359     * @param ast the ast to check.
360     * @return list of line numbers for empty lines.
361     */
362    private List<Integer> getEmptyLines(DetailAST ast) {
363        final DetailAST lastToken = ast.getLastChild().getLastChild();
364        int lastTokenLineNo = 0;
365        if (lastToken != null) {
366            // -1 as count starts from 0
367            // -2 as last token line cannot be empty, because it is a RCURLY
368            lastTokenLineNo = lastToken.getLineNo() - 2;
369        }
370        final List<Integer> emptyLines = new ArrayList<>();
371        final FileContents fileContents = getFileContents();
372
373        for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
374            if (fileContents.lineIsBlank(lineNo)) {
375                emptyLines.add(lineNo);
376            }
377        }
378        return emptyLines;
379    }
380
381    /**
382     * Get list of empty lines to log.
383     * @param emptyLines list of empty lines.
384     * @return list of empty lines to log.
385     */
386    private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) {
387        final List<Integer> emptyLinesToLog = new ArrayList<>();
388        if (emptyLines.size() >= 2) {
389            int previousEmptyLineNo = emptyLines.get(0);
390            for (int emptyLineNo : emptyLines) {
391                if (previousEmptyLineNo + 1 == emptyLineNo) {
392                    emptyLinesToLog.add(emptyLineNo);
393                }
394                previousEmptyLineNo = emptyLineNo;
395            }
396        }
397        return emptyLinesToLog;
398    }
399
400    /**
401     * Whether the token has not allowed multiple empty lines before.
402     * @param ast the ast to check.
403     * @return true if the token has not allowed multiple empty lines before.
404     */
405    private boolean hasMultipleLinesBefore(DetailAST ast) {
406        boolean result = false;
407        if ((ast.getType() != TokenTypes.VARIABLE_DEF
408            || isTypeField(ast))
409                && hasNotAllowedTwoEmptyLinesBefore(ast)) {
410            result = true;
411        }
412        return result;
413    }
414
415    /**
416     * Process Package.
417     * @param ast token
418     * @param nextToken next token
419     */
420    private void processPackage(DetailAST ast, DetailAST nextToken) {
421        if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
422            if (getFileContents().getFileName().endsWith("package-info.java")) {
423                if (ast.getFirstChild().getChildCount() == 0 && !isPrecededByJavadoc(ast)) {
424                    log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
425                }
426            }
427            else {
428                log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
429            }
430        }
431        if (!hasEmptyLineAfter(ast)) {
432            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
433        }
434    }
435
436    /**
437     * Process Import.
438     * @param ast token
439     * @param nextToken next token
440     */
441    private void processImport(DetailAST ast, DetailAST nextToken) {
442        if (nextToken.getType() != TokenTypes.IMPORT
443                && nextToken.getType() != TokenTypes.STATIC_IMPORT && !hasEmptyLineAfter(ast)) {
444            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
445        }
446    }
447
448    /**
449     * Process Variable.
450     * @param ast token
451     * @param nextToken next Token
452     */
453    private void processVariableDef(DetailAST ast, DetailAST nextToken) {
454        if (isTypeField(ast) && !hasEmptyLineAfter(ast)
455                && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
456            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
457                    nextToken.getText());
458        }
459    }
460
461    /**
462     * Checks whether token placement violates policy of empty line between fields.
463     * @param detailAST token to be analyzed
464     * @return true if policy is violated and warning should be raised; false otherwise
465     */
466    private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
467        return detailAST.getType() != TokenTypes.RCURLY
468                && (!allowNoEmptyLineBetweenFields
469                    || detailAST.getType() != TokenTypes.VARIABLE_DEF);
470    }
471
472    /**
473     * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
474     * @param token DetailAST token
475     * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
476     */
477    private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
478        return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
479                && isPrePreviousLineEmpty(token);
480    }
481
482    /**
483     * Checks if a token has empty pre-previous line.
484     * @param token DetailAST token.
485     * @return true, if token has empty lines before.
486     */
487    private boolean isPrePreviousLineEmpty(DetailAST token) {
488        boolean result = false;
489        final int lineNo = token.getLineNo();
490        // 3 is the number of the pre-previous line because the numbering starts from zero.
491        final int number = 3;
492        if (lineNo >= number) {
493            final String prePreviousLine = getLines()[lineNo - number];
494            result = CommonUtil.isBlank(prePreviousLine);
495        }
496        return result;
497    }
498
499    /**
500     * Checks if token have empty line after.
501     * @param token token.
502     * @return true if token have empty line after.
503     */
504    private boolean hasEmptyLineAfter(DetailAST token) {
505        DetailAST lastToken = token.getLastChild().getLastChild();
506        if (lastToken == null) {
507            lastToken = token.getLastChild();
508        }
509        DetailAST nextToken = token.getNextSibling();
510        if (isComment(nextToken)) {
511            nextToken = nextToken.getNextSibling();
512        }
513        // Start of the next token
514        final int nextBegin = nextToken.getLineNo();
515        // End of current token.
516        final int currentEnd = lastToken.getLineNo();
517        return hasEmptyLine(currentEnd + 1, nextBegin - 1);
518    }
519
520    /**
521     * Checks, whether there are empty lines within the specified line range. Line numbering is
522     * started from 1 for parameter values
523     * @param startLine number of the first line in the range
524     * @param endLine number of the second line in the range
525     * @return {@code true} if found any blank line within the range, {@code false}
526     *         otherwise
527     */
528    private boolean hasEmptyLine(int startLine, int endLine) {
529        // Initial value is false - blank line not found
530        boolean result = false;
531        final FileContents fileContents = getFileContents();
532        for (int line = startLine; line <= endLine; line++) {
533            // Check, if the line is blank. Lines are numbered from 0, so subtract 1
534            if (fileContents.lineIsBlank(line - 1)) {
535                result = true;
536                break;
537            }
538        }
539        return result;
540    }
541
542    /**
543     * Checks if a token has a empty line before.
544     * @param token token.
545     * @return true, if token have empty line before.
546     */
547    private boolean hasEmptyLineBefore(DetailAST token) {
548        boolean result = false;
549        final int lineNo = token.getLineNo();
550        if (lineNo != 1) {
551            // [lineNo - 2] is the number of the previous line as the numbering starts from zero.
552            final String lineBefore = getLines()[lineNo - 2];
553            result = CommonUtil.isBlank(lineBefore);
554        }
555        return result;
556    }
557
558    /**
559     * Check if token is preceded by javadoc comment.
560     * @param token token for check.
561     * @return true, if token is preceded by javadoc comment.
562     */
563    private static boolean isPrecededByJavadoc(DetailAST token) {
564        boolean result = false;
565        final DetailAST previous = token.getPreviousSibling();
566        if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
567                && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
568            result = true;
569        }
570        return result;
571    }
572
573    /**
574     * Check if token is a comment.
575     * @param ast ast node
576     * @return true, if given ast is comment.
577     */
578    private static boolean isComment(DetailAST ast) {
579        return ast.getType() == TokenTypes.SINGLE_LINE_COMMENT
580                   || ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN;
581    }
582
583    /**
584     * If variable definition is a type field.
585     * @param variableDef variable definition.
586     * @return true variable definition is a type field.
587     */
588    private static boolean isTypeField(DetailAST variableDef) {
589        final int parentType = variableDef.getParent().getParent().getType();
590        return parentType == TokenTypes.CLASS_DEF;
591    }
592
593}