View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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.LinkedList;
24  import java.util.List;
25  import java.util.Optional;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
35  
36  /**
37   * <div>
38   * Checks for empty line separators before package, all import declarations,
39   * fields, constructors, methods, nested classes,
40   * static initializers and instance initializers.
41   * </div>
42   *
43   * <p>
44   * Checks for empty line separators before not only statements but
45   * implementation and documentation comments and blocks as well.
46   * </p>
47   *
48   * <p>
49   * ATTENTION: empty line separator is required between token siblings,
50   * not after line where token is found.
51   * If token does not have a sibling of the same type, then empty line
52   * is required at its end (for example for CLASS_DEF it is after '}').
53   * Also, trailing comments are skipped.
54   * </p>
55   * <ul>
56   * <li>
57   * Property {@code allowMultipleEmptyLines} - Allow multiple empty lines between class members.
58   * Type is {@code boolean}.
59   * Default value is {@code true}.
60   * </li>
61   * <li>
62   * Property {@code allowMultipleEmptyLinesInsideClassMembers} - Allow multiple
63   * empty lines inside class members.
64   * Type is {@code boolean}.
65   * Default value is {@code true}.
66   * </li>
67   * <li>
68   * Property {@code allowNoEmptyLineBetweenFields} - Allow no empty line between fields.
69   * Type is {@code boolean}.
70   * Default value is {@code false}.
71   * </li>
72   * <li>
73   * Property {@code tokens} - tokens to check
74   * Type is {@code java.lang.String[]}.
75   * Validation type is {@code tokenSet}.
76   * Default value is:
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF">
78   * PACKAGE_DEF</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IMPORT">
80   * IMPORT</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_IMPORT">
82   * STATIC_IMPORT</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
84   * CLASS_DEF</a>,
85   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
86   * INTERFACE_DEF</a>,
87   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
88   * ENUM_DEF</a>,
89   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
90   * STATIC_INIT</a>,
91   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
92   * INSTANCE_INIT</a>,
93   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
94   * METHOD_DEF</a>,
95   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
96   * CTOR_DEF</a>,
97   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
98   * VARIABLE_DEF</a>,
99   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
100  * RECORD_DEF</a>,
101  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
102  * COMPACT_CTOR_DEF</a>.
103  * </li>
104  * </ul>
105  *
106  * <p>
107  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
108  * </p>
109  *
110  * <p>
111  * Violation Message Keys:
112  * </p>
113  * <ul>
114  * <li>
115  * {@code empty.line.separator}
116  * </li>
117  * <li>
118  * {@code empty.line.separator.multiple.lines}
119  * </li>
120  * <li>
121  * {@code empty.line.separator.multiple.lines.after}
122  * </li>
123  * <li>
124  * {@code empty.line.separator.multiple.lines.inside}
125  * </li>
126  * </ul>
127  *
128  * @since 5.8
129  */
130 @StatelessCheck
131 public class EmptyLineSeparatorCheck extends AbstractCheck {
132 
133     /**
134      * A key is pointing to the warning message empty.line.separator in "messages.properties"
135      * file.
136      */
137     public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
138 
139     /**
140      * A key is pointing to the warning message empty.line.separator.multiple.lines
141      *  in "messages.properties"
142      * file.
143      */
144     public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
145 
146     /**
147      * A key is pointing to the warning message empty.line.separator.lines.after
148      * in "messages.properties" file.
149      */
150     public static final String MSG_MULTIPLE_LINES_AFTER =
151             "empty.line.separator.multiple.lines.after";
152 
153     /**
154      * A key is pointing to the warning message empty.line.separator.multiple.lines.inside
155      * in "messages.properties" file.
156      */
157     public static final String MSG_MULTIPLE_LINES_INSIDE =
158             "empty.line.separator.multiple.lines.inside";
159 
160     /** Allow no empty line between fields. */
161     private boolean allowNoEmptyLineBetweenFields;
162 
163     /** Allow multiple empty lines between class members. */
164     private boolean allowMultipleEmptyLines = true;
165 
166     /** Allow multiple empty lines inside class members. */
167     private boolean allowMultipleEmptyLinesInsideClassMembers = true;
168 
169     /**
170      * Setter to allow no empty line between fields.
171      *
172      * @param allow
173      *        User's value.
174      * @since 5.8
175      */
176     public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
177         allowNoEmptyLineBetweenFields = allow;
178     }
179 
180     /**
181      * Setter to allow multiple empty lines between class members.
182      *
183      * @param allow User's value.
184      * @since 6.3
185      */
186     public void setAllowMultipleEmptyLines(boolean allow) {
187         allowMultipleEmptyLines = allow;
188     }
189 
190     /**
191      * Setter to allow multiple empty lines inside class members.
192      *
193      * @param allow User's value.
194      * @since 6.18
195      */
196     public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) {
197         allowMultipleEmptyLinesInsideClassMembers = allow;
198     }
199 
200     @Override
201     public boolean isCommentNodesRequired() {
202         return true;
203     }
204 
205     @Override
206     public int[] getDefaultTokens() {
207         return getAcceptableTokens();
208     }
209 
210     @Override
211     public int[] getAcceptableTokens() {
212         return new int[] {
213             TokenTypes.PACKAGE_DEF,
214             TokenTypes.IMPORT,
215             TokenTypes.STATIC_IMPORT,
216             TokenTypes.CLASS_DEF,
217             TokenTypes.INTERFACE_DEF,
218             TokenTypes.ENUM_DEF,
219             TokenTypes.STATIC_INIT,
220             TokenTypes.INSTANCE_INIT,
221             TokenTypes.METHOD_DEF,
222             TokenTypes.CTOR_DEF,
223             TokenTypes.VARIABLE_DEF,
224             TokenTypes.RECORD_DEF,
225             TokenTypes.COMPACT_CTOR_DEF,
226         };
227     }
228 
229     @Override
230     public int[] getRequiredTokens() {
231         return CommonUtil.EMPTY_INT_ARRAY;
232     }
233 
234     @Override
235     public void visitToken(DetailAST ast) {
236         checkComments(ast);
237         if (hasMultipleLinesBefore(ast)) {
238             log(ast, MSG_MULTIPLE_LINES, ast.getText());
239         }
240         if (!allowMultipleEmptyLinesInsideClassMembers) {
241             processMultipleLinesInside(ast);
242         }
243         if (ast.getType() == TokenTypes.PACKAGE_DEF) {
244             checkCommentInModifiers(ast);
245         }
246         DetailAST nextToken = ast.getNextSibling();
247         while (nextToken != null && TokenUtil.isCommentType(nextToken.getType())) {
248             nextToken = nextToken.getNextSibling();
249         }
250         if (nextToken != null) {
251             checkToken(ast, nextToken);
252         }
253     }
254 
255     /**
256      * Checks that token and next token are separated.
257      *
258      * @param ast token to validate
259      * @param nextToken next sibling of the token
260      */
261     private void checkToken(DetailAST ast, DetailAST nextToken) {
262         final int astType = ast.getType();
263 
264         switch (astType) {
265             case TokenTypes.VARIABLE_DEF -> processVariableDef(ast, nextToken);
266 
267             case TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT -> processImport(ast, nextToken);
268 
269             case TokenTypes.PACKAGE_DEF -> processPackage(ast, nextToken);
270 
271             default -> {
272                 if (nextToken.getType() == TokenTypes.RCURLY) {
273                     if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
274                         final DetailAST result = getLastElementBeforeEmptyLines(
275                                 ast, nextToken.getLineNo()
276                         );
277                         log(result, MSG_MULTIPLE_LINES_AFTER, result.getText());
278                     }
279                 }
280                 else if (!hasEmptyLineAfter(ast)) {
281                     log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
282                 }
283             }
284         }
285     }
286 
287     /**
288      * Checks that packageDef token is separated from comment in modifiers.
289      *
290      * @param packageDef package def token
291      */
292     private void checkCommentInModifiers(DetailAST packageDef) {
293         final Optional<DetailAST> comment = findCommentUnder(packageDef);
294         comment.ifPresent(commentValue -> {
295             log(commentValue, MSG_SHOULD_BE_SEPARATED, commentValue.getText());
296         });
297     }
298 
299     /**
300      * Log violation in case there are multiple empty lines inside constructor,
301      * initialization block or method.
302      *
303      * @param ast the ast to check.
304      */
305     private void processMultipleLinesInside(DetailAST ast) {
306         final int astType = ast.getType();
307         if (isClassMemberBlock(astType)) {
308             final List<Integer> emptyLines = getEmptyLines(ast);
309             final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
310             for (Integer lineNo : emptyLinesToLog) {
311                 log(getLastElementBeforeEmptyLines(ast, lineNo), MSG_MULTIPLE_LINES_INSIDE);
312             }
313         }
314     }
315 
316     /**
317      * Returns the element after which empty lines exist.
318      *
319      * @param ast the ast to check.
320      * @param line the empty line which gives violation.
321      * @return The DetailAST after which empty lines are present.
322      */
323     private static DetailAST getLastElementBeforeEmptyLines(DetailAST ast, int line) {
324         DetailAST result = ast;
325         if (ast.getFirstChild().getLineNo() <= line) {
326             result = ast.getFirstChild();
327             while (result.getNextSibling() != null
328                     && result.getNextSibling().getLineNo() <= line) {
329                 result = result.getNextSibling();
330             }
331             if (result.hasChildren()) {
332                 result = getLastElementBeforeEmptyLines(result, line);
333             }
334         }
335 
336         if (result.getNextSibling() != null) {
337             final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling());
338             if (postFixNode.isPresent()) {
339                 // A post fix AST will always have a sibling METHOD CALL
340                 // METHOD CALL will at least have two children
341                 // The first child is DOT in case of POSTFIX which have at least 2 children
342                 // First child of DOT again puts us back to normal AST tree which will
343                 // recurse down below from here
344                 final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow();
345                 result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line);
346             }
347         }
348         return result;
349     }
350 
351     /**
352      * Gets postfix Node from AST if present.
353      *
354      * @param ast the AST used to get postfix Node.
355      * @return Optional postfix node.
356      */
357     private static Optional<DetailAST> getPostFixNode(DetailAST ast) {
358         Optional<DetailAST> result = Optional.empty();
359         if (ast.getType() == TokenTypes.EXPR
360             // EXPR always has at least one child
361             && ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
362             // METHOD CALL always has at two least child
363             final DetailAST node = ast.getFirstChild().getFirstChild();
364             if (node.getType() == TokenTypes.DOT) {
365                 result = Optional.of(node);
366             }
367         }
368         return result;
369     }
370 
371     /**
372      * Whether the AST is a class member block.
373      *
374      * @param astType the AST to check.
375      * @return true if the AST is a class member block.
376      */
377     private static boolean isClassMemberBlock(int astType) {
378         return TokenUtil.isOfType(astType,
379             TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.METHOD_DEF,
380             TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF);
381     }
382 
383     /**
384      * Get list of empty lines.
385      *
386      * @param ast the ast to check.
387      * @return list of line numbers for empty lines.
388      */
389     private List<Integer> getEmptyLines(DetailAST ast) {
390         final DetailAST lastToken = ast.getLastChild().getLastChild();
391         int lastTokenLineNo = 0;
392         if (lastToken != null) {
393             // -1 as count starts from 0
394             // -2 as last token line cannot be empty, because it is a RCURLY
395             lastTokenLineNo = lastToken.getLineNo() - 2;
396         }
397         final List<Integer> emptyLines = new ArrayList<>();
398 
399         for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
400             if (CommonUtil.isBlank(getLine(lineNo))) {
401                 emptyLines.add(lineNo);
402             }
403         }
404         return emptyLines;
405     }
406 
407     /**
408      * Get list of empty lines to log.
409      *
410      * @param emptyLines list of empty lines.
411      * @return list of empty lines to log.
412      */
413     private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) {
414         final List<Integer> emptyLinesToLog = new ArrayList<>();
415         int previousEmptyLineNo = -1;
416         for (int emptyLineNo : emptyLines) {
417             if (previousEmptyLineNo + 1 == emptyLineNo) {
418                 emptyLinesToLog.add(previousEmptyLineNo);
419             }
420             previousEmptyLineNo = emptyLineNo;
421         }
422         return emptyLinesToLog;
423     }
424 
425     /**
426      * Whether the token has not allowed multiple empty lines before.
427      *
428      * @param ast the ast to check.
429      * @return true if the token has not allowed multiple empty lines before.
430      */
431     private boolean hasMultipleLinesBefore(DetailAST ast) {
432         return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast))
433                 && hasNotAllowedTwoEmptyLinesBefore(ast);
434     }
435 
436     /**
437      * Process Package.
438      *
439      * @param ast token
440      * @param nextToken next token
441      */
442     private void processPackage(DetailAST ast, DetailAST nextToken) {
443         if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
444             if (CheckUtil.isPackageInfo(getFilePath())) {
445                 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) {
446                     log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
447                 }
448             }
449             else {
450                 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
451             }
452         }
453         if (isLineEmptyAfterPackage(ast)) {
454             final DetailAST elementAst = getViolationAstForPackage(ast);
455             log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText());
456         }
457         else if (!hasEmptyLineAfter(ast)) {
458             log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
459         }
460     }
461 
462     /**
463      * Checks if there is another element at next line of package declaration.
464      *
465      * @param ast Package ast.
466      * @return true, if there is an element.
467      */
468     private static boolean isLineEmptyAfterPackage(DetailAST ast) {
469         DetailAST nextElement = ast;
470         final int lastChildLineNo = ast.getLastChild().getLineNo();
471         while (nextElement.getLineNo() < lastChildLineNo + 1
472                 && nextElement.getNextSibling() != null) {
473             nextElement = nextElement.getNextSibling();
474         }
475         return nextElement.getLineNo() == lastChildLineNo + 1;
476     }
477 
478     /**
479      * Gets the Ast on which violation is to be given for package declaration.
480      *
481      * @param ast Package ast.
482      * @return Violation ast.
483      */
484     private static DetailAST getViolationAstForPackage(DetailAST ast) {
485         DetailAST nextElement = ast;
486         final int lastChildLineNo = ast.getLastChild().getLineNo();
487         while (nextElement.getLineNo() < lastChildLineNo + 1) {
488             nextElement = nextElement.getNextSibling();
489         }
490         return nextElement;
491     }
492 
493     /**
494      * Process Import.
495      *
496      * @param ast token
497      * @param nextToken next token
498      */
499     private void processImport(DetailAST ast, DetailAST nextToken) {
500         if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT)
501             && !hasEmptyLineAfter(ast)) {
502             log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
503         }
504     }
505 
506     /**
507      * Process Variable.
508      *
509      * @param ast token
510      * @param nextToken next Token
511      */
512     private void processVariableDef(DetailAST ast, DetailAST nextToken) {
513         if (isTypeField(ast) && !hasEmptyLineAfter(ast)
514                 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
515             log(nextToken, MSG_SHOULD_BE_SEPARATED,
516                     nextToken.getText());
517         }
518     }
519 
520     /**
521      * Checks whether token placement violates policy of empty line between fields.
522      *
523      * @param detailAST token to be analyzed
524      * @return true if policy is violated and warning should be raised; false otherwise
525      */
526     private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
527         return detailAST.getType() != TokenTypes.RCURLY
528                 && (!allowNoEmptyLineBetweenFields
529                     || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF));
530     }
531 
532     /**
533      * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
534      *
535      * @param token DetailAST token
536      * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
537      */
538     private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
539         return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
540                 && isPrePreviousLineEmpty(token);
541     }
542 
543     /**
544      * Check if group of comments located right before token has more than one previous empty line.
545      *
546      * @param token DetailAST token
547      */
548     private void checkComments(DetailAST token) {
549         if (!allowMultipleEmptyLines) {
550             if (TokenUtil.isOfType(token,
551                 TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
552                 TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) {
553                 DetailAST previousNode = token.getPreviousSibling();
554                 while (isCommentInBeginningOfLine(previousNode)) {
555                     if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) {
556                         log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText());
557                     }
558                     previousNode = previousNode.getPreviousSibling();
559                 }
560             }
561             else {
562                 checkCommentsInsideToken(token);
563             }
564         }
565     }
566 
567     /**
568      * Check if group of comments located at the start of token has more than one previous empty
569      * line.
570      *
571      * @param token DetailAST token
572      */
573     private void checkCommentsInsideToken(DetailAST token) {
574         final List<DetailAST> childNodes = new LinkedList<>();
575         DetailAST childNode = token.getLastChild();
576         while (childNode != null) {
577             if (childNode.getType() == TokenTypes.MODIFIERS) {
578                 for (DetailAST node = token.getFirstChild().getLastChild();
579                          node != null;
580                          node = node.getPreviousSibling()) {
581                     if (isCommentInBeginningOfLine(node)) {
582                         childNodes.add(node);
583                     }
584                 }
585             }
586             else if (isCommentInBeginningOfLine(childNode)) {
587                 childNodes.add(childNode);
588             }
589             childNode = childNode.getPreviousSibling();
590         }
591         for (DetailAST node : childNodes) {
592             if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) {
593                 log(node, MSG_MULTIPLE_LINES, node.getText());
594             }
595         }
596     }
597 
598     /**
599      * Checks if a token has empty pre-previous line.
600      *
601      * @param token DetailAST token.
602      * @return true, if token has empty lines before.
603      */
604     private boolean isPrePreviousLineEmpty(DetailAST token) {
605         boolean result = false;
606         final int lineNo = token.getLineNo();
607         // 3 is the number of the pre-previous line because the numbering starts from zero.
608         final int number = 3;
609         if (lineNo >= number) {
610             final String prePreviousLine = getLine(lineNo - number);
611 
612             result = CommonUtil.isBlank(prePreviousLine);
613             final boolean previousLineIsEmpty = CommonUtil.isBlank(getLine(lineNo - 2));
614 
615             if (previousLineIsEmpty && result) {
616                 result = true;
617             }
618             else if (token.findFirstToken(TokenTypes.TYPE) != null) {
619                 result = isTwoPrecedingPreviousLinesFromCommentEmpty(token);
620             }
621         }
622         return result;
623 
624     }
625 
626     /**
627      * Checks if token has two preceding lines empty, starting from its describing comment.
628      *
629      * @param token token checked.
630      * @return true, if both previous and pre-previous lines from dependent comment are empty
631      */
632     private boolean isTwoPrecedingPreviousLinesFromCommentEmpty(DetailAST token) {
633         boolean upToPrePreviousLinesEmpty = false;
634 
635         for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild();
636              typeChild != null; typeChild = typeChild.getPreviousSibling()) {
637 
638             if (isTokenNotOnPreviousSiblingLines(typeChild, token)) {
639 
640                 final String commentBeginningPreviousLine =
641                     getLine(typeChild.getLineNo() - 2);
642                 final String commentBeginningPrePreviousLine =
643                     getLine(typeChild.getLineNo() - 3);
644 
645                 if (CommonUtil.isBlank(commentBeginningPreviousLine)
646                     && CommonUtil.isBlank(commentBeginningPrePreviousLine)) {
647                     upToPrePreviousLinesEmpty = true;
648                     break;
649                 }
650 
651             }
652 
653         }
654 
655         return upToPrePreviousLinesEmpty;
656     }
657 
658     /**
659      * Checks if token is not placed on the realm of previous sibling of token's parent.
660      *
661      * @param token token checked.
662      * @param parentToken parent token.
663      * @return true, if child token doesn't occupy parent token's previous sibling's realm.
664      */
665     private static boolean isTokenNotOnPreviousSiblingLines(DetailAST token,
666                                                             DetailAST parentToken) {
667         DetailAST previousSibling = parentToken.getPreviousSibling();
668         for (DetailAST astNode = previousSibling; astNode != null;
669              astNode = astNode.getLastChild()) {
670             previousSibling = astNode;
671         }
672 
673         return token.getLineNo() != previousSibling.getLineNo();
674     }
675 
676     /**
677      * Checks if token have empty line after.
678      *
679      * @param token token.
680      * @return true if token have empty line after.
681      */
682     private boolean hasEmptyLineAfter(DetailAST token) {
683         DetailAST lastToken = token.getLastChild().getLastChild();
684         if (lastToken == null) {
685             lastToken = token.getLastChild();
686         }
687         DetailAST nextToken = token.getNextSibling();
688         if (TokenUtil.isCommentType(nextToken.getType())) {
689             nextToken = nextToken.getNextSibling();
690         }
691         // Start of the next token
692         final int nextBegin = nextToken.getLineNo();
693         // End of current token.
694         final int currentEnd = lastToken.getLineNo();
695         return hasEmptyLine(currentEnd + 1, nextBegin - 1);
696     }
697 
698     /**
699      * Finds comment in next sibling of given packageDef.
700      *
701      * @param packageDef token to check
702      * @return comment under the token
703      */
704     private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) {
705         return Optional.ofNullable(packageDef.getNextSibling())
706             .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS))
707             .map(DetailAST::getFirstChild)
708             .filter(token -> TokenUtil.isCommentType(token.getType()))
709             .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1);
710     }
711 
712     /**
713      * Checks, whether there are empty lines within the specified line range. Line numbering is
714      * started from 1 for parameter values
715      *
716      * @param startLine number of the first line in the range
717      * @param endLine number of the second line in the range
718      * @return {@code true} if found any blank line within the range, {@code false}
719      *         otherwise
720      */
721     private boolean hasEmptyLine(int startLine, int endLine) {
722         // Initial value is false - blank line not found
723         boolean result = false;
724         for (int line = startLine; line <= endLine; line++) {
725             // Check, if the line is blank. Lines are numbered from 0, so subtract 1
726             if (CommonUtil.isBlank(getLine(line - 1))) {
727                 result = true;
728                 break;
729             }
730         }
731         return result;
732     }
733 
734     /**
735      * Checks if a token has an empty line before.
736      *
737      * @param token token.
738      * @return true, if token have empty line before.
739      */
740     private boolean hasEmptyLineBefore(DetailAST token) {
741         boolean result = false;
742         final int lineNo = token.getLineNo();
743         if (lineNo != 1) {
744             // [lineNo - 2] is the number of the previous line as the numbering starts from zero.
745             final String lineBefore = getLine(lineNo - 2);
746 
747             if (CommonUtil.isBlank(lineBefore)) {
748                 result = true;
749             }
750             else if (token.findFirstToken(TokenTypes.TYPE) != null) {
751                 for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild();
752                      typeChild != null && !result && typeChild.getLineNo() > 1;
753                      typeChild = typeChild.getPreviousSibling()) {
754 
755                     final String commentBeginningPreviousLine =
756                         getLine(typeChild.getLineNo() - 2);
757                     result = CommonUtil.isBlank(commentBeginningPreviousLine);
758 
759                 }
760             }
761         }
762         return result;
763     }
764 
765     /**
766      * Check if token is comment, which starting in beginning of line.
767      *
768      * @param comment comment token for check.
769      * @return true, if token is comment, which starting in beginning of line.
770      */
771     private boolean isCommentInBeginningOfLine(DetailAST comment) {
772         // comment.getLineNo() - 1 is the number of the previous line as the numbering starts
773         // from zero.
774         boolean result = false;
775         if (comment != null) {
776             final String lineWithComment = getLine(comment.getLineNo() - 1).trim();
777             result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*");
778         }
779         return result;
780     }
781 
782     /**
783      * Check if token is preceded by javadoc comment.
784      *
785      * @param token token for check.
786      * @return true, if token is preceded by javadoc comment.
787      */
788     private static boolean isPrecededByJavadoc(DetailAST token) {
789         boolean result = false;
790         final DetailAST previous = token.getPreviousSibling();
791         if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
792                 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
793             result = true;
794         }
795         return result;
796     }
797 
798     /**
799      * If variable definition is a type field.
800      *
801      * @param variableDef variable definition.
802      * @return true variable definition is a type field.
803      */
804     private static boolean isTypeField(DetailAST variableDef) {
805         return TokenUtil.isTypeDeclaration(variableDef.getParent().getParent().getType());
806     }
807 
808 }