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