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         switch (astType) {
264             case TokenTypes.VARIABLE_DEF:
265                 processVariableDef(ast, nextToken);
266                 break;
267             case TokenTypes.IMPORT:
268             case TokenTypes.STATIC_IMPORT:
269                 processImport(ast, nextToken);
270                 break;
271             case TokenTypes.PACKAGE_DEF:
272                 processPackage(ast, nextToken);
273                 break;
274             default:
275                 if (nextToken.getType() == TokenTypes.RCURLY) {
276                     if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
277                         final DetailAST result = getLastElementBeforeEmptyLines(ast,
278                                 nextToken.getLineNo());
279                         log(result, MSG_MULTIPLE_LINES_AFTER, result.getText());
280                     }
281                 }
282                 else if (!hasEmptyLineAfter(ast)) {
283                     log(nextToken, MSG_SHOULD_BE_SEPARATED,
284                         nextToken.getText());
285                 }
286         }
287     }
288 
289     /**
290      * Checks that packageDef token is separated from comment in modifiers.
291      *
292      * @param packageDef package def token
293      */
294     private void checkCommentInModifiers(DetailAST packageDef) {
295         final Optional<DetailAST> comment = findCommentUnder(packageDef);
296         comment.ifPresent(commentValue -> {
297             log(commentValue, MSG_SHOULD_BE_SEPARATED, commentValue.getText());
298         });
299     }
300 
301     /**
302      * Log violation in case there are multiple empty lines inside constructor,
303      * initialization block or method.
304      *
305      * @param ast the ast to check.
306      */
307     private void processMultipleLinesInside(DetailAST ast) {
308         final int astType = ast.getType();
309         if (isClassMemberBlock(astType)) {
310             final List<Integer> emptyLines = getEmptyLines(ast);
311             final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
312             for (Integer lineNo : emptyLinesToLog) {
313                 log(getLastElementBeforeEmptyLines(ast, lineNo), MSG_MULTIPLE_LINES_INSIDE);
314             }
315         }
316     }
317 
318     /**
319      * Returns the element after which empty lines exist.
320      *
321      * @param ast the ast to check.
322      * @param line the empty line which gives violation.
323      * @return The DetailAST after which empty lines are present.
324      */
325     private static DetailAST getLastElementBeforeEmptyLines(DetailAST ast, int line) {
326         DetailAST result = ast;
327         if (ast.getFirstChild().getLineNo() <= line) {
328             result = ast.getFirstChild();
329             while (result.getNextSibling() != null
330                     && result.getNextSibling().getLineNo() <= line) {
331                 result = result.getNextSibling();
332             }
333             if (result.hasChildren()) {
334                 result = getLastElementBeforeEmptyLines(result, line);
335             }
336         }
337 
338         if (result.getNextSibling() != null) {
339             final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling());
340             if (postFixNode.isPresent()) {
341                 // A post fix AST will always have a sibling METHOD CALL
342                 // METHOD CALL will at least have two children
343                 // The first child is DOT in case of POSTFIX which have at least 2 children
344                 // First child of DOT again puts us back to normal AST tree which will
345                 // recurse down below from here
346                 final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow();
347                 result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line);
348             }
349         }
350         return result;
351     }
352 
353     /**
354      * Gets postfix Node from AST if present.
355      *
356      * @param ast the AST used to get postfix Node.
357      * @return Optional postfix node.
358      */
359     private static Optional<DetailAST> getPostFixNode(DetailAST ast) {
360         Optional<DetailAST> result = Optional.empty();
361         if (ast.getType() == TokenTypes.EXPR
362             // EXPR always has at least one child
363             && ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
364             // METHOD CALL always has at two least child
365             final DetailAST node = ast.getFirstChild().getFirstChild();
366             if (node.getType() == TokenTypes.DOT) {
367                 result = Optional.of(node);
368             }
369         }
370         return result;
371     }
372 
373     /**
374      * Whether the AST is a class member block.
375      *
376      * @param astType the AST to check.
377      * @return true if the AST is a class member block.
378      */
379     private static boolean isClassMemberBlock(int astType) {
380         return TokenUtil.isOfType(astType,
381             TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.METHOD_DEF,
382             TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF);
383     }
384 
385     /**
386      * Get list of empty lines.
387      *
388      * @param ast the ast to check.
389      * @return list of line numbers for empty lines.
390      */
391     private List<Integer> getEmptyLines(DetailAST ast) {
392         final DetailAST lastToken = ast.getLastChild().getLastChild();
393         int lastTokenLineNo = 0;
394         if (lastToken != null) {
395             // -1 as count starts from 0
396             // -2 as last token line cannot be empty, because it is a RCURLY
397             lastTokenLineNo = lastToken.getLineNo() - 2;
398         }
399         final List<Integer> emptyLines = new ArrayList<>();
400 
401         for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
402             if (CommonUtil.isBlank(getLine(lineNo))) {
403                 emptyLines.add(lineNo);
404             }
405         }
406         return emptyLines;
407     }
408 
409     /**
410      * Get list of empty lines to log.
411      *
412      * @param emptyLines list of empty lines.
413      * @return list of empty lines to log.
414      */
415     private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) {
416         final List<Integer> emptyLinesToLog = new ArrayList<>();
417         int previousEmptyLineNo = -1;
418         for (int emptyLineNo : emptyLines) {
419             if (previousEmptyLineNo + 1 == emptyLineNo) {
420                 emptyLinesToLog.add(previousEmptyLineNo);
421             }
422             previousEmptyLineNo = emptyLineNo;
423         }
424         return emptyLinesToLog;
425     }
426 
427     /**
428      * Whether the token has not allowed multiple empty lines before.
429      *
430      * @param ast the ast to check.
431      * @return true if the token has not allowed multiple empty lines before.
432      */
433     private boolean hasMultipleLinesBefore(DetailAST ast) {
434         return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast))
435                 && hasNotAllowedTwoEmptyLinesBefore(ast);
436     }
437 
438     /**
439      * Process Package.
440      *
441      * @param ast token
442      * @param nextToken next token
443      */
444     private void processPackage(DetailAST ast, DetailAST nextToken) {
445         if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
446             if (CheckUtil.isPackageInfo(getFilePath())) {
447                 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) {
448                     log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
449                 }
450             }
451             else {
452                 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
453             }
454         }
455         if (isLineEmptyAfterPackage(ast)) {
456             final DetailAST elementAst = getViolationAstForPackage(ast);
457             log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText());
458         }
459         else if (!hasEmptyLineAfter(ast)) {
460             log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
461         }
462     }
463 
464     /**
465      * Checks if there is another element at next line of package declaration.
466      *
467      * @param ast Package ast.
468      * @return true, if there is an element.
469      */
470     private static boolean isLineEmptyAfterPackage(DetailAST ast) {
471         DetailAST nextElement = ast;
472         final int lastChildLineNo = ast.getLastChild().getLineNo();
473         while (nextElement.getLineNo() < lastChildLineNo + 1
474                 && nextElement.getNextSibling() != null) {
475             nextElement = nextElement.getNextSibling();
476         }
477         return nextElement.getLineNo() == lastChildLineNo + 1;
478     }
479 
480     /**
481      * Gets the Ast on which violation is to be given for package declaration.
482      *
483      * @param ast Package ast.
484      * @return Violation ast.
485      */
486     private static DetailAST getViolationAstForPackage(DetailAST ast) {
487         DetailAST nextElement = ast;
488         final int lastChildLineNo = ast.getLastChild().getLineNo();
489         while (nextElement.getLineNo() < lastChildLineNo + 1) {
490             nextElement = nextElement.getNextSibling();
491         }
492         return nextElement;
493     }
494 
495     /**
496      * Process Import.
497      *
498      * @param ast token
499      * @param nextToken next token
500      */
501     private void processImport(DetailAST ast, DetailAST nextToken) {
502         if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT)
503             && !hasEmptyLineAfter(ast)) {
504             log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
505         }
506     }
507 
508     /**
509      * Process Variable.
510      *
511      * @param ast token
512      * @param nextToken next Token
513      */
514     private void processVariableDef(DetailAST ast, DetailAST nextToken) {
515         if (isTypeField(ast) && !hasEmptyLineAfter(ast)
516                 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
517             log(nextToken, MSG_SHOULD_BE_SEPARATED,
518                     nextToken.getText());
519         }
520     }
521 
522     /**
523      * Checks whether token placement violates policy of empty line between fields.
524      *
525      * @param detailAST token to be analyzed
526      * @return true if policy is violated and warning should be raised; false otherwise
527      */
528     private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
529         return detailAST.getType() != TokenTypes.RCURLY
530                 && (!allowNoEmptyLineBetweenFields
531                     || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF));
532     }
533 
534     /**
535      * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
536      *
537      * @param token DetailAST token
538      * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
539      */
540     private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
541         return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
542                 && isPrePreviousLineEmpty(token);
543     }
544 
545     /**
546      * Check if group of comments located right before token has more than one previous empty line.
547      *
548      * @param token DetailAST token
549      */
550     private void checkComments(DetailAST token) {
551         if (!allowMultipleEmptyLines) {
552             if (TokenUtil.isOfType(token,
553                 TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
554                 TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) {
555                 DetailAST previousNode = token.getPreviousSibling();
556                 while (isCommentInBeginningOfLine(previousNode)) {
557                     if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) {
558                         log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText());
559                     }
560                     previousNode = previousNode.getPreviousSibling();
561                 }
562             }
563             else {
564                 checkCommentsInsideToken(token);
565             }
566         }
567     }
568 
569     /**
570      * Check if group of comments located at the start of token has more than one previous empty
571      * line.
572      *
573      * @param token DetailAST token
574      */
575     private void checkCommentsInsideToken(DetailAST token) {
576         final List<DetailAST> childNodes = new LinkedList<>();
577         DetailAST childNode = token.getLastChild();
578         while (childNode != null) {
579             if (childNode.getType() == TokenTypes.MODIFIERS) {
580                 for (DetailAST node = token.getFirstChild().getLastChild();
581                          node != null;
582                          node = node.getPreviousSibling()) {
583                     if (isCommentInBeginningOfLine(node)) {
584                         childNodes.add(node);
585                     }
586                 }
587             }
588             else if (isCommentInBeginningOfLine(childNode)) {
589                 childNodes.add(childNode);
590             }
591             childNode = childNode.getPreviousSibling();
592         }
593         for (DetailAST node : childNodes) {
594             if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) {
595                 log(node, MSG_MULTIPLE_LINES, node.getText());
596             }
597         }
598     }
599 
600     /**
601      * Checks if a token has empty pre-previous line.
602      *
603      * @param token DetailAST token.
604      * @return true, if token has empty lines before.
605      */
606     private boolean isPrePreviousLineEmpty(DetailAST token) {
607         boolean result = false;
608         final int lineNo = token.getLineNo();
609         // 3 is the number of the pre-previous line because the numbering starts from zero.
610         final int number = 3;
611         if (lineNo >= number) {
612             final String prePreviousLine = getLine(lineNo - number);
613             result = CommonUtil.isBlank(prePreviousLine);
614         }
615         return result;
616     }
617 
618     /**
619      * Checks if token have empty line after.
620      *
621      * @param token token.
622      * @return true if token have empty line after.
623      */
624     private boolean hasEmptyLineAfter(DetailAST token) {
625         DetailAST lastToken = token.getLastChild().getLastChild();
626         if (lastToken == null) {
627             lastToken = token.getLastChild();
628         }
629         DetailAST nextToken = token.getNextSibling();
630         if (TokenUtil.isCommentType(nextToken.getType())) {
631             nextToken = nextToken.getNextSibling();
632         }
633         // Start of the next token
634         final int nextBegin = nextToken.getLineNo();
635         // End of current token.
636         final int currentEnd = lastToken.getLineNo();
637         return hasEmptyLine(currentEnd + 1, nextBegin - 1);
638     }
639 
640     /**
641      * Finds comment in next sibling of given packageDef.
642      *
643      * @param packageDef token to check
644      * @return comment under the token
645      */
646     private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) {
647         return Optional.ofNullable(packageDef.getNextSibling())
648             .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS))
649             .map(DetailAST::getFirstChild)
650             .filter(token -> TokenUtil.isCommentType(token.getType()))
651             .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1);
652     }
653 
654     /**
655      * Checks, whether there are empty lines within the specified line range. Line numbering is
656      * started from 1 for parameter values
657      *
658      * @param startLine number of the first line in the range
659      * @param endLine number of the second line in the range
660      * @return {@code true} if found any blank line within the range, {@code false}
661      *         otherwise
662      */
663     private boolean hasEmptyLine(int startLine, int endLine) {
664         // Initial value is false - blank line not found
665         boolean result = false;
666         for (int line = startLine; line <= endLine; line++) {
667             // Check, if the line is blank. Lines are numbered from 0, so subtract 1
668             if (CommonUtil.isBlank(getLine(line - 1))) {
669                 result = true;
670                 break;
671             }
672         }
673         return result;
674     }
675 
676     /**
677      * Checks if a token has an empty line before.
678      *
679      * @param token token.
680      * @return true, if token have empty line before.
681      */
682     private boolean hasEmptyLineBefore(DetailAST token) {
683         boolean result = false;
684         final int lineNo = token.getLineNo();
685         if (lineNo != 1) {
686             // [lineNo - 2] is the number of the previous line as the numbering starts from zero.
687             final String lineBefore = getLine(lineNo - 2);
688             result = CommonUtil.isBlank(lineBefore);
689         }
690         return result;
691     }
692 
693     /**
694      * Check if token is comment, which starting in beginning of line.
695      *
696      * @param comment comment token for check.
697      * @return true, if token is comment, which starting in beginning of line.
698      */
699     private boolean isCommentInBeginningOfLine(DetailAST comment) {
700         // comment.getLineNo() - 1 is the number of the previous line as the numbering starts
701         // from zero.
702         boolean result = false;
703         if (comment != null) {
704             final String lineWithComment = getLine(comment.getLineNo() - 1).trim();
705             result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*");
706         }
707         return result;
708     }
709 
710     /**
711      * Check if token is preceded by javadoc comment.
712      *
713      * @param token token for check.
714      * @return true, if token is preceded by javadoc comment.
715      */
716     private static boolean isPrecededByJavadoc(DetailAST token) {
717         boolean result = false;
718         final DetailAST previous = token.getPreviousSibling();
719         if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
720                 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
721             result = true;
722         }
723         return result;
724     }
725 
726     /**
727      * If variable definition is a type field.
728      *
729      * @param variableDef variable definition.
730      * @return true variable definition is a type field.
731      */
732     private static boolean isTypeField(DetailAST variableDef) {
733         return TokenUtil.isTypeDeclaration(variableDef.getParent().getParent().getType());
734     }
735 
736 }