View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.indentation;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.Locale;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
31  
32  /**
33   * This Check controls the indentation between comments and surrounding code.
34   * Comments are indented at the same level as the surrounding code.
35   * Detailed info about such convention can be found
36   * <a href=
37   * "http://checkstyle.sourceforge.net/reports/google-java-style-20170228.html#s4.8.6.1-block-comment-style">
38   * here</a>
39   * <p>
40   * Examples:
41   * </p>
42   * <p>
43   * To configure the Check:
44   * </p>
45   *
46   * <pre>
47   * {@code
48   * &lt;module name=&quot;CommentsIndentation&quot;/&gt;
49   * }
50   * {@code
51   * /*
52   *  * comment
53   *  * some comment
54   *  *&#47;
55   * boolean bool = true; - such comment indentation is ok
56   *    /*
57   *    * comment
58   *    * some comment
59   *     *&#47;
60   * double d = 3.14; - Block Comment has incorrect indentation level 7, expected 4.
61   * // some comment - comment is ok
62   * String str = "";
63   *     // some comment Comment has incorrect indentation level 8, expected 4.
64   * String str1 = "";
65   * }
66   * </pre>
67   *
68   * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
69   * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
70   */
71  @StatelessCheck
72  public class CommentsIndentationCheck extends AbstractCheck {
73  
74      /**
75       * A key is pointing to the warning message text in "messages.properties" file.
76       */
77      public static final String MSG_KEY_SINGLE = "comments.indentation.single";
78  
79      /**
80       * A key is pointing to the warning message text in "messages.properties" file.
81       */
82      public static final String MSG_KEY_BLOCK = "comments.indentation.block";
83  
84      @Override
85      public int[] getDefaultTokens() {
86          return new int[] {
87              TokenTypes.SINGLE_LINE_COMMENT,
88              TokenTypes.BLOCK_COMMENT_BEGIN,
89          };
90      }
91  
92      @Override
93      public int[] getAcceptableTokens() {
94          return new int[] {
95              TokenTypes.SINGLE_LINE_COMMENT,
96              TokenTypes.BLOCK_COMMENT_BEGIN,
97          };
98      }
99  
100     @Override
101     public int[] getRequiredTokens() {
102         return CommonUtils.EMPTY_INT_ARRAY;
103     }
104 
105     @Override
106     public boolean isCommentNodesRequired() {
107         return true;
108     }
109 
110     @Override
111     public void visitToken(DetailAST commentAst) {
112         switch (commentAst.getType()) {
113             case TokenTypes.SINGLE_LINE_COMMENT:
114             case TokenTypes.BLOCK_COMMENT_BEGIN:
115                 visitComment(commentAst);
116                 break;
117             default:
118                 final String exceptionMsg = "Unexpected token type: " + commentAst.getText();
119                 throw new IllegalArgumentException(exceptionMsg);
120         }
121     }
122 
123     /**
124      * Checks comment indentations over surrounding code, e.g.:
125      * <p>
126      * {@code
127      * // some comment - this is ok
128      * double d = 3.14;
129      *     // some comment - this is <b>not</b> ok.
130      * double d1 = 5.0;
131      * }
132      * </p>
133      * @param comment comment to check.
134      */
135     private void visitComment(DetailAST comment) {
136         if (!isTrailingComment(comment)) {
137             final DetailAST prevStmt = getPreviousStatement(comment);
138             final DetailAST nextStmt = getNextStmt(comment);
139 
140             if (isInEmptyCaseBlock(prevStmt, nextStmt)) {
141                 handleCommentInEmptyCaseBlock(prevStmt, comment, nextStmt);
142             }
143             else if (isFallThroughComment(prevStmt, nextStmt)) {
144                 handleFallThroughComment(prevStmt, comment, nextStmt);
145             }
146             else if (isInEmptyCodeBlock(prevStmt, nextStmt)) {
147                 handleCommentInEmptyCodeBlock(comment, nextStmt);
148             }
149             else if (isCommentAtTheEndOfTheCodeBlock(nextStmt)) {
150                 handleCommentAtTheEndOfTheCodeBlock(prevStmt, comment, nextStmt);
151             }
152             else if (nextStmt != null && !areSameLevelIndented(comment, nextStmt, nextStmt)) {
153                 log(comment.getLineNo(), getMessageKey(comment), nextStmt.getLineNo(),
154                     comment.getColumnNo(), nextStmt.getColumnNo());
155             }
156         }
157     }
158 
159     /**
160      * Returns the next statement of a comment.
161      * @param comment comment.
162      * @return the next statement of a comment.
163      */
164     private static DetailAST getNextStmt(DetailAST comment) {
165         DetailAST nextStmt = comment.getNextSibling();
166         while (nextStmt != null
167                 && isComment(nextStmt)
168                 && comment.getColumnNo() != nextStmt.getColumnNo()) {
169             nextStmt = nextStmt.getNextSibling();
170         }
171         return nextStmt;
172     }
173 
174     /**
175      * Returns the previous statement of a comment.
176      * @param comment comment.
177      * @return the previous statement of a comment.
178      */
179     private DetailAST getPreviousStatement(DetailAST comment) {
180         final DetailAST prevStatement;
181         if (isDistributedPreviousStatement(comment)) {
182             prevStatement = getDistributedPreviousStatement(comment);
183         }
184         else {
185             prevStatement = getOneLinePreviousStatement(comment);
186         }
187         return prevStatement;
188     }
189 
190     /**
191      * Checks whether the previous statement of a comment is distributed over two or more lines.
192      * @param comment comment to check.
193      * @return true if the previous statement of a comment is distributed over two or more lines.
194      */
195     private boolean isDistributedPreviousStatement(DetailAST comment) {
196         final DetailAST previousSibling = comment.getPreviousSibling();
197         return isDistributedExpression(comment)
198             || isDistributedReturnStatement(previousSibling)
199             || isDistributedThrowStatement(previousSibling);
200     }
201 
202     /**
203      * Checks whether the previous statement of a comment is a method call chain or
204      * string concatenation statement distributed over two ore more lines.
205      * @param comment comment to check.
206      * @return true if the previous statement is a distributed expression.
207      */
208     private boolean isDistributedExpression(DetailAST comment) {
209         DetailAST previousSibling = comment.getPreviousSibling();
210         while (previousSibling != null && isComment(previousSibling)) {
211             previousSibling = previousSibling.getPreviousSibling();
212         }
213         boolean isDistributed = false;
214         if (previousSibling != null) {
215             if (previousSibling.getType() == TokenTypes.SEMI
216                     && isOnPreviousLineIgnoringComments(comment, previousSibling)) {
217                 DetailAST currentToken = previousSibling.getPreviousSibling();
218                 while (currentToken.getFirstChild() != null) {
219                     currentToken = currentToken.getFirstChild();
220                 }
221                 if (currentToken.getType() == TokenTypes.COMMENT_CONTENT) {
222                     currentToken = currentToken.getParent();
223                     while (isComment(currentToken)) {
224                         currentToken = currentToken.getNextSibling();
225                     }
226                 }
227                 if (previousSibling.getLineNo() != currentToken.getLineNo()) {
228                     isDistributed = true;
229                 }
230             }
231             else {
232                 isDistributed = isStatementWithPossibleCurlies(previousSibling);
233             }
234         }
235         return isDistributed;
236     }
237 
238     /**
239      * Whether the statement can have or always have curly brackets.
240      * @param previousSibling the statement to check.
241      * @return true if the statement can have or always have curly brackets.
242      */
243     private static boolean isStatementWithPossibleCurlies(DetailAST previousSibling) {
244         return previousSibling.getType() == TokenTypes.LITERAL_IF
245             || previousSibling.getType() == TokenTypes.LITERAL_TRY
246             || previousSibling.getType() == TokenTypes.LITERAL_FOR
247             || previousSibling.getType() == TokenTypes.LITERAL_DO
248             || previousSibling.getType() == TokenTypes.LITERAL_WHILE
249             || previousSibling.getType() == TokenTypes.LITERAL_SWITCH
250             || isDefinition(previousSibling);
251     }
252 
253     /**
254      * Whether the statement is a kind of definition (method, class etc.).
255      * @param previousSibling the statement to check.
256      * @return true if the statement is a kind of definition.
257      */
258     private static boolean isDefinition(DetailAST previousSibling) {
259         return previousSibling.getType() == TokenTypes.METHOD_DEF
260             || previousSibling.getType() == TokenTypes.CLASS_DEF
261             || previousSibling.getType() == TokenTypes.INTERFACE_DEF
262             || previousSibling.getType() == TokenTypes.ENUM_DEF
263             || previousSibling.getType() == TokenTypes.ANNOTATION_DEF;
264     }
265 
266     /**
267      * Checks whether the previous statement of a comment is a distributed return statement.
268      * @param commentPreviousSibling previous sibling of the comment.
269      * @return true if the previous statement of a comment is a distributed return statement.
270      */
271     private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
272         boolean isDistributed = false;
273         if (commentPreviousSibling != null
274                 && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
275             final DetailAST firstChild = commentPreviousSibling.getFirstChild();
276             final DetailAST nextSibling = firstChild.getNextSibling();
277             if (nextSibling != null) {
278                 isDistributed = true;
279             }
280         }
281         return isDistributed;
282     }
283 
284     /**
285      * Checks whether the previous statement of a comment is a distributed throw statement.
286      * @param commentPreviousSibling previous sibling of the comment.
287      * @return true if the previous statement of a comment is a distributed throw statement.
288      */
289     private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
290         boolean isDistributed = false;
291         if (commentPreviousSibling != null
292                 && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
293             final DetailAST firstChild = commentPreviousSibling.getFirstChild();
294             final DetailAST nextSibling = firstChild.getNextSibling();
295             if (nextSibling.getLineNo() != commentPreviousSibling.getLineNo()) {
296                 isDistributed = true;
297             }
298         }
299         return isDistributed;
300     }
301 
302     /**
303      * Returns the first token of the distributed previous statement of comment.
304      * @param comment comment to check.
305      * @return the first token of the distributed previous statement of comment.
306      */
307     private static DetailAST getDistributedPreviousStatement(DetailAST comment) {
308         DetailAST currentToken = comment.getPreviousSibling();
309         while (isComment(currentToken)) {
310             currentToken = currentToken.getPreviousSibling();
311         }
312         final DetailAST previousStatement;
313         if (currentToken.getType() == TokenTypes.SEMI) {
314             currentToken = currentToken.getPreviousSibling();
315             while (currentToken.getFirstChild() != null) {
316                 currentToken = currentToken.getFirstChild();
317             }
318             previousStatement = currentToken;
319         }
320         else {
321             previousStatement = currentToken;
322         }
323         return previousStatement;
324     }
325 
326     /**
327      * Checks whether case block is empty.
328      * @param nextStmt previous statement.
329      * @param prevStmt next statement.
330      * @return true if case block is empty.
331      */
332     private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
333         return prevStmt != null
334             && nextStmt != null
335             && (prevStmt.getType() == TokenTypes.LITERAL_CASE
336                 || prevStmt.getType() == TokenTypes.CASE_GROUP)
337             && (nextStmt.getType() == TokenTypes.LITERAL_CASE
338                 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
339     }
340 
341     /**
342      * Checks whether comment is a 'fall through' comment.
343      * For example:
344      * <p>
345      * {@code
346      *    ...
347      *    case OPTION_ONE:
348      *        int someVariable = 1;
349      *        // fall through
350      *    case OPTION_TWO:
351      *        int a = 5;
352      *        break;
353      *    ...
354      * }
355      * </p>
356      * @param prevStmt previous statement.
357      * @param nextStmt next statement.
358      * @return true if a comment is a 'fall through' comment.
359      */
360     private static boolean isFallThroughComment(DetailAST prevStmt, DetailAST nextStmt) {
361         return prevStmt != null
362             && nextStmt != null
363             && prevStmt.getType() != TokenTypes.LITERAL_CASE
364             && (nextStmt.getType() == TokenTypes.LITERAL_CASE
365                 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
366     }
367 
368     /**
369      * Checks whether a comment is placed at the end of the code block.
370      * @param nextStmt next statement.
371      * @return true if a comment is placed at the end of the block.
372      */
373     private static boolean isCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
374         return nextStmt != null
375             && nextStmt.getType() == TokenTypes.RCURLY;
376     }
377 
378     /**
379      * Checks whether comment is placed in the empty code block.
380      * For example:
381      * <p>
382      * ...
383      * {@code
384      *  // empty code block
385      * }
386      * ...
387      * </p>
388      * Note, the method does not treat empty case blocks.
389      * @param prevStmt previous statement.
390      * @param nextStmt next statement.
391      * @return true if comment is placed in the empty code block.
392      */
393     private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
394         return prevStmt != null
395             && nextStmt != null
396             && (prevStmt.getType() == TokenTypes.SLIST
397                 || prevStmt.getType() == TokenTypes.LCURLY
398                 || prevStmt.getType() == TokenTypes.ARRAY_INIT
399                 || prevStmt.getType() == TokenTypes.OBJBLOCK)
400             && nextStmt.getType() == TokenTypes.RCURLY;
401     }
402 
403     /**
404      * Handles a comment which is placed within empty case block.
405      * Note, if comment is placed at the end of the empty case block, we have Checkstyle's
406      * limitations to clearly detect user intention of explanation target - above or below. The
407      * only case we can assume as a violation is when a single line comment within the empty case
408      * block has indentation level that is lower than the indentation level of the next case
409      * token. For example:
410      * <p>
411      * {@code
412      *    ...
413      *    case OPTION_ONE:
414      * // violation
415      *    case OPTION_TWO:
416      *    ...
417      * }
418      * </p>
419      * @param prevStmt previous statement.
420      * @param comment single line comment.
421      * @param nextStmt next statement.
422      */
423     private void handleCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
424                                                DetailAST nextStmt) {
425 
426         if (comment.getColumnNo() < prevStmt.getColumnNo()
427                 || comment.getColumnNo() < nextStmt.getColumnNo()) {
428             logMultilineIndentation(prevStmt, comment, nextStmt);
429         }
430     }
431 
432     /**
433      * Handles 'fall through' single line comment.
434      * Note, 'fall through' and similar comments can have indentation level as next or previous
435      * statement.
436      * For example:
437      * <p>
438      * {@code
439      *    ...
440      *    case OPTION_ONE:
441      *        int someVariable = 1;
442      *        // fall through - OK
443      *    case OPTION_TWO:
444      *        int a = 5;
445      *        break;
446      *    ...
447      * }
448      * </p>
449      * <p>
450      * {@code
451      *    ...
452      *    case OPTION_ONE:
453      *        int someVariable = 1;
454      *    // than init variable a - OK
455      *    case OPTION_TWO:
456      *        int a = 5;
457      *        break;
458      *    ...
459      * }
460      * </p>
461      * @param prevStmt previous statement.
462      * @param comment single line comment.
463      * @param nextStmt next statement.
464      */
465     private void handleFallThroughComment(DetailAST prevStmt, DetailAST comment,
466                                           DetailAST nextStmt) {
467 
468         if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
469             logMultilineIndentation(prevStmt, comment, nextStmt);
470         }
471     }
472 
473     /**
474      * Handles a comment which is placed at the end of non empty code block.
475      * Note, if single line comment is placed at the end of non empty block the comment should have
476      * the same indentation level as the previous statement. For example:
477      * <p>
478      * {@code
479      *    if (a == true) {
480      *        int b = 1;
481      *        // comment
482      *    }
483      * }
484      * </p>
485      * @param prevStmt previous statement.
486      * @param comment comment to check.
487      * @param nextStmt next statement.
488      */
489     private void handleCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, DetailAST comment,
490                                                      DetailAST nextStmt) {
491         if (prevStmt != null) {
492             if (prevStmt.getType() == TokenTypes.LITERAL_CASE
493                     || prevStmt.getType() == TokenTypes.CASE_GROUP
494                     || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT) {
495                 if (comment.getColumnNo() < nextStmt.getColumnNo()) {
496                     log(comment.getLineNo(), getMessageKey(comment), nextStmt.getLineNo(),
497                         comment.getColumnNo(), nextStmt.getColumnNo());
498                 }
499             }
500             else if (isCommentForMultiblock(nextStmt)) {
501                 if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
502                     logMultilineIndentation(prevStmt, comment, nextStmt);
503                 }
504             }
505             else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
506                 final int prevStmtLineNo = prevStmt.getLineNo();
507                 log(comment.getLineNo(), getMessageKey(comment), prevStmtLineNo,
508                         comment.getColumnNo(), getLineStart(prevStmtLineNo));
509             }
510         }
511 
512     }
513 
514     /**
515      * Whether the comment might have been used for the next block in a multi-block structure.
516      * @param endBlockStmt the end of the current block.
517      * @return true, if the comment might have been used for the next
518      *     block in a multi-block structure.
519      */
520     private static boolean isCommentForMultiblock(DetailAST endBlockStmt) {
521         final DetailAST nextBlock = endBlockStmt.getParent().getNextSibling();
522         final int endBlockLineNo = endBlockStmt.getLineNo();
523         final DetailAST catchAst = endBlockStmt.getParent().getParent();
524         final DetailAST finallyAst = catchAst.getNextSibling();
525         return nextBlock != null && nextBlock.getLineNo() == endBlockLineNo
526                 || finallyAst != null
527                     && catchAst.getType() == TokenTypes.LITERAL_CATCH
528                     && finallyAst.getLineNo() == endBlockLineNo;
529     }
530 
531     /**
532      * Handles a comment which is placed within the empty code block.
533      * Note, if comment is placed at the end of the empty code block, we have Checkstyle's
534      * limitations to clearly detect user intention of explanation target - above or below. The
535      * only case we can assume as a violation is when a single line comment within the empty
536      * code block has indentation level that is lower than the indentation level of the closing
537      * right curly brace. For example:
538      * <p>
539      * {@code
540      *    if (a == true) {
541      * // violation
542      *    }
543      * }
544      * </p>
545      *
546      * @param comment comment to check.
547      * @param nextStmt next statement.
548      */
549     private void handleCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
550         if (comment.getColumnNo() < nextStmt.getColumnNo()) {
551             log(comment.getLineNo(), getMessageKey(comment), nextStmt.getLineNo(),
552                 comment.getColumnNo(), nextStmt.getColumnNo());
553         }
554     }
555 
556     /**
557      * Does pre-order traverse of abstract syntax tree to find the previous statement of the
558      * comment. If previous statement of the comment is found, then the traverse will
559      * be finished.
560      * @param comment current statement.
561      * @return previous statement of the comment or null if the comment does not have previous
562      *         statement.
563      */
564     private DetailAST getOneLinePreviousStatement(DetailAST comment) {
565         DetailAST root = comment.getParent();
566         while (root != null && !isBlockStart(root)) {
567             root = root.getParent();
568         }
569 
570         final Deque<DetailAST> stack = new ArrayDeque<>();
571         DetailAST previousStatement = null;
572         while (root != null || !stack.isEmpty()) {
573             if (!stack.isEmpty()) {
574                 root = stack.pop();
575             }
576             while (root != null) {
577                 previousStatement = findPreviousStatement(comment, root);
578                 if (previousStatement != null) {
579                     root = null;
580                     stack.clear();
581                     break;
582                 }
583                 if (root.getNextSibling() != null) {
584                     stack.push(root.getNextSibling());
585                 }
586                 root = root.getFirstChild();
587             }
588         }
589         return previousStatement;
590     }
591 
592     /**
593      * Whether the ast is a comment.
594      * @param ast the ast to check.
595      * @return true if the ast is a comment.
596      */
597     private static boolean isComment(DetailAST ast) {
598         final int astType = ast.getType();
599         return astType == TokenTypes.SINGLE_LINE_COMMENT
600             || astType == TokenTypes.BLOCK_COMMENT_BEGIN
601             || astType == TokenTypes.COMMENT_CONTENT
602             || astType == TokenTypes.BLOCK_COMMENT_END;
603     }
604 
605     /**
606      * Whether the AST node starts a block.
607      * @param root the AST node to check.
608      * @return true if the AST node starts a block.
609      */
610     private static boolean isBlockStart(DetailAST root) {
611         return root.getType() == TokenTypes.SLIST
612                 || root.getType() == TokenTypes.OBJBLOCK
613                 || root.getType() == TokenTypes.ARRAY_INIT
614                 || root.getType() == TokenTypes.CASE_GROUP;
615     }
616 
617     /**
618      * Finds a previous statement of the comment.
619      * Uses root token of the line while searching.
620      * @param comment comment.
621      * @param root root token of the line.
622      * @return previous statement of the comment or null if previous statement was not found.
623      */
624     private DetailAST findPreviousStatement(DetailAST comment, DetailAST root) {
625         DetailAST previousStatement = null;
626         if (root.getLineNo() >= comment.getLineNo()) {
627             // ATTENTION: parent of the comment is below the comment in case block
628             // See https://github.com/checkstyle/checkstyle/issues/851
629             previousStatement = getPrevStatementFromSwitchBlock(comment);
630         }
631         final DetailAST tokenWhichBeginsTheLine;
632         if (root.getType() == TokenTypes.EXPR
633                 && root.getFirstChild().getFirstChild() != null) {
634             if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
635                 tokenWhichBeginsTheLine = root.getFirstChild();
636             }
637             else {
638                 tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
639             }
640         }
641         else if (root.getType() == TokenTypes.PLUS) {
642             tokenWhichBeginsTheLine = root.getFirstChild();
643         }
644         else {
645             tokenWhichBeginsTheLine = root;
646         }
647         if (tokenWhichBeginsTheLine != null
648                 && !isComment(tokenWhichBeginsTheLine)
649                 && isOnPreviousLineIgnoringComments(comment, tokenWhichBeginsTheLine)) {
650             previousStatement = tokenWhichBeginsTheLine;
651         }
652         return previousStatement;
653     }
654 
655     /**
656      * Finds a token which begins the line.
657      * @param root root token of the line.
658      * @return token which begins the line.
659      */
660     private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
661         final DetailAST tokenWhichBeginsTheLine;
662         if (isUsingOfObjectReferenceToInvokeMethod(root)) {
663             tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
664         }
665         else {
666             tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
667         }
668         return tokenWhichBeginsTheLine;
669     }
670 
671     /**
672      * Checks whether there is a use of an object reference to invoke an object's method on line.
673      * @param root root token of the line.
674      * @return true if there is a use of an object reference to invoke an object's method on line.
675      */
676     private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
677         return root.getFirstChild().getFirstChild().getFirstChild() != null
678             && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
679     }
680 
681     /**
682      * Finds the start token of method call chain.
683      * @param root root token of the line.
684      * @return the start token of method call chain.
685      */
686     private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
687         DetailAST startOfMethodCallChain = root;
688         while (startOfMethodCallChain.getFirstChild() != null
689                 && startOfMethodCallChain.getFirstChild().getLineNo() == root.getLineNo()) {
690             startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
691         }
692         if (startOfMethodCallChain.getFirstChild() != null) {
693             startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
694         }
695         return startOfMethodCallChain;
696     }
697 
698     /**
699      * Checks whether the checked statement is on the previous line ignoring empty lines
700      * and lines which contain only comments.
701      * @param currentStatement current statement.
702      * @param checkedStatement checked statement.
703      * @return true if checked statement is on the line which is previous to current statement
704      *     ignoring empty lines and lines which contain only comments.
705      */
706     private boolean isOnPreviousLineIgnoringComments(DetailAST currentStatement,
707                                                      DetailAST checkedStatement) {
708         DetailAST nextToken = getNextToken(checkedStatement);
709         int distanceAim = 1;
710         if (nextToken != null && isComment(nextToken)) {
711             distanceAim += countEmptyLines(checkedStatement, currentStatement);
712         }
713 
714         while (nextToken != null && nextToken != currentStatement && isComment(nextToken)) {
715             if (nextToken.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
716                 distanceAim += nextToken.getLastChild().getLineNo() - nextToken.getLineNo();
717             }
718             distanceAim++;
719             nextToken = nextToken.getNextSibling();
720         }
721         return currentStatement.getLineNo() - checkedStatement.getLineNo() == distanceAim;
722     }
723 
724     /**
725      * Get the token to start counting the number of lines to add to the distance aim from.
726      * @param checkedStatement the checked statement.
727      * @return the token to start counting the number of lines to add to the distance aim from.
728      */
729     private DetailAST getNextToken(DetailAST checkedStatement) {
730         DetailAST nextToken;
731         if (checkedStatement.getType() == TokenTypes.SLIST
732                 || checkedStatement.getType() == TokenTypes.ARRAY_INIT
733                 || checkedStatement.getType() == TokenTypes.CASE_GROUP) {
734             nextToken = checkedStatement.getFirstChild();
735         }
736         else {
737             nextToken = checkedStatement.getNextSibling();
738         }
739         if (nextToken != null && isComment(nextToken) && isTrailingComment(nextToken)) {
740             nextToken = nextToken.getNextSibling();
741         }
742         return nextToken;
743     }
744 
745     /**
746      * Count the number of empty lines between statements.
747      * @param startStatement start statement.
748      * @param endStatement end statement.
749      * @return the number of empty lines between statements.
750      */
751     private int countEmptyLines(DetailAST startStatement, DetailAST endStatement) {
752         int emptyLinesNumber = 0;
753         final String[] lines = getLines();
754         final int endLineNo = endStatement.getLineNo();
755         for (int lineNo = startStatement.getLineNo(); lineNo < endLineNo; lineNo++) {
756             if (CommonUtils.isBlank(lines[lineNo])) {
757                 emptyLinesNumber++;
758             }
759         }
760         return emptyLinesNumber;
761     }
762 
763     /**
764      * Logs comment which can have the same indentation level as next or previous statement.
765      * @param comment comment.
766      * @param nextStmt next statement.
767      * @param prevStmt previous statement.
768      */
769     private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
770                                          DetailAST nextStmt) {
771         final String multilineNoTemplate = "%d, %d";
772         log(comment.getLineNo(), getMessageKey(comment),
773             String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
774                 nextStmt.getLineNo()), comment.getColumnNo(),
775             String.format(Locale.getDefault(), multilineNoTemplate,
776                     getLineStart(prevStmt.getLineNo()), getLineStart(nextStmt.getLineNo())));
777     }
778 
779     /**
780      * Get a message key depending on a comment type.
781      * @param comment the comment to process.
782      * @return a message key.
783      */
784     private static String getMessageKey(DetailAST comment) {
785         final String msgKey;
786         if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
787             msgKey = MSG_KEY_SINGLE;
788         }
789         else {
790             msgKey = MSG_KEY_BLOCK;
791         }
792         return msgKey;
793     }
794 
795     /**
796      * Gets comment's previous statement from switch block.
797      * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}.
798      * @return comment's previous statement or null if previous statement is absent.
799      */
800     private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
801         final DetailAST prevStmt;
802         final DetailAST parentStatement = comment.getParent();
803         if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
804             prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
805         }
806         else {
807             prevStmt = getPrevCaseToken(parentStatement);
808         }
809         return prevStmt;
810     }
811 
812     /**
813      * Gets previous statement for comment which is placed immediately under case.
814      * @param parentStatement comment's parent statement.
815      * @return comment's previous statement or null if previous statement is absent.
816      */
817     private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
818         DetailAST prevStmt = null;
819         final DetailAST prevBlock = parentStatement.getPreviousSibling();
820         if (prevBlock.getLastChild() != null) {
821             DetailAST blockBody = prevBlock.getLastChild().getLastChild();
822             if (blockBody.getType() == TokenTypes.SEMI) {
823                 blockBody = blockBody.getPreviousSibling();
824             }
825             if (blockBody.getType() == TokenTypes.EXPR) {
826                 if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
827                     prevStmt = findStartTokenOfMethodCallChain(blockBody);
828                 }
829                 else {
830                     prevStmt = blockBody.getFirstChild().getFirstChild();
831                 }
832             }
833             else {
834                 if (blockBody.getType() == TokenTypes.SLIST) {
835                     prevStmt = blockBody.getParent().getParent();
836                 }
837                 else {
838                     prevStmt = blockBody;
839                 }
840             }
841             if (isComment(prevStmt)) {
842                 prevStmt = prevStmt.getNextSibling();
843             }
844         }
845         return prevStmt;
846     }
847 
848     /**
849      * Gets previous case-token for comment.
850      * @param parentStatement comment's parent statement.
851      * @return previous case-token or null if previous case-token is absent.
852      */
853     private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
854         final DetailAST prevCaseToken;
855         final DetailAST parentBlock = parentStatement.getParent();
856         if (parentBlock.getParent() != null
857                 && parentBlock.getParent().getPreviousSibling() != null
858                 && parentBlock.getParent().getPreviousSibling().getType()
859                     == TokenTypes.LITERAL_CASE) {
860             prevCaseToken = parentBlock.getParent().getPreviousSibling();
861         }
862         else {
863             prevCaseToken = null;
864         }
865         return prevCaseToken;
866     }
867 
868     /**
869      * Checks if comment and next code statement
870      * (or previous code stmt like <b>case</b> in switch block) are indented at the same level,
871      * e.g.:
872      * <p>
873      * <pre>
874      * {@code
875      * // some comment - same indentation level
876      * int x = 10;
877      *     // some comment - different indentation level
878      * int x1 = 5;
879      * /*
880      *  *
881      *  *&#47;
882      *  boolean bool = true; - same indentation level
883      * }
884      * </pre>
885      * </p>
886      * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
887      * @param prevStmt previous code statement.
888      * @param nextStmt next code statement.
889      * @return true if comment and next code statement are indented at the same level.
890      */
891     private boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
892                                                 DetailAST nextStmt) {
893 
894         return comment.getColumnNo() == getLineStart(nextStmt.getLineNo())
895             || comment.getColumnNo() == getLineStart(prevStmt.getLineNo());
896     }
897 
898     /**
899      * Get a column number where a code starts.
900      * @param lineNo the line number to get column number in.
901      * @return the column number where a code starts.
902      */
903     private int getLineStart(int lineNo) {
904         final char[] line = getLines()[lineNo - 1].toCharArray();
905         int lineStart = 0;
906         while (Character.isWhitespace(line[lineStart])) {
907             lineStart++;
908         }
909         return lineStart;
910     }
911 
912     /**
913      * Checks if current comment is a trailing comment.
914      * @param comment comment to check.
915      * @return true if current comment is a trailing comment.
916      */
917     private boolean isTrailingComment(DetailAST comment) {
918         final boolean isTrailingComment;
919         if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
920             isTrailingComment = isTrailingSingleLineComment(comment);
921         }
922         else {
923             isTrailingComment = isTrailingBlockComment(comment);
924         }
925         return isTrailingComment;
926     }
927 
928     /**
929      * Checks if current single line comment is trailing comment, e.g.:
930      * <p>
931      * {@code
932      * double d = 3.14; // some comment
933      * }
934      * </p>
935      * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
936      * @return true if current single line comment is trailing comment.
937      */
938     private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
939         final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
940         final int commentColumnNo = singleLineComment.getColumnNo();
941         return !CommonUtils.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
942     }
943 
944     /**
945      * Checks if current comment block is trailing comment, e.g.:
946      * <p>
947      * {@code
948      * double d = 3.14; /* some comment *&#47;
949      * /* some comment *&#47; double d = 18.5;
950      * }
951      * </p>
952      * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
953      * @return true if current comment block is trailing comment.
954      */
955     private boolean isTrailingBlockComment(DetailAST blockComment) {
956         final String commentLine = getLine(blockComment.getLineNo() - 1);
957         final int commentColumnNo = blockComment.getColumnNo();
958         final DetailAST nextSibling = blockComment.getNextSibling();
959         return !CommonUtils.hasWhitespaceBefore(commentColumnNo, commentLine)
960             || nextSibling != null && nextSibling.getLineNo() == blockComment.getLineNo();
961     }
962 }