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