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