001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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 com.puppycrawl.tools.checkstyle.api.Check;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 *
028 * Checks for empty line separators after header, package, all import declarations,
029 * fields, constructors, methods, nested classes,
030 * static initializers and instance initializers.
031 *
032 * <p> By default the check will check the following statements:
033 *  {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF},
034 *  {@link TokenTypes#IMPORT IMPORT},
035 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
036 *  {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF},
037 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
038 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT},
039 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
040 *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
041 *  {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}.
042 * </p>
043 *
044 * <p>
045 * Example of declarations without empty line separator:
046 * </p>
047 *
048 * <pre>
049 * ///////////////////////////////////////////////////
050 * //HEADER
051 * ///////////////////////////////////////////////////
052 * package com.puppycrawl.tools.checkstyle.whitespace;
053 * import java.io.Serializable;
054 * class Foo
055 * {
056 *     public static final int FOO_CONST = 1;
057 *     public void foo() {} //should be separated from previous statement.
058 * }
059 * </pre>
060 *
061 * <p> An example of how to configure the check with default parameters is:
062 * </p>
063 *
064 * <pre>
065 * &lt;module name="EmptyLineSeparator"/&gt;
066 * </pre>
067 *
068 * <p>
069 * Example of declarations with empty line separator
070 * that is expected by the Check by default:
071 * </p>
072 *
073 * <pre>
074 * ///////////////////////////////////////////////////
075 * //HEADER
076 * ///////////////////////////////////////////////////
077 *
078 * package com.puppycrawl.tools.checkstyle.whitespace;
079 *
080 * import java.io.Serializable;
081 *
082 * class Foo
083 * {
084 *     public static final int FOO_CONST = 1;
085 *
086 *     public void foo() {}
087 * }
088 * </pre>
089 * <p> An example how to check empty line after
090 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and
091 * {@link TokenTypes#METHOD_DEF METHOD_DEF}:
092 * </p>
093 *
094 * <pre>
095 * &lt;module name="EmptyLineSeparator"&gt;
096 *    &lt;property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/&gt;
097 * &lt;/module&gt;
098 * </pre>
099 *
100 * <p>
101 * An example how to allow no empty line between fields:
102 * </p>
103 * <pre>
104 * &lt;module name="EmptyLineSeparator"&gt;
105 *    &lt;property name="allowNoEmptyLineBetweenFields" value="true"/&gt;
106 * &lt;/module&gt;
107 * </pre>
108 *
109 * <p>
110 * Example of declarations with multiple empty lines between class members (allowed by default):
111 * </p>
112 *
113 * <pre>
114 * ///////////////////////////////////////////////////
115 * //HEADER
116 * ///////////////////////////////////////////////////
117 *
118 *
119 * package com.puppycrawl.tools.checkstyle.whitespace;
120 *
121 *
122 *
123 * import java.io.Serializable;
124 *
125 *
126 * class Foo
127 * {
128 *     public static final int FOO_CONST = 1;
129 *
130 *
131 *
132 *     public void foo() {}
133 * }
134 * </pre>
135 * <p>
136 * An example how to disallow multiple empty lines between class members:
137 * </p>
138 * <pre>
139 * &lt;module name="EmptyLineSeparator"&gt;
140 *    &lt;property name="allowMultipleEmptyLines" value="false"/&gt;
141 * &lt;/module&gt;
142 * </pre>
143 *
144 * @author maxvetrenko
145 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
146 */
147public class EmptyLineSeparatorCheck extends Check
148{
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    /** */
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    {
176        allowNoEmptyLineBetweenFields = allow;
177    }
178
179    /**
180     * Allow multiple empty lines between class members.
181     * @param allow User's value.
182     */
183    public void setAllowMultipleEmptyLines(boolean allow)
184    {
185        allowMultipleEmptyLines = allow;
186    }
187
188    @Override
189    public int[] getDefaultTokens()
190    {
191        return new int[] {
192            TokenTypes.PACKAGE_DEF,
193            TokenTypes.IMPORT,
194            TokenTypes.CLASS_DEF,
195            TokenTypes.INTERFACE_DEF,
196            TokenTypes.ENUM_DEF,
197            TokenTypes.STATIC_INIT,
198            TokenTypes.INSTANCE_INIT,
199            TokenTypes.METHOD_DEF,
200            TokenTypes.CTOR_DEF,
201            TokenTypes.VARIABLE_DEF,
202        };
203    }
204
205    @Override
206    public void visitToken(DetailAST ast)
207    {
208        final DetailAST nextToken = ast.getNextSibling();
209
210        if (nextToken != null) {
211            final int astType = ast.getType();
212            switch (astType) {
213                case TokenTypes.VARIABLE_DEF:
214                    if (isTypeField(ast) && !hasEmptyLineAfter(ast)) {
215                        if (allowNoEmptyLineBetweenFields
216                            && nextToken.getType() != TokenTypes.VARIABLE_DEF
217                            && nextToken.getType() != TokenTypes.RCURLY)
218                        {
219                            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
220                                 nextToken.getText());
221                        }
222                        else if ((!allowNoEmptyLineBetweenFields || !allowMultipleEmptyLines)
223                                 && nextToken.getType() != TokenTypes.RCURLY)
224                        {
225                            log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
226                                 nextToken.getText());
227                        }
228                    }
229                    if (!allowMultipleEmptyLines && isTypeField(ast)
230                             && isPrePreviousLineEmpty(ast))
231                    {
232                        log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
233                    }
234                    break;
235                case TokenTypes.IMPORT:
236                    if (astType != nextToken.getType() && !hasEmptyLineAfter(ast)
237                        || (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)
238                            && ast.getPreviousSibling() == null))
239                    {
240                        log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
241                    }
242                    if (!allowMultipleEmptyLines && isPrePreviousLineEmpty(ast)) {
243                        log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
244                    }
245                    break;
246                case TokenTypes.PACKAGE_DEF:
247                    if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
248                        log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
249                    }
250                    if (!allowMultipleEmptyLines && isPrePreviousLineEmpty(ast)) {
251                        log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
252                    }
253                default:
254                    if (nextToken.getType() != TokenTypes.RCURLY && !hasEmptyLineAfter(ast)) {
255                        log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
256                    }
257                    if (!allowMultipleEmptyLines && isPrePreviousLineEmpty(ast)) {
258                        log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
259                    }
260            }
261        }
262    }
263
264    /**
265     * Checks if a token has empty pre-previous line.
266     * @param token DetailAST token.
267     * @return true, if token has empty lines before.
268     */
269    private boolean isPrePreviousLineEmpty(DetailAST token)
270    {
271        final int lineNo = token.getLineNo();
272        // 3 is the number of the pre-previous line because the numbering starts from zero.
273        final int number = 3;
274        final String prePreviousLine = getLines()[lineNo - number];
275        return prePreviousLine.trim().isEmpty();
276    }
277
278    /**
279     * Checks if token have empty line after.
280     * @param token token.
281     * @return true if token have empty line after.
282     */
283    private boolean hasEmptyLineAfter(DetailAST token)
284    {
285        DetailAST lastToken = token.getLastChild().getLastChild();
286        if (null == lastToken) {
287            lastToken = token.getLastChild();
288        }
289        return token.getNextSibling().getLineNo() - lastToken.getLineNo() > 1;
290    }
291
292    /**
293     * Checks if a token has a empty line before.
294     * @param token token.
295     * @return true, if token have empty line before.
296     */
297    private boolean hasEmptyLineBefore(DetailAST token)
298    {
299        final int lineNo = token.getLineNo();
300        //  [lineNo - 2] is the number of the previous line because the numbering starts from zero.
301        final String lineBefore = getLines()[lineNo - 2];
302        return lineBefore.trim().isEmpty();
303    }
304
305    /**
306     * If variable definition is a type field.
307     * @param variableDef variable definition.
308     * @return true variable definition is a type field.
309     */
310    private boolean isTypeField(DetailAST variableDef)
311    {
312        final int parentType = variableDef.getParent().getParent().getType();
313        return parentType == TokenTypes.CLASS_DEF;
314    }
315}