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