001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.indentation;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.Locale;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
032
033/**
034 * <p>
035 * Controls the indentation between comments and surrounding code.
036 * Comments are indented at the same level as the surrounding code.
037 * Detailed info about such convention can be found
038 * <a href="https://checkstyle.org/styleguides/google-java-style-20180523/javaguide.html#s4.8.6.1-block-comment-style">
039 * here</a>
040 * </p>
041 * <p>
042 * Please take a look at the following examples to understand how the check works:
043 * </p>
044 * <p>
045 * Example #1: Block comments.
046 * </p>
047 * <pre>
048 * 1   &#47;*
049 * 2    * it is Ok
050 * 3    *&#47;
051 * 4   boolean bool = true;
052 * 5
053 * 6     &#47;* violation
054 * 7      * (block comment should have the same indentation level as line 9)
055 * 8      *&#47;
056 * 9   double d = 3.14;
057 * </pre>
058 * <p>
059 * Example #2: Comment is placed at the end of the block and has previous statement.
060 * </p>
061 * <pre>
062 * 1   public void foo1() {
063 * 2     foo2();
064 * 3     // it is OK
065 * 4   }
066 * 5
067 * 6   public void foo2() {
068 * 7     foo3();
069 * 8       // violation (comment should have the same indentation level as line 7)
070 * 9   }
071 * </pre>
072 * <p>
073 * Example #3: Comment is used as a single line border to separate groups of methods.
074 * </p>
075 * <pre>
076 * 1   /////////////////////////////// it is OK
077 * 2
078 * 3   public void foo7() {
079 * 4     int a = 0;
080 * 5   }
081 * 6
082 * 7     ///////////////////////////// violation (should have the same indentation level as line 9)
083 * 8
084 * 9   public void foo8() {}
085 * </pre>
086 * <p>
087 * Example #4: Comment has distributed previous statement.
088 * </p>
089 * <pre>
090 * 1   public void foo11() {
091 * 2     CheckUtil
092 * 3       .getFirstNode(new DetailAST())
093 * 4       .getFirstChild()
094 * 5       .getNextSibling();
095 * 6     // it is OK
096 * 7   }
097 * 8
098 * 9   public void foo12() {
099 * 10    CheckUtil
100 * 11      .getFirstNode(new DetailAST())
101 * 12      .getFirstChild()
102 * 13      .getNextSibling();
103 * 14              // violation (should have the same indentation level as line 10)
104 * 15  }
105 * </pre>
106 * <p>
107 * Example #5: Single line block comment is placed within an empty code block.
108 * Note, if comment is placed at the end of the empty code block, we have
109 * Checkstyle's limitations to clearly detect user intention of explanation
110 * target - above or below. The only case we can assume as a violation is when
111 * a single line comment within the empty code block has indentation level that
112 * is lower than the indentation level of the closing right curly brace.
113 * </p>
114 * <pre>
115 * 1   public void foo46() {
116 * 2     // comment
117 * 3     // block
118 * 4     // it is OK (we cannot clearly detect user intention of explanation target)
119 * 5   }
120 * 6
121 * 7   public void foo46() {
122 * 8  // comment
123 * 9  // block
124 * 10 // violation (comment should have the same indentation level as line 11)
125 * 11  }
126 * </pre>
127 * <p>
128 * Example #6: 'fallthrough' comments and similar.
129 * </p>
130 * <pre>
131 * 0   switch(a) {
132 * 1     case "1":
133 * 2       int k = 7;
134 * 3       // it is OK
135 * 4     case "2":
136 * 5       int k = 7;
137 * 6     // it is OK
138 * 7     case "3":
139 * 8       if (true) {}
140 * 9           // violation (should have the same indentation level as line 8 or 10)
141 * 10    case "4":
142 * 11    case "5": {
143 * 12      int a;
144 * 13    }
145 * 14    // fall through (it is OK)
146 * 15    case "12": {
147 * 16      int a;
148 * 17    }
149 * 18    default:
150 * 19      // it is OK
151 * 20  }
152 * </pre>
153 * <p>
154 * Example #7: Comment is placed within a distributed statement.
155 * </p>
156 * <pre>
157 * 1   String breaks = "J"
158 * 2   // violation (comment should have the same indentation level as line 3)
159 * 3       + "A"
160 * 4       // it is OK
161 * 5       + "V"
162 * 6       + "A"
163 * 7   // it is OK
164 * 8   ;
165 * </pre>
166 * <p>
167 * Example #8: Comment is placed within an empty case block.
168 * Note, if comment is placed at the end of the empty case block, we have
169 * Checkstyle's limitations to clearly detect user intention of explanation
170 * target - above or below. The only case we can assume as a violation is when
171 * a single line comment within the empty case block has indentation level that
172 * is lower than the indentation level of the next case token.
173 * </p>
174 * <pre>
175 * 1   case 4:
176 * 2     // it is OK
177 * 3   case 5:
178 * 4  // violation (should have the same indentation level as line 3 or 5)
179 * 5   case 6:
180 * </pre>
181 * <p>
182 * Example #9: Single line block comment has previous and next statement.
183 * </p>
184 * <pre>
185 * 1   String s1 = "Clean code!";
186 * 2      s.toString().toString().toString();
187 * 3   // single line
188 * 4   // block
189 * 5   // comment (it is OK)
190 * 6   int a = 5;
191 * 7
192 * 8   String s2 = "Code complete!";
193 * 9    s.toString().toString().toString();
194 * 10            // violation (should have the same indentation level as line 11)
195 * 11       // violation (should have the same indentation level as line 12)
196 * 12     // violation (should have the same indentation level as line 13)
197 * 13  int b = 18;
198 * </pre>
199 * <p>
200 * Example #10: Comment within the block tries to describe the next code block.
201 * </p>
202 * <pre>
203 * 1   public void foo42() {
204 * 2     int a = 5;
205 * 3     if (a == 5) {
206 * 4       int b;
207 * 5       // it is OK
208 * 6      } else if (a ==6) { ... }
209 * 7   }
210 * 8
211 * 9   public void foo43() {
212 * 10    try {
213 * 11      int a;
214 * 12     // Why do we catch exception here? - violation (not the same indentation as line 11)
215 * 13     } catch (Exception e) { ... }
216 * 14  }
217 * </pre>
218 * <ul>
219 * <li>
220 * Property {@code tokens} - tokens to check
221 * Type is {@code int[]}.
222 * Default value is:
223 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SINGLE_LINE_COMMENT">
224 * SINGLE_LINE_COMMENT</a>,
225 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BLOCK_COMMENT_BEGIN">
226 * BLOCK_COMMENT_BEGIN</a>.
227 * </li>
228 * </ul>
229 * <p>
230 * To configure the Check:
231 * </p>
232 * <pre>
233 * &lt;module name=&quot;CommentsIndentation&quot;/&gt;
234 * </pre>
235 * <p>
236 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
237 * </p>
238 * <p>
239 * Violation Message Keys:
240 * </p>
241 * <ul>
242 * <li>
243 * {@code comments.indentation.block}
244 * </li>
245 * <li>
246 * {@code comments.indentation.single}
247 * </li>
248 * </ul>
249 *
250 * @since 6.10
251 */
252@StatelessCheck
253public class CommentsIndentationCheck extends AbstractCheck {
254
255    /**
256     * A key is pointing to the warning message text in "messages.properties" file.
257     */
258    public static final String MSG_KEY_SINGLE = "comments.indentation.single";
259
260    /**
261     * A key is pointing to the warning message text in "messages.properties" file.
262     */
263    public static final String MSG_KEY_BLOCK = "comments.indentation.block";
264
265    @Override
266    public int[] getDefaultTokens() {
267        return new int[] {
268            TokenTypes.SINGLE_LINE_COMMENT,
269            TokenTypes.BLOCK_COMMENT_BEGIN,
270        };
271    }
272
273    @Override
274    public int[] getAcceptableTokens() {
275        return new int[] {
276            TokenTypes.SINGLE_LINE_COMMENT,
277            TokenTypes.BLOCK_COMMENT_BEGIN,
278        };
279    }
280
281    @Override
282    public int[] getRequiredTokens() {
283        return CommonUtil.EMPTY_INT_ARRAY;
284    }
285
286    @Override
287    public boolean isCommentNodesRequired() {
288        return true;
289    }
290
291    @Override
292    public void visitToken(DetailAST commentAst) {
293        switch (commentAst.getType()) {
294            case TokenTypes.SINGLE_LINE_COMMENT:
295            case TokenTypes.BLOCK_COMMENT_BEGIN:
296                visitComment(commentAst);
297                break;
298            default:
299                final String exceptionMsg = "Unexpected token type: " + commentAst.getText();
300                throw new IllegalArgumentException(exceptionMsg);
301        }
302    }
303
304    /**
305     * Checks comment indentations over surrounding code, e.g.:
306     * <p>
307     * {@code
308     * // some comment - this is ok
309     * double d = 3.14;
310     *     // some comment - this is <b>not</b> ok.
311     * double d1 = 5.0;
312     * }
313     * </p>
314     *
315     * @param comment comment to check.
316     */
317    private void visitComment(DetailAST comment) {
318        if (!isTrailingComment(comment)) {
319            final DetailAST prevStmt = getPreviousStatement(comment);
320            final DetailAST nextStmt = getNextStmt(comment);
321
322            if (isInEmptyCaseBlock(prevStmt, nextStmt)) {
323                handleCommentInEmptyCaseBlock(prevStmt, comment, nextStmt);
324            }
325            else if (isFallThroughComment(prevStmt, nextStmt)) {
326                handleFallThroughComment(prevStmt, comment, nextStmt);
327            }
328            else if (isInEmptyCodeBlock(prevStmt, nextStmt)) {
329                handleCommentInEmptyCodeBlock(comment, nextStmt);
330            }
331            else if (isCommentAtTheEndOfTheCodeBlock(nextStmt)) {
332                handleCommentAtTheEndOfTheCodeBlock(prevStmt, comment, nextStmt);
333            }
334            else if (nextStmt != null && !areSameLevelIndented(comment, nextStmt, nextStmt)) {
335                log(comment, getMessageKey(comment), nextStmt.getLineNo(),
336                    comment.getColumnNo(), nextStmt.getColumnNo());
337            }
338        }
339    }
340
341    /**
342     * Returns the next statement of a comment.
343     *
344     * @param comment comment.
345     * @return the next statement of a comment.
346     */
347    private static DetailAST getNextStmt(DetailAST comment) {
348        DetailAST nextStmt = comment.getNextSibling();
349        while (nextStmt != null
350                && isComment(nextStmt)
351                && comment.getColumnNo() != nextStmt.getColumnNo()) {
352            nextStmt = nextStmt.getNextSibling();
353        }
354        return nextStmt;
355    }
356
357    /**
358     * Returns the previous statement of a comment.
359     *
360     * @param comment comment.
361     * @return the previous statement of a comment.
362     */
363    private DetailAST getPreviousStatement(DetailAST comment) {
364        final DetailAST prevStatement;
365        if (isDistributedPreviousStatement(comment)) {
366            prevStatement = getDistributedPreviousStatement(comment);
367        }
368        else {
369            prevStatement = getOneLinePreviousStatement(comment);
370        }
371        return prevStatement;
372    }
373
374    /**
375     * Checks whether the previous statement of a comment is distributed over two or more lines.
376     *
377     * @param comment comment to check.
378     * @return true if the previous statement of a comment is distributed over two or more lines.
379     */
380    private boolean isDistributedPreviousStatement(DetailAST comment) {
381        final DetailAST previousSibling = comment.getPreviousSibling();
382        return isDistributedExpression(comment)
383            || isDistributedReturnStatement(previousSibling)
384            || isDistributedThrowStatement(previousSibling);
385    }
386
387    /**
388     * Checks whether the previous statement of a comment is a method call chain or
389     * string concatenation statement distributed over two ore more lines.
390     *
391     * @param comment comment to check.
392     * @return true if the previous statement is a distributed expression.
393     */
394    private boolean isDistributedExpression(DetailAST comment) {
395        DetailAST previousSibling = comment.getPreviousSibling();
396        while (previousSibling != null && isComment(previousSibling)) {
397            previousSibling = previousSibling.getPreviousSibling();
398        }
399        boolean isDistributed = false;
400        if (previousSibling != null) {
401            if (previousSibling.getType() == TokenTypes.SEMI
402                    && isOnPreviousLineIgnoringComments(comment, previousSibling)) {
403                DetailAST currentToken = previousSibling.getPreviousSibling();
404                while (currentToken.getFirstChild() != null) {
405                    currentToken = currentToken.getFirstChild();
406                }
407                if (currentToken.getType() == TokenTypes.COMMENT_CONTENT) {
408                    currentToken = currentToken.getParent();
409                    while (isComment(currentToken)) {
410                        currentToken = currentToken.getNextSibling();
411                    }
412                }
413                if (!TokenUtil.areOnSameLine(previousSibling, currentToken)) {
414                    isDistributed = true;
415                }
416            }
417            else {
418                isDistributed = isStatementWithPossibleCurlies(previousSibling);
419            }
420        }
421        return isDistributed;
422    }
423
424    /**
425     * Whether the statement can have or always have curly brackets.
426     *
427     * @param previousSibling the statement to check.
428     * @return true if the statement can have or always have curly brackets.
429     */
430    private static boolean isStatementWithPossibleCurlies(DetailAST previousSibling) {
431        return previousSibling.getType() == TokenTypes.LITERAL_IF
432            || previousSibling.getType() == TokenTypes.LITERAL_TRY
433            || previousSibling.getType() == TokenTypes.LITERAL_FOR
434            || previousSibling.getType() == TokenTypes.LITERAL_DO
435            || previousSibling.getType() == TokenTypes.LITERAL_WHILE
436            || previousSibling.getType() == TokenTypes.LITERAL_SWITCH
437            || isDefinition(previousSibling);
438    }
439
440    /**
441     * Whether the statement is a kind of definition (method, class etc.).
442     *
443     * @param previousSibling the statement to check.
444     * @return true if the statement is a kind of definition.
445     */
446    private static boolean isDefinition(DetailAST previousSibling) {
447        return TokenUtil.isTypeDeclaration(previousSibling.getType())
448            || previousSibling.getType() == TokenTypes.METHOD_DEF;
449    }
450
451    /**
452     * Checks whether the previous statement of a comment is a distributed return statement.
453     *
454     * @param commentPreviousSibling previous sibling of the comment.
455     * @return true if the previous statement of a comment is a distributed return statement.
456     */
457    private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
458        boolean isDistributed = false;
459        if (commentPreviousSibling != null
460                && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
461            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
462            final DetailAST nextSibling = firstChild.getNextSibling();
463            if (nextSibling != null) {
464                isDistributed = true;
465            }
466        }
467        return isDistributed;
468    }
469
470    /**
471     * Checks whether the previous statement of a comment is a distributed throw statement.
472     *
473     * @param commentPreviousSibling previous sibling of the comment.
474     * @return true if the previous statement of a comment is a distributed throw statement.
475     */
476    private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
477        boolean isDistributed = false;
478        if (commentPreviousSibling != null
479                && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
480            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
481            final DetailAST nextSibling = firstChild.getNextSibling();
482            if (!TokenUtil.areOnSameLine(nextSibling, commentPreviousSibling)) {
483                isDistributed = true;
484            }
485        }
486        return isDistributed;
487    }
488
489    /**
490     * Returns the first token of the distributed previous statement of comment.
491     *
492     * @param comment comment to check.
493     * @return the first token of the distributed previous statement of comment.
494     */
495    private static DetailAST getDistributedPreviousStatement(DetailAST comment) {
496        DetailAST currentToken = comment.getPreviousSibling();
497        while (isComment(currentToken)) {
498            currentToken = currentToken.getPreviousSibling();
499        }
500        final DetailAST previousStatement;
501        if (currentToken.getType() == TokenTypes.SEMI) {
502            currentToken = currentToken.getPreviousSibling();
503            while (currentToken.getFirstChild() != null) {
504                currentToken = currentToken.getFirstChild();
505            }
506            previousStatement = currentToken;
507        }
508        else {
509            previousStatement = currentToken;
510        }
511        return previousStatement;
512    }
513
514    /**
515     * Checks whether case block is empty.
516     *
517     * @param nextStmt previous statement.
518     * @param prevStmt next statement.
519     * @return true if case block is empty.
520     */
521    private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
522        return prevStmt != null
523            && nextStmt != null
524            && (prevStmt.getType() == TokenTypes.LITERAL_CASE
525                || prevStmt.getType() == TokenTypes.CASE_GROUP)
526            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
527                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
528    }
529
530    /**
531     * Checks whether comment is a 'fall through' comment.
532     * For example:
533     * <p>
534     * {@code
535     *    ...
536     *    case OPTION_ONE:
537     *        int someVariable = 1;
538     *        // fall through
539     *    case OPTION_TWO:
540     *        int a = 5;
541     *        break;
542     *    ...
543     * }
544     * </p>
545     *
546     * @param prevStmt previous statement.
547     * @param nextStmt next statement.
548     * @return true if a comment is a 'fall through' comment.
549     */
550    private static boolean isFallThroughComment(DetailAST prevStmt, DetailAST nextStmt) {
551        return prevStmt != null
552            && nextStmt != null
553            && prevStmt.getType() != TokenTypes.LITERAL_CASE
554            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
555                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
556    }
557
558    /**
559     * Checks whether a comment is placed at the end of the code block.
560     *
561     * @param nextStmt next statement.
562     * @return true if a comment is placed at the end of the block.
563     */
564    private static boolean isCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
565        return nextStmt != null
566            && nextStmt.getType() == TokenTypes.RCURLY;
567    }
568
569    /**
570     * Checks whether comment is placed in the empty code block.
571     * For example:
572     * <p>
573     * ...
574     * {@code
575     *  // empty code block
576     * }
577     * ...
578     * </p>
579     * Note, the method does not treat empty case blocks.
580     *
581     * @param prevStmt previous statement.
582     * @param nextStmt next statement.
583     * @return true if comment is placed in the empty code block.
584     */
585    private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
586        return prevStmt != null
587            && nextStmt != null
588            && (prevStmt.getType() == TokenTypes.SLIST
589                || prevStmt.getType() == TokenTypes.LCURLY
590                || prevStmt.getType() == TokenTypes.ARRAY_INIT
591                || prevStmt.getType() == TokenTypes.OBJBLOCK)
592            && nextStmt.getType() == TokenTypes.RCURLY;
593    }
594
595    /**
596     * Handles a comment which is placed within empty case block.
597     * Note, if comment is placed at the end of the empty case block, we have Checkstyle's
598     * limitations to clearly detect user intention of explanation target - above or below. The
599     * only case we can assume as a violation is when a single line comment within the empty case
600     * block has indentation level that is lower than the indentation level of the next case
601     * token. For example:
602     * <p>
603     * {@code
604     *    ...
605     *    case OPTION_ONE:
606     * // violation
607     *    case OPTION_TWO:
608     *    ...
609     * }
610     * </p>
611     *
612     * @param prevStmt previous statement.
613     * @param comment single line comment.
614     * @param nextStmt next statement.
615     */
616    private void handleCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
617                                               DetailAST nextStmt) {
618        if (comment.getColumnNo() < prevStmt.getColumnNo()
619                || comment.getColumnNo() < nextStmt.getColumnNo()) {
620            logMultilineIndentation(prevStmt, comment, nextStmt);
621        }
622    }
623
624    /**
625     * Handles 'fall through' single line comment.
626     * Note, 'fall through' and similar comments can have indentation level as next or previous
627     * statement.
628     * For example:
629     * <p>
630     * {@code
631     *    ...
632     *    case OPTION_ONE:
633     *        int someVariable = 1;
634     *        // fall through - OK
635     *    case OPTION_TWO:
636     *        int a = 5;
637     *        break;
638     *    ...
639     * }
640     * </p>
641     * <p>
642     * {@code
643     *    ...
644     *    case OPTION_ONE:
645     *        int someVariable = 1;
646     *    // then init variable a - OK
647     *    case OPTION_TWO:
648     *        int a = 5;
649     *        break;
650     *    ...
651     * }
652     * </p>
653     *
654     * @param prevStmt previous statement.
655     * @param comment single line comment.
656     * @param nextStmt next statement.
657     */
658    private void handleFallThroughComment(DetailAST prevStmt, DetailAST comment,
659                                          DetailAST nextStmt) {
660        if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
661            logMultilineIndentation(prevStmt, comment, nextStmt);
662        }
663    }
664
665    /**
666     * Handles a comment which is placed at the end of non empty code block.
667     * Note, if single line comment is placed at the end of non empty block the comment should have
668     * the same indentation level as the previous statement. For example:
669     * <p>
670     * {@code
671     *    if (a == true) {
672     *        int b = 1;
673     *        // comment
674     *    }
675     * }
676     * </p>
677     *
678     * @param prevStmt previous statement.
679     * @param comment comment to check.
680     * @param nextStmt next statement.
681     */
682    private void handleCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, DetailAST comment,
683                                                     DetailAST nextStmt) {
684        if (prevStmt != null) {
685            if (prevStmt.getType() == TokenTypes.LITERAL_CASE
686                    || prevStmt.getType() == TokenTypes.CASE_GROUP
687                    || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT) {
688                if (comment.getColumnNo() < nextStmt.getColumnNo()) {
689                    log(comment, getMessageKey(comment), nextStmt.getLineNo(),
690                        comment.getColumnNo(), nextStmt.getColumnNo());
691                }
692            }
693            else if (isCommentForMultiblock(nextStmt)) {
694                if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
695                    logMultilineIndentation(prevStmt, comment, nextStmt);
696                }
697            }
698            else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
699                final int prevStmtLineNo = prevStmt.getLineNo();
700                log(comment, getMessageKey(comment), prevStmtLineNo,
701                        comment.getColumnNo(), getLineStart(prevStmtLineNo));
702            }
703        }
704    }
705
706    /**
707     * Whether the comment might have been used for the next block in a multi-block structure.
708     *
709     * @param endBlockStmt the end of the current block.
710     * @return true, if the comment might have been used for the next
711     *     block in a multi-block structure.
712     */
713    private static boolean isCommentForMultiblock(DetailAST endBlockStmt) {
714        final DetailAST nextBlock = endBlockStmt.getParent().getNextSibling();
715        final int endBlockLineNo = endBlockStmt.getLineNo();
716        final DetailAST catchAst = endBlockStmt.getParent().getParent();
717        final DetailAST finallyAst = catchAst.getNextSibling();
718        return nextBlock != null && nextBlock.getLineNo() == endBlockLineNo
719                || finallyAst != null
720                    && catchAst.getType() == TokenTypes.LITERAL_CATCH
721                    && finallyAst.getLineNo() == endBlockLineNo;
722    }
723
724    /**
725     * Handles a comment which is placed within the empty code block.
726     * Note, if comment is placed at the end of the empty code block, we have Checkstyle's
727     * limitations to clearly detect user intention of explanation target - above or below. The
728     * only case we can assume as a violation is when a single line comment within the empty
729     * code block has indentation level that is lower than the indentation level of the closing
730     * right curly brace. For example:
731     * <p>
732     * {@code
733     *    if (a == true) {
734     * // violation
735     *    }
736     * }
737     * </p>
738     *
739     * @param comment comment to check.
740     * @param nextStmt next statement.
741     */
742    private void handleCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
743        if (comment.getColumnNo() < nextStmt.getColumnNo()) {
744            log(comment, getMessageKey(comment), nextStmt.getLineNo(),
745                comment.getColumnNo(), nextStmt.getColumnNo());
746        }
747    }
748
749    /**
750     * Does pre-order traverse of abstract syntax tree to find the previous statement of the
751     * comment. If previous statement of the comment is found, then the traverse will
752     * be finished.
753     *
754     * @param comment current statement.
755     * @return previous statement of the comment or null if the comment does not have previous
756     *         statement.
757     */
758    private DetailAST getOneLinePreviousStatement(DetailAST comment) {
759        DetailAST root = comment.getParent();
760        while (root != null && !isBlockStart(root)) {
761            root = root.getParent();
762        }
763
764        final Deque<DetailAST> stack = new ArrayDeque<>();
765        DetailAST previousStatement = null;
766        while (root != null || !stack.isEmpty()) {
767            if (!stack.isEmpty()) {
768                root = stack.pop();
769            }
770            while (root != null) {
771                previousStatement = findPreviousStatement(comment, root);
772                if (previousStatement != null) {
773                    root = null;
774                    stack.clear();
775                    break;
776                }
777                if (root.getNextSibling() != null) {
778                    stack.push(root.getNextSibling());
779                }
780                root = root.getFirstChild();
781            }
782        }
783        return previousStatement;
784    }
785
786    /**
787     * Whether the ast is a comment.
788     *
789     * @param ast the ast to check.
790     * @return true if the ast is a comment.
791     */
792    private static boolean isComment(DetailAST ast) {
793        final int astType = ast.getType();
794        return astType == TokenTypes.SINGLE_LINE_COMMENT
795            || astType == TokenTypes.BLOCK_COMMENT_BEGIN
796            || astType == TokenTypes.COMMENT_CONTENT
797            || astType == TokenTypes.BLOCK_COMMENT_END;
798    }
799
800    /**
801     * Whether the AST node starts a block.
802     *
803     * @param root the AST node to check.
804     * @return true if the AST node starts a block.
805     */
806    private static boolean isBlockStart(DetailAST root) {
807        return root.getType() == TokenTypes.SLIST
808                || root.getType() == TokenTypes.OBJBLOCK
809                || root.getType() == TokenTypes.ARRAY_INIT
810                || root.getType() == TokenTypes.CASE_GROUP;
811    }
812
813    /**
814     * Finds a previous statement of the comment.
815     * Uses root token of the line while searching.
816     *
817     * @param comment comment.
818     * @param root root token of the line.
819     * @return previous statement of the comment or null if previous statement was not found.
820     */
821    private DetailAST findPreviousStatement(DetailAST comment, DetailAST root) {
822        DetailAST previousStatement = null;
823        if (root.getLineNo() >= comment.getLineNo()) {
824            // ATTENTION: parent of the comment is below the comment in case block
825            // See https://github.com/checkstyle/checkstyle/issues/851
826            previousStatement = getPrevStatementFromSwitchBlock(comment);
827        }
828        final DetailAST tokenWhichBeginsTheLine;
829        if (root.getType() == TokenTypes.EXPR
830                && root.getFirstChild().getFirstChild() != null) {
831            if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
832                tokenWhichBeginsTheLine = root.getFirstChild();
833            }
834            else {
835                tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
836            }
837        }
838        else if (root.getType() == TokenTypes.PLUS) {
839            tokenWhichBeginsTheLine = root.getFirstChild();
840        }
841        else {
842            tokenWhichBeginsTheLine = root;
843        }
844        if (tokenWhichBeginsTheLine != null
845                && !isComment(tokenWhichBeginsTheLine)
846                && isOnPreviousLineIgnoringComments(comment, tokenWhichBeginsTheLine)) {
847            previousStatement = tokenWhichBeginsTheLine;
848        }
849        return previousStatement;
850    }
851
852    /**
853     * Finds a token which begins the line.
854     *
855     * @param root root token of the line.
856     * @return token which begins the line.
857     */
858    private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
859        final DetailAST tokenWhichBeginsTheLine;
860        if (isUsingOfObjectReferenceToInvokeMethod(root)) {
861            tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
862        }
863        else {
864            tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
865        }
866        return tokenWhichBeginsTheLine;
867    }
868
869    /**
870     * Checks whether there is a use of an object reference to invoke an object's method on line.
871     *
872     * @param root root token of the line.
873     * @return true if there is a use of an object reference to invoke an object's method on line.
874     */
875    private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
876        return root.getFirstChild().getFirstChild().getFirstChild() != null
877            && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
878    }
879
880    /**
881     * Finds the start token of method call chain.
882     *
883     * @param root root token of the line.
884     * @return the start token of method call chain.
885     */
886    private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
887        DetailAST startOfMethodCallChain = root;
888        while (startOfMethodCallChain.getFirstChild() != null
889                && TokenUtil.areOnSameLine(startOfMethodCallChain.getFirstChild(), root)) {
890            startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
891        }
892        if (startOfMethodCallChain.getFirstChild() != null) {
893            startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
894        }
895        return startOfMethodCallChain;
896    }
897
898    /**
899     * Checks whether the checked statement is on the previous line ignoring empty lines
900     * and lines which contain only comments.
901     *
902     * @param currentStatement current statement.
903     * @param checkedStatement checked statement.
904     * @return true if checked statement is on the line which is previous to current statement
905     *     ignoring empty lines and lines which contain only comments.
906     */
907    private boolean isOnPreviousLineIgnoringComments(DetailAST currentStatement,
908                                                     DetailAST checkedStatement) {
909        DetailAST nextToken = getNextToken(checkedStatement);
910        int distanceAim = 1;
911        if (nextToken != null && isComment(nextToken)) {
912            distanceAim += countEmptyLines(checkedStatement, currentStatement);
913        }
914
915        while (nextToken != null && nextToken != currentStatement && isComment(nextToken)) {
916            if (nextToken.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
917                distanceAim += nextToken.getLastChild().getLineNo() - nextToken.getLineNo();
918            }
919            distanceAim++;
920            nextToken = nextToken.getNextSibling();
921        }
922        return currentStatement.getLineNo() - checkedStatement.getLineNo() == distanceAim;
923    }
924
925    /**
926     * Get the token to start counting the number of lines to add to the distance aim from.
927     *
928     * @param checkedStatement the checked statement.
929     * @return the token to start counting the number of lines to add to the distance aim from.
930     */
931    private DetailAST getNextToken(DetailAST checkedStatement) {
932        DetailAST nextToken;
933        if (checkedStatement.getType() == TokenTypes.SLIST
934                || checkedStatement.getType() == TokenTypes.ARRAY_INIT
935                || checkedStatement.getType() == TokenTypes.CASE_GROUP) {
936            nextToken = checkedStatement.getFirstChild();
937        }
938        else {
939            nextToken = checkedStatement.getNextSibling();
940        }
941        if (nextToken != null && isComment(nextToken) && isTrailingComment(nextToken)) {
942            nextToken = nextToken.getNextSibling();
943        }
944        return nextToken;
945    }
946
947    /**
948     * Count the number of empty lines between statements.
949     *
950     * @param startStatement start statement.
951     * @param endStatement end statement.
952     * @return the number of empty lines between statements.
953     */
954    private int countEmptyLines(DetailAST startStatement, DetailAST endStatement) {
955        int emptyLinesNumber = 0;
956        final String[] lines = getLines();
957        final int endLineNo = endStatement.getLineNo();
958        for (int lineNo = startStatement.getLineNo(); lineNo < endLineNo; lineNo++) {
959            if (CommonUtil.isBlank(lines[lineNo])) {
960                emptyLinesNumber++;
961            }
962        }
963        return emptyLinesNumber;
964    }
965
966    /**
967     * Logs comment which can have the same indentation level as next or previous statement.
968     *
969     * @param comment comment.
970     * @param nextStmt next statement.
971     * @param prevStmt previous statement.
972     */
973    private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
974                                         DetailAST nextStmt) {
975        final String multilineNoTemplate = "%d, %d";
976        log(comment, getMessageKey(comment),
977            String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
978                nextStmt.getLineNo()), comment.getColumnNo(),
979            String.format(Locale.getDefault(), multilineNoTemplate,
980                    getLineStart(prevStmt.getLineNo()), getLineStart(nextStmt.getLineNo())));
981    }
982
983    /**
984     * Get a message key depending on a comment type.
985     *
986     * @param comment the comment to process.
987     * @return a message key.
988     */
989    private static String getMessageKey(DetailAST comment) {
990        final String msgKey;
991        if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
992            msgKey = MSG_KEY_SINGLE;
993        }
994        else {
995            msgKey = MSG_KEY_BLOCK;
996        }
997        return msgKey;
998    }
999
1000    /**
1001     * Gets comment's previous statement from switch block.
1002     *
1003     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}.
1004     * @return comment's previous statement or null if previous statement is absent.
1005     */
1006    private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
1007        final DetailAST prevStmt;
1008        final DetailAST parentStatement = comment.getParent();
1009        if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
1010            prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
1011        }
1012        else {
1013            prevStmt = getPrevCaseToken(parentStatement);
1014        }
1015        return prevStmt;
1016    }
1017
1018    /**
1019     * Gets previous statement for comment which is placed immediately under case.
1020     *
1021     * @param parentStatement comment's parent statement.
1022     * @return comment's previous statement or null if previous statement is absent.
1023     */
1024    private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
1025        DetailAST prevStmt = null;
1026        final DetailAST prevBlock = parentStatement.getPreviousSibling();
1027        if (prevBlock.getLastChild() != null) {
1028            DetailAST blockBody = prevBlock.getLastChild().getLastChild();
1029            if (blockBody.getType() == TokenTypes.SEMI) {
1030                blockBody = blockBody.getPreviousSibling();
1031            }
1032            if (blockBody.getType() == TokenTypes.EXPR) {
1033                if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
1034                    prevStmt = findStartTokenOfMethodCallChain(blockBody);
1035                }
1036                else {
1037                    prevStmt = blockBody.getFirstChild().getFirstChild();
1038                }
1039            }
1040            else {
1041                if (blockBody.getType() == TokenTypes.SLIST) {
1042                    prevStmt = blockBody.getParent().getParent();
1043                }
1044                else {
1045                    prevStmt = blockBody;
1046                }
1047            }
1048            if (isComment(prevStmt)) {
1049                prevStmt = prevStmt.getNextSibling();
1050            }
1051        }
1052        return prevStmt;
1053    }
1054
1055    /**
1056     * Gets previous case-token for comment.
1057     *
1058     * @param parentStatement comment's parent statement.
1059     * @return previous case-token or null if previous case-token is absent.
1060     */
1061    private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
1062        final DetailAST prevCaseToken;
1063        final DetailAST parentBlock = parentStatement.getParent();
1064        if (parentBlock.getParent() != null
1065                && parentBlock.getParent().getPreviousSibling() != null
1066                && parentBlock.getParent().getPreviousSibling().getType()
1067                    == TokenTypes.LITERAL_CASE) {
1068            prevCaseToken = parentBlock.getParent().getPreviousSibling();
1069        }
1070        else {
1071            prevCaseToken = null;
1072        }
1073        return prevCaseToken;
1074    }
1075
1076    /**
1077     * Checks if comment and next code statement
1078     * (or previous code stmt like <b>case</b> in switch block) are indented at the same level,
1079     * e.g.:
1080     * <p>
1081     * <pre>
1082     * {@code
1083     * // some comment - same indentation level
1084     * int x = 10;
1085     *     // some comment - different indentation level
1086     * int x1 = 5;
1087     * /*
1088     *  *
1089     *  *&#47;
1090     *  boolean bool = true; - same indentation level
1091     * }
1092     * </pre>
1093     * </p>
1094     *
1095     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
1096     * @param prevStmt previous code statement.
1097     * @param nextStmt next code statement.
1098     * @return true if comment and next code statement are indented at the same level.
1099     */
1100    private boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
1101                                                DetailAST nextStmt) {
1102        return comment.getColumnNo() == getLineStart(nextStmt.getLineNo())
1103            || comment.getColumnNo() == getLineStart(prevStmt.getLineNo());
1104    }
1105
1106    /**
1107     * Get a column number where a code starts.
1108     *
1109     * @param lineNo the line number to get column number in.
1110     * @return the column number where a code starts.
1111     */
1112    private int getLineStart(int lineNo) {
1113        final char[] line = getLines()[lineNo - 1].toCharArray();
1114        int lineStart = 0;
1115        while (Character.isWhitespace(line[lineStart])) {
1116            lineStart++;
1117        }
1118        return lineStart;
1119    }
1120
1121    /**
1122     * Checks if current comment is a trailing comment.
1123     *
1124     * @param comment comment to check.
1125     * @return true if current comment is a trailing comment.
1126     */
1127    private boolean isTrailingComment(DetailAST comment) {
1128        final boolean isTrailingComment;
1129        if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
1130            isTrailingComment = isTrailingSingleLineComment(comment);
1131        }
1132        else {
1133            isTrailingComment = isTrailingBlockComment(comment);
1134        }
1135        return isTrailingComment;
1136    }
1137
1138    /**
1139     * Checks if current single line comment is trailing comment, e.g.:
1140     * <p>
1141     * {@code
1142     * double d = 3.14; // some comment
1143     * }
1144     * </p>
1145     *
1146     * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
1147     * @return true if current single line comment is trailing comment.
1148     */
1149    private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
1150        final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
1151        final int commentColumnNo = singleLineComment.getColumnNo();
1152        return !CommonUtil.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
1153    }
1154
1155    /**
1156     * Checks if current comment block is trailing comment, e.g.:
1157     * <p>
1158     * {@code
1159     * double d = 3.14; /* some comment *&#47;
1160     * /* some comment *&#47; double d = 18.5;
1161     * }
1162     * </p>
1163     *
1164     * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
1165     * @return true if current comment block is trailing comment.
1166     */
1167    private boolean isTrailingBlockComment(DetailAST blockComment) {
1168        final String commentLine = getLine(blockComment.getLineNo() - 1);
1169        final int commentColumnNo = blockComment.getColumnNo();
1170        final DetailAST nextSibling = blockComment.getNextSibling();
1171        return !CommonUtil.hasWhitespaceBefore(commentColumnNo, commentLine)
1172            || nextSibling != null && TokenUtil.areOnSameLine(nextSibling, blockComment);
1173    }
1174
1175}