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