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