1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import com.puppycrawl.tools.checkstyle.StatelessCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FileContents;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
32  
33  /**
34   * Checks for empty line separators after header, package, all import declarations,
35   * fields, constructors, methods, nested classes,
36   * static initializers and instance initializers.
37   *
38   * <p> By default the check will check the following statements:
39   *  {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF},
40   *  {@link TokenTypes#IMPORT IMPORT},
41   *  {@link TokenTypes#STATIC_IMPORT STATIC_IMPORT},
42   *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
43   *  {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF},
44   *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
45   *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT},
46   *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
47   *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
48   *  {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}.
49   * </p>
50   *
51   * <p>
52   * Example of declarations without empty line separator:
53   * </p>
54   *
55   * <pre>
56   * ///////////////////////////////////////////////////
57   * //HEADER
58   * ///////////////////////////////////////////////////
59   * package com.puppycrawl.tools.checkstyle.whitespace;
60   * import java.io.Serializable;
61   * class Foo
62   * {
63   *     public static final int FOO_CONST = 1;
64   *     public void foo() {} //should be separated from previous statement.
65   * }
66   * </pre>
67   *
68   * <p> An example of how to configure the check with default parameters is:
69   * </p>
70   *
71   * <pre>
72   * &lt;module name="EmptyLineSeparator"/&gt;
73   * </pre>
74   *
75   * <p>
76   * Example of declarations with empty line separator
77   * that is expected by the Check by default:
78   * </p>
79   *
80   * <pre>
81   * ///////////////////////////////////////////////////
82   * //HEADER
83   * ///////////////////////////////////////////////////
84   *
85   * package com.puppycrawl.tools.checkstyle.whitespace;
86   *
87   * import java.io.Serializable;
88   *
89   * class Foo
90   * {
91   *     public static final int FOO_CONST = 1;
92   *
93   *     public void foo() {}
94   * }
95   * </pre>
96   * <p> An example how to check empty line after
97   * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and
98   * {@link TokenTypes#METHOD_DEF METHOD_DEF}:
99   * </p>
100  *
101  * <pre>
102  * &lt;module name="EmptyLineSeparator"&gt;
103  *    &lt;property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/&gt;
104  * &lt;/module&gt;
105  * </pre>
106  *
107  * <p>
108  * An example how to allow no empty line between fields:
109  * </p>
110  * <pre>
111  * &lt;module name="EmptyLineSeparator"&gt;
112  *    &lt;property name="allowNoEmptyLineBetweenFields" value="true"/&gt;
113  * &lt;/module&gt;
114  * </pre>
115  *
116  * <p>
117  * Example of declarations with multiple empty lines between class members (allowed by default):
118  * </p>
119  *
120  * <pre>
121  * ///////////////////////////////////////////////////
122  * //HEADER
123  * ///////////////////////////////////////////////////
124  *
125  *
126  * package com.puppycrawl.tools.checkstyle.whitespace;
127  *
128  *
129  *
130  * import java.io.Serializable;
131  *
132  *
133  * class Foo
134  * {
135  *     public static final int FOO_CONST = 1;
136  *
137  *
138  *
139  *     public void foo() {}
140  * }
141  * </pre>
142  * <p>
143  * An example how to disallow multiple empty lines between class members:
144  * </p>
145  * <pre>
146  * &lt;module name="EmptyLineSeparator"&gt;
147  *    &lt;property name="allowMultipleEmptyLines" value="false"/&gt;
148  * &lt;/module&gt;
149  * </pre>
150  *
151  * <p>
152  * An example how to disallow multiple empty line inside methods, constructors, etc.:
153  * </p>
154  * <pre>
155  * &lt;module name="EmptyLineSeparator"&gt;
156  *    &lt;property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/&gt;
157  * &lt;/module&gt;
158  * </pre>
159  *
160  * <p> The check is valid only for statements that have body:
161  * {@link TokenTypes#CLASS_DEF},
162  * {@link TokenTypes#INTERFACE_DEF},
163  * {@link TokenTypes#ENUM_DEF},
164  * {@link TokenTypes#STATIC_INIT},
165  * {@link TokenTypes#INSTANCE_INIT},
166  * {@link TokenTypes#METHOD_DEF},
167  * {@link TokenTypes#CTOR_DEF}
168  * </p>
169  * <p>
170  * Example of declarations with multiple empty lines inside method:
171  * </p>
172  *
173  * <pre>
174  * ///////////////////////////////////////////////////
175  * //HEADER
176  * ///////////////////////////////////////////////////
177  *
178  * package com.puppycrawl.tools.checkstyle.whitespace;
179  *
180  * class Foo
181  * {
182  *
183  *     public void foo() {
184  *
185  *
186  *          System.out.println(1); // violation since method has 2 empty lines subsequently
187  *     }
188  * }
189  * </pre>
190  */
191 @StatelessCheck
192 public class EmptyLineSeparatorCheck extends AbstractCheck {
193 
194     /**
195      * A key is pointing to the warning message empty.line.separator in "messages.properties"
196      * file.
197      */
198     public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
199 
200     /**
201      * A key is pointing to the warning message empty.line.separator.multiple.lines
202      *  in "messages.properties"
203      * file.
204      */
205     public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
206 
207     /**
208      * A key is pointing to the warning message empty.line.separator.lines.after
209      * in "messages.properties" file.
210      */
211     public static final String MSG_MULTIPLE_LINES_AFTER =
212             "empty.line.separator.multiple.lines.after";
213 
214     /**
215      * A key is pointing to the warning message empty.line.separator.multiple.lines.inside
216      * in "messages.properties" file.
217      */
218     public static final String MSG_MULTIPLE_LINES_INSIDE =
219             "empty.line.separator.multiple.lines.inside";
220 
221     /** Allows no empty line between fields. */
222     private boolean allowNoEmptyLineBetweenFields;
223 
224     /** Allows multiple empty lines between class members. */
225     private boolean allowMultipleEmptyLines = true;
226 
227     /** Allows multiple empty lines inside class members. */
228     private boolean allowMultipleEmptyLinesInsideClassMembers = true;
229 
230     /**
231      * Allow no empty line between fields.
232      * @param allow
233      *        User's value.
234      */
235     public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
236         allowNoEmptyLineBetweenFields = allow;
237     }
238 
239     /**
240      * Allow multiple empty lines between class members.
241      * @param allow User's value.
242      */
243     public void setAllowMultipleEmptyLines(boolean allow) {
244         allowMultipleEmptyLines = allow;
245     }
246 
247     /**
248      * Allow multiple empty lines inside class members.
249      * @param allow User's value.
250      */
251     public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) {
252         allowMultipleEmptyLinesInsideClassMembers = allow;
253     }
254 
255     @Override
256     public boolean isCommentNodesRequired() {
257         return true;
258     }
259 
260     @Override
261     public int[] getDefaultTokens() {
262         return getAcceptableTokens();
263     }
264 
265     @Override
266     public int[] getAcceptableTokens() {
267         return new int[] {
268             TokenTypes.PACKAGE_DEF,
269             TokenTypes.IMPORT,
270             TokenTypes.STATIC_IMPORT,
271             TokenTypes.CLASS_DEF,
272             TokenTypes.INTERFACE_DEF,
273             TokenTypes.ENUM_DEF,
274             TokenTypes.STATIC_INIT,
275             TokenTypes.INSTANCE_INIT,
276             TokenTypes.METHOD_DEF,
277             TokenTypes.CTOR_DEF,
278             TokenTypes.VARIABLE_DEF,
279         };
280     }
281 
282     @Override
283     public int[] getRequiredTokens() {
284         return CommonUtil.EMPTY_INT_ARRAY;
285     }
286 
287     @Override
288     public void visitToken(DetailAST ast) {
289         if (hasMultipleLinesBefore(ast)) {
290             log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
291         }
292         if (!allowMultipleEmptyLinesInsideClassMembers) {
293             processMultipleLinesInside(ast);
294         }
295 
296         DetailAST nextToken = ast.getNextSibling();
297         while (nextToken != null && isComment(nextToken)) {
298             nextToken = nextToken.getNextSibling();
299         }
300         if (nextToken != null) {
301             final int astType = ast.getType();
302             switch (astType) {
303                 case TokenTypes.VARIABLE_DEF:
304                     processVariableDef(ast, nextToken);
305                     break;
306                 case TokenTypes.IMPORT:
307                 case TokenTypes.STATIC_IMPORT:
308                     processImport(ast, nextToken);
309                     break;
310                 case TokenTypes.PACKAGE_DEF:
311                     processPackage(ast, nextToken);
312                     break;
313                 default:
314                     if (nextToken.getType() == TokenTypes.RCURLY) {
315                         if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
316                             log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText());
317                         }
318                     }
319                     else if (!hasEmptyLineAfter(ast)) {
320                         log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
321                             nextToken.getText());
322                     }
323             }
324         }
325     }
326 
327     /**
328      * Log violation in case there are multiple empty lines inside constructor,
329      * initialization block or method.
330      * @param ast the ast to check.
331      */
332     private void processMultipleLinesInside(DetailAST ast) {
333         final int astType = ast.getType();
334         if (isClassMemberBlock(astType)) {
335             final List<Integer> emptyLines = getEmptyLines(ast);
336             final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
337 
338             for (Integer lineNo : emptyLinesToLog) {
339                 // Checkstyle counts line numbers from 0 but IDE from 1
340                 log(lineNo + 1, MSG_MULTIPLE_LINES_INSIDE);
341             }
342         }
343     }
344 
345     /**
346      * Whether the AST is a class member block.
347      * @param astType the AST to check.
348      * @return true if the AST is a class member block.
349      */
350     private static boolean isClassMemberBlock(int astType) {
351         return astType == TokenTypes.STATIC_INIT
352                 || astType == TokenTypes.INSTANCE_INIT
353                 || astType == TokenTypes.METHOD_DEF
354                 || astType == TokenTypes.CTOR_DEF;
355     }
356 
357     /**
358      * Get list of empty lines.
359      * @param ast the ast to check.
360      * @return list of line numbers for empty lines.
361      */
362     private List<Integer> getEmptyLines(DetailAST ast) {
363         final DetailAST lastToken = ast.getLastChild().getLastChild();
364         int lastTokenLineNo = 0;
365         if (lastToken != null) {
366             // -1 as count starts from 0
367             // -2 as last token line cannot be empty, because it is a RCURLY
368             lastTokenLineNo = lastToken.getLineNo() - 2;
369         }
370         final List<Integer> emptyLines = new ArrayList<>();
371         final FileContents fileContents = getFileContents();
372 
373         for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
374             if (fileContents.lineIsBlank(lineNo)) {
375                 emptyLines.add(lineNo);
376             }
377         }
378         return emptyLines;
379     }
380 
381     /**
382      * Get list of empty lines to log.
383      * @param emptyLines list of empty lines.
384      * @return list of empty lines to log.
385      */
386     private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) {
387         final List<Integer> emptyLinesToLog = new ArrayList<>();
388         if (emptyLines.size() >= 2) {
389             int previousEmptyLineNo = emptyLines.get(0);
390             for (int emptyLineNo : emptyLines) {
391                 if (previousEmptyLineNo + 1 == emptyLineNo) {
392                     emptyLinesToLog.add(emptyLineNo);
393                 }
394                 previousEmptyLineNo = emptyLineNo;
395             }
396         }
397         return emptyLinesToLog;
398     }
399 
400     /**
401      * Whether the token has not allowed multiple empty lines before.
402      * @param ast the ast to check.
403      * @return true if the token has not allowed multiple empty lines before.
404      */
405     private boolean hasMultipleLinesBefore(DetailAST ast) {
406         boolean result = false;
407         if ((ast.getType() != TokenTypes.VARIABLE_DEF
408             || isTypeField(ast))
409                 && hasNotAllowedTwoEmptyLinesBefore(ast)) {
410             result = true;
411         }
412         return result;
413     }
414 
415     /**
416      * Process Package.
417      * @param ast token
418      * @param nextToken next token
419      */
420     private void processPackage(DetailAST ast, DetailAST nextToken) {
421         if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
422             if (getFileContents().getFileName().endsWith("package-info.java")) {
423                 if (ast.getFirstChild().getChildCount() == 0 && !isPrecededByJavadoc(ast)) {
424                     log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
425                 }
426             }
427             else {
428                 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
429             }
430         }
431         if (!hasEmptyLineAfter(ast)) {
432             log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
433         }
434     }
435 
436     /**
437      * Process Import.
438      * @param ast token
439      * @param nextToken next token
440      */
441     private void processImport(DetailAST ast, DetailAST nextToken) {
442         if (nextToken.getType() != TokenTypes.IMPORT
443                 && nextToken.getType() != TokenTypes.STATIC_IMPORT && !hasEmptyLineAfter(ast)) {
444             log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
445         }
446     }
447 
448     /**
449      * Process Variable.
450      * @param ast token
451      * @param nextToken next Token
452      */
453     private void processVariableDef(DetailAST ast, DetailAST nextToken) {
454         if (isTypeField(ast) && !hasEmptyLineAfter(ast)
455                 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
456             log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
457                     nextToken.getText());
458         }
459     }
460 
461     /**
462      * Checks whether token placement violates policy of empty line between fields.
463      * @param detailAST token to be analyzed
464      * @return true if policy is violated and warning should be raised; false otherwise
465      */
466     private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
467         return detailAST.getType() != TokenTypes.RCURLY
468                 && (!allowNoEmptyLineBetweenFields
469                     || detailAST.getType() != TokenTypes.VARIABLE_DEF);
470     }
471 
472     /**
473      * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
474      * @param token DetailAST token
475      * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
476      */
477     private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
478         return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
479                 && isPrePreviousLineEmpty(token);
480     }
481 
482     /**
483      * Checks if a token has empty pre-previous line.
484      * @param token DetailAST token.
485      * @return true, if token has empty lines before.
486      */
487     private boolean isPrePreviousLineEmpty(DetailAST token) {
488         boolean result = false;
489         final int lineNo = token.getLineNo();
490         // 3 is the number of the pre-previous line because the numbering starts from zero.
491         final int number = 3;
492         if (lineNo >= number) {
493             final String prePreviousLine = getLines()[lineNo - number];
494             result = CommonUtil.isBlank(prePreviousLine);
495         }
496         return result;
497     }
498 
499     /**
500      * Checks if token have empty line after.
501      * @param token token.
502      * @return true if token have empty line after.
503      */
504     private boolean hasEmptyLineAfter(DetailAST token) {
505         DetailAST lastToken = token.getLastChild().getLastChild();
506         if (lastToken == null) {
507             lastToken = token.getLastChild();
508         }
509         DetailAST nextToken = token.getNextSibling();
510         if (isComment(nextToken)) {
511             nextToken = nextToken.getNextSibling();
512         }
513         // Start of the next token
514         final int nextBegin = nextToken.getLineNo();
515         // End of current token.
516         final int currentEnd = lastToken.getLineNo();
517         return hasEmptyLine(currentEnd + 1, nextBegin - 1);
518     }
519 
520     /**
521      * Checks, whether there are empty lines within the specified line range. Line numbering is
522      * started from 1 for parameter values
523      * @param startLine number of the first line in the range
524      * @param endLine number of the second line in the range
525      * @return {@code true} if found any blank line within the range, {@code false}
526      *         otherwise
527      */
528     private boolean hasEmptyLine(int startLine, int endLine) {
529         // Initial value is false - blank line not found
530         boolean result = false;
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         return result;
540     }
541 
542     /**
543      * Checks if a token has a empty line before.
544      * @param token token.
545      * @return true, if token have empty line before.
546      */
547     private boolean hasEmptyLineBefore(DetailAST token) {
548         boolean result = false;
549         final int lineNo = token.getLineNo();
550         if (lineNo != 1) {
551             // [lineNo - 2] is the number of the previous line as the numbering starts from zero.
552             final String lineBefore = getLines()[lineNo - 2];
553             result = CommonUtil.isBlank(lineBefore);
554         }
555         return result;
556     }
557 
558     /**
559      * Check if token is preceded by javadoc comment.
560      * @param token token for check.
561      * @return true, if token is preceded by javadoc comment.
562      */
563     private static boolean isPrecededByJavadoc(DetailAST token) {
564         boolean result = false;
565         final DetailAST previous = token.getPreviousSibling();
566         if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
567                 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
568             result = true;
569         }
570         return result;
571     }
572 
573     /**
574      * Check if token is a comment.
575      * @param ast ast node
576      * @return true, if given ast is comment.
577      */
578     private static boolean isComment(DetailAST ast) {
579         return ast.getType() == TokenTypes.SINGLE_LINE_COMMENT
580                    || ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN;
581     }
582 
583     /**
584      * If variable definition is a type field.
585      * @param variableDef variable definition.
586      * @return true variable definition is a type field.
587      */
588     private static boolean isTypeField(DetailAST variableDef) {
589         final int parentType = variableDef.getParent().getParent().getType();
590         return parentType == TokenTypes.CLASS_DEF;
591     }
592 
593 }