001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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 org.apache.commons.lang3.ArrayUtils;
023
024import com.puppycrawl.tools.checkstyle.api.Check;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.FileContents;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028
029/**
030 * Checks for empty line separators after header, package, all import declarations,
031 * fields, constructors, methods, nested classes,
032 * static initializers and instance initializers.
033 *
034 * <p> By default the check will check the following statements:
035 *  {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF},
036 *  {@link TokenTypes#IMPORT IMPORT},
037 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
038 *  {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF},
039 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
040 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT},
041 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
042 *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
043 *  {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}.
044 * </p>
045 *
046 * <p>
047 * Example of declarations without empty line separator:
048 * </p>
049 *
050 * <pre>
051 * ///////////////////////////////////////////////////
052 * //HEADER
053 * ///////////////////////////////////////////////////
054 * package com.puppycrawl.tools.checkstyle.whitespace;
055 * import java.io.Serializable;
056 * class Foo
057 * {
058 *     public static final int FOO_CONST = 1;
059 *     public void foo() {} //should be separated from previous statement.
060 * }
061 * </pre>
062 *
063 * <p> An example of how to configure the check with default parameters is:
064 * </p>
065 *
066 * <pre>
067 * &lt;module name="EmptyLineSeparator"/&gt;
068 * </pre>
069 *
070 * <p>
071 * Example of declarations with empty line separator
072 * that is expected by the Check by default:
073 * </p>
074 *
075 * <pre>
076 * ///////////////////////////////////////////////////
077 * //HEADER
078 * ///////////////////////////////////////////////////
079 *
080 * package com.puppycrawl.tools.checkstyle.whitespace;
081 *
082 * import java.io.Serializable;
083 *
084 * class Foo
085 * {
086 *     public static final int FOO_CONST = 1;
087 *
088 *     public void foo() {}
089 * }
090 * </pre>
091 * <p> An example how to check empty line after
092 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and
093 * {@link TokenTypes#METHOD_DEF METHOD_DEF}:
094 * </p>
095 *
096 * <pre>
097 * &lt;module name="EmptyLineSeparator"&gt;
098 *    &lt;property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/&gt;
099 * &lt;/module&gt;
100 * </pre>
101 *
102 * <p>
103 * An example how to allow no empty line between fields:
104 * </p>
105 * <pre>
106 * &lt;module name="EmptyLineSeparator"&gt;
107 *    &lt;property name="allowNoEmptyLineBetweenFields" value="true"/&gt;
108 * &lt;/module&gt;
109 * </pre>
110 *
111 * <p>
112 * Example of declarations with multiple empty lines between class members (allowed by default):
113 * </p>
114 *
115 * <pre>
116 * ///////////////////////////////////////////////////
117 * //HEADER
118 * ///////////////////////////////////////////////////
119 *
120 *
121 * package com.puppycrawl.tools.checkstyle.whitespace;
122 *
123 *
124 *
125 * import java.io.Serializable;
126 *
127 *
128 * class Foo
129 * {
130 *     public static final int FOO_CONST = 1;
131 *
132 *
133 *
134 *     public void foo() {}
135 * }
136 * </pre>
137 * <p>
138 * An example how to disallow multiple empty lines between class members:
139 * </p>
140 * <pre>
141 * &lt;module name="EmptyLineSeparator"&gt;
142 *    &lt;property name="allowMultipleEmptyLines" value="false"/&gt;
143 * &lt;/module&gt;
144 * </pre>
145 *
146 * @author maxvetrenko
147 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
148 */
149public class EmptyLineSeparatorCheck extends Check {
150
151    /**
152     * A key is pointing to the warning message empty.line.separator in "messages.properties"
153     * file.
154     */
155    public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
156
157    /**
158     * A key is pointing to the warning message empty.line.separator.multiple.lines
159     *  in "messages.properties"
160     * file.
161     */
162    public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
163
164    /**
165     * A key is pointing to the warning message empty.line.separator.lines.after
166     * in "messages.properties" file.
167     */
168    public static final String MSG_MULTIPLE_LINES_AFTER =
169            "empty.line.separator.multiple.lines.after";
170
171    /** Allows no empty line between fields. */
172    private boolean allowNoEmptyLineBetweenFields;
173
174    /** Allows multiple empty lines between class members. */
175    private boolean allowMultipleEmptyLines = true;
176
177    /**
178     * Allow no empty line between fields.
179     * @param allow
180     *        User's value.
181     */
182    public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
183        allowNoEmptyLineBetweenFields = allow;
184    }
185
186    /**
187     * Allow multiple empty lines between class members.
188     * @param allow User's value.
189     */
190    public void setAllowMultipleEmptyLines(boolean allow) {
191        allowMultipleEmptyLines = allow;
192    }
193
194    @Override
195    public int[] getDefaultTokens() {
196        return getAcceptableTokens();
197    }
198
199    @Override
200    public int[] getAcceptableTokens() {
201        return new int[] {
202            TokenTypes.PACKAGE_DEF,
203            TokenTypes.IMPORT,
204            TokenTypes.CLASS_DEF,
205            TokenTypes.INTERFACE_DEF,
206            TokenTypes.ENUM_DEF,
207            TokenTypes.STATIC_INIT,
208            TokenTypes.INSTANCE_INIT,
209            TokenTypes.METHOD_DEF,
210            TokenTypes.CTOR_DEF,
211            TokenTypes.VARIABLE_DEF,
212        };
213    }
214
215    @Override
216    public int[] getRequiredTokens() {
217        return ArrayUtils.EMPTY_INT_ARRAY;
218    }
219
220    @Override
221    public void visitToken(DetailAST ast) {
222        if (hasMultipleLinesBefore(ast)) {
223            log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
224        }
225
226        final DetailAST nextToken = ast.getNextSibling();
227        if (nextToken != null) {
228            final int astType = ast.getType();
229            switch (astType) {
230                case TokenTypes.VARIABLE_DEF:
231                    processVariableDef(ast, nextToken);
232                    break;
233                case TokenTypes.IMPORT:
234                    processImport(ast, nextToken, astType);
235                    break;
236                case TokenTypes.PACKAGE_DEF:
237                    processPackage(ast, nextToken);
238                    break;
239                default:
240                    if (nextToken.getType() == TokenTypes.RCURLY) {
241                        if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
242                            log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText());
243                        }
244                    }
245                    else if (!hasEmptyLineAfter(ast)) {
246                        log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
247                            nextToken.getText());
248                    }
249            }
250        }
251    }
252
253    /**
254     * Whether the token has not allowed multiple empty lines before.
255     * @param ast the ast to check.
256     * @return true if the token has not allowed multiple empty lines before.
257     */
258    private boolean hasMultipleLinesBefore(DetailAST ast) {
259        boolean result = false;
260        if ((ast.getType() != TokenTypes.VARIABLE_DEF
261            || isTypeField(ast))
262                && hasNotAllowedTwoEmptyLinesBefore(ast)) {
263            result = true;
264        }
265        return result;
266    }
267
268    /**
269     * Process Package.
270     * @param ast token
271     * @param nextToken next token
272     */
273    private void processPackage(DetailAST ast, DetailAST nextToken) {
274        if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
275            log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
276        }
277        if (!hasEmptyLineAfter(ast)) {
278            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
279        }
280    }
281
282    /**
283     * Process Import.
284     * @param ast token
285     * @param nextToken next token
286     * @param astType token Type
287     */
288    private void processImport(DetailAST ast, DetailAST nextToken, int astType) {
289        if (astType != nextToken.getType() && !hasEmptyLineAfter(ast)) {
290            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
291        }
292    }
293
294    /**
295     * Process Variable.
296     * @param ast token
297     * @param nextToken next Token
298     */
299    private void processVariableDef(DetailAST ast, DetailAST nextToken) {
300        if (isTypeField(ast) && !hasEmptyLineAfter(ast)
301                && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
302            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
303                    nextToken.getText());
304        }
305    }
306
307    /**
308     * Checks whether token placement violates policy of empty line between fields.
309     * @param detailAST token to be analyzed
310     * @return true if policy is violated and warning should be raised; false otherwise
311     */
312    private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
313        return allowNoEmptyLineBetweenFields
314                    && detailAST.getType() != TokenTypes.VARIABLE_DEF
315                    && detailAST.getType() != TokenTypes.RCURLY
316                || !allowNoEmptyLineBetweenFields
317                    && detailAST.getType() != TokenTypes.RCURLY;
318    }
319
320    /**
321     * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
322     * @param token DetailAST token
323     * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
324     */
325    private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
326        return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
327                && isPrePreviousLineEmpty(token);
328    }
329
330    /**
331     * Checks if a token has empty pre-previous line.
332     * @param token DetailAST token.
333     * @return true, if token has empty lines before.
334     */
335    private boolean isPrePreviousLineEmpty(DetailAST token) {
336        boolean result = false;
337        final int lineNo = token.getLineNo();
338        // 3 is the number of the pre-previous line because the numbering starts from zero.
339        final int number = 3;
340        if (lineNo >= number) {
341            final String prePreviousLine = getLines()[lineNo - number];
342            result = prePreviousLine.trim().isEmpty();
343        }
344        return result;
345    }
346
347    /**
348     * Checks if token have empty line after.
349     * @param token token.
350     * @return true if token have empty line after.
351     */
352    private boolean hasEmptyLineAfter(DetailAST token) {
353        DetailAST lastToken = token.getLastChild().getLastChild();
354        if (lastToken == null) {
355            lastToken = token.getLastChild();
356        }
357        // Start of the next token
358        final int nextBegin = token.getNextSibling().getLineNo();
359        // End of current token.
360        final int currentEnd = lastToken.getLineNo();
361        return hasEmptyLine(currentEnd + 1, nextBegin - 1);
362    }
363
364    /**
365     * Checks, whether there are empty lines within the specified line range. Line numbering is
366     * started from 1 for parameter values
367     * @param startLine number of the first line in the range
368     * @param endLine number of the second line in the range
369     * @return <code>true</code> if found any blank line within the range, <code>false</code>
370     *         otherwise
371     */
372    private boolean hasEmptyLine(int startLine, int endLine) {
373        // Initial value is false - blank line not found
374        boolean result = false;
375        if (startLine <= endLine) {
376            final FileContents fileContents = getFileContents();
377            for (int line = startLine; line <= endLine; line++) {
378                // Check, if the line is blank. Lines are numbered from 0, so subtract 1
379                if (fileContents.lineIsBlank(line - 1)) {
380                    result = true;
381                    break;
382                }
383            }
384        }
385        return result;
386    }
387
388    /**
389     * Checks if a token has a empty line before.
390     * @param token token.
391     * @return true, if token have empty line before.
392     */
393    private boolean hasEmptyLineBefore(DetailAST token) {
394        final int lineNo = token.getLineNo();
395        if (lineNo == 1) {
396            return false;
397        }
398        //  [lineNo - 2] is the number of the previous line because the numbering starts from zero.
399        final String lineBefore = getLines()[lineNo - 2];
400        return lineBefore.trim().isEmpty();
401    }
402
403    /**
404     * If variable definition is a type field.
405     * @param variableDef variable definition.
406     * @return true variable definition is a type field.
407     */
408    private static boolean isTypeField(DetailAST variableDef) {
409        final int parentType = variableDef.getParent().getParent().getType();
410        return parentType == TokenTypes.CLASS_DEF;
411    }
412}