001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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 org.apache.commons.lang3.ArrayUtils;
027
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
032
033/**
034 * This Check 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=
038 * "http://checkstyle.sourceforge.net/reports/google-java-style.html#s4.8.6.1-block-comment-style">
039 * here</a>
040 * <p>
041 * Examples:
042 * </p>
043 * <p>
044 * To configure the Check:
045 * </p>
046 *
047 * <pre>
048 * {@code
049 * &lt;module name=&quot;CommentsIndentation&quot;/&gt;
050 * }
051 * {@code
052 * /*
053 *  * comment
054 *  * some comment
055 *  *&#47;
056 * boolean bool = true; - such comment indentation is ok
057 *    /*
058 *    * comment
059 *    * some comment
060 *     *&#47;
061 * double d = 3.14; - Block Comment has incorrect indentation level 7, expected 4.
062 * // some comment - comment is ok
063 * String str = "";
064 *     // some comment Comment has incorrect indentation level 8, expected 4.
065 * String str1 = "";
066 * }
067 * </pre>
068 *
069 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
070 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
071 */
072public class CommentsIndentationCheck extends AbstractCheck {
073
074    /**
075     * A key is pointing to the warning message text in "messages.properties" file.
076     */
077    public static final String MSG_KEY_SINGLE = "comments.indentation.single";
078
079    /**
080     * A key is pointing to the warning message text in "messages.properties" file.
081     */
082    public static final String MSG_KEY_BLOCK = "comments.indentation.block";
083
084    @Override
085    public int[] getDefaultTokens() {
086        return new int[] {
087            TokenTypes.SINGLE_LINE_COMMENT,
088            TokenTypes.BLOCK_COMMENT_BEGIN,
089        };
090    }
091
092    @Override
093    public int[] getAcceptableTokens() {
094        return new int[] {
095            TokenTypes.SINGLE_LINE_COMMENT,
096            TokenTypes.BLOCK_COMMENT_BEGIN,
097        };
098    }
099
100    @Override
101    public int[] getRequiredTokens() {
102        return ArrayUtils.EMPTY_INT_ARRAY;
103    }
104
105    @Override
106    public boolean isCommentNodesRequired() {
107        return true;
108    }
109
110    @Override
111    public void visitToken(DetailAST commentAst) {
112        switch (commentAst.getType()) {
113            case TokenTypes.SINGLE_LINE_COMMENT:
114                visitSingleLineComment(commentAst);
115                break;
116            case TokenTypes.BLOCK_COMMENT_BEGIN:
117                visitBlockComment(commentAst);
118                break;
119            default:
120                final String exceptionMsg = "Unexpected token type: " + commentAst.getText();
121                throw new IllegalArgumentException(exceptionMsg);
122        }
123    }
124
125    /**
126     * Checks single line comment indentations over surrounding code, e.g.:
127     * <p>
128     * {@code
129     * // some comment - this is ok
130     * double d = 3.14;
131     *     // some comment - this is <b>not</b> ok.
132     * double d1 = 5.0;
133     * }
134     * </p>
135     * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
136     */
137    private void visitSingleLineComment(DetailAST singleLineComment) {
138        final DetailAST prevStmt = getPreviousStatementOfSingleLineComment(singleLineComment);
139        final DetailAST nextStmt = singleLineComment.getNextSibling();
140
141        if (!isTrailingSingleLineComment(singleLineComment)) {
142            if (isInEmptyCaseBlock(prevStmt, nextStmt)) {
143                handleSingleLineCommentInEmptyCaseBlock(prevStmt, singleLineComment,
144                    nextStmt);
145            }
146            else if (isFallThroughSingleLineComment(prevStmt, nextStmt)) {
147                handleFallThroughtSingleLineComment(prevStmt, singleLineComment,
148                    nextStmt);
149            }
150            else if (isInEmptyCodeBlock(prevStmt, nextStmt)) {
151                handleSingleLineCommentInEmptyCodeBlock(singleLineComment, nextStmt);
152            }
153            else if (isSingleLineCommentAtTheEndOfTheCodeBlock(nextStmt)) {
154                handleSIngleLineCommentAtTheEndOfTheCodeBlock(prevStmt, singleLineComment,
155                    nextStmt);
156            }
157            else if (nextStmt != null
158                        && !areSameLevelIndented(singleLineComment, nextStmt, nextStmt)) {
159                log(singleLineComment.getLineNo(), MSG_KEY_SINGLE, nextStmt.getLineNo(),
160                    singleLineComment.getColumnNo(), nextStmt.getColumnNo());
161            }
162        }
163    }
164
165    /**
166     * Returns the previous statement of a single line comment.
167     * @param comment single line comment.
168     * @return the previous statement of a single line comment.
169     */
170    private static DetailAST getPreviousStatementOfSingleLineComment(DetailAST comment) {
171        final DetailAST prevStatement;
172        if (isDistributedPreviousStatement(comment)) {
173            prevStatement = getDistributedPreviousStatementOfSingleLineComment(comment);
174        }
175        else {
176            prevStatement = getOneLinePreviousStatementOfSingleLineComment(comment);
177        }
178        return prevStatement;
179    }
180
181    /**
182     * Checks whether the previous statement of a single line comment is distributed over two or
183     * more lines.
184     * @param singleLineComment single line comment.
185     * @return true if the previous statement of a single line comment is distributed over two or
186     *         more lines.
187     */
188    private static boolean isDistributedPreviousStatement(DetailAST singleLineComment) {
189        final DetailAST previousSibling = singleLineComment.getPreviousSibling();
190        return isDistributedMethodChainOrConcatenationStatement(singleLineComment, previousSibling)
191            || isDistributedReturnStatement(previousSibling)
192            || isDistributedThrowStatement(previousSibling);
193    }
194
195    /**
196     * Checks whether the previous statement of a single line comment is a method call chain or
197     * string concatenation statemen distributed over two ore more lines.
198     * @param comment single line comment.
199     * @param commentPreviousSibling previous sibling of the sinle line comment.
200     * @return if the previous statement of a single line comment is a method call chain or
201     *         string concatenation statemen distributed over two ore more lines.
202     */
203    private static boolean isDistributedMethodChainOrConcatenationStatement(
204        DetailAST comment, DetailAST commentPreviousSibling) {
205        boolean destributed = false;
206        if (commentPreviousSibling != null
207                && commentPreviousSibling.getType() == TokenTypes.SEMI
208                && comment.getLineNo() - commentPreviousSibling.getLineNo() == 1) {
209            DetailAST currentToken = commentPreviousSibling.getPreviousSibling();
210            while (currentToken.getFirstChild() != null) {
211                currentToken = currentToken.getFirstChild();
212            }
213            if (currentToken.getType() != TokenTypes.COMMENT_CONTENT
214                    && commentPreviousSibling.getLineNo() != currentToken.getLineNo()) {
215                destributed = true;
216            }
217        }
218        return destributed;
219    }
220
221    /**
222     * Checks whether the previous statement of a single line comment is a destributed return
223     * statement.
224     * @param commentPreviousSibling previous sibling of the single line comment.
225     * @return true if the previous statement of a single line comment is a destributed return
226     *         statement.
227     */
228    private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
229        boolean destributed = false;
230        if (commentPreviousSibling != null
231                && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
232            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
233            final DetailAST nextSibling = firstChild.getNextSibling();
234            if (nextSibling != null) {
235                destributed = true;
236            }
237        }
238        return destributed;
239    }
240
241    /**
242     * Checks whether the previous statement of a single line comment is a destributed throw
243     * statement.
244     * @param commentPreviousSibling previous sibling of the single line comment.
245     * @return true if the previous statement of a single line comment is a destributed throw
246     *         statement.
247     */
248    private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
249        boolean destributed = false;
250        if (commentPreviousSibling != null
251                && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
252            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
253            final DetailAST nextSibling = firstChild.getNextSibling();
254            if (nextSibling.getLineNo() != commentPreviousSibling.getLineNo()) {
255                destributed = true;
256            }
257        }
258        return destributed;
259    }
260
261    /**
262     * Returns the first token of the destributed previous statement of single line comment.
263     * @param comment single line comment.
264     * @return the first token of the destributed previous statement of single line comment.
265     */
266    private static DetailAST getDistributedPreviousStatementOfSingleLineComment(DetailAST comment) {
267        final DetailAST previousStatement;
268        DetailAST currentToken = comment.getPreviousSibling();
269        if (currentToken.getType() == TokenTypes.LITERAL_RETURN
270                || currentToken.getType() == TokenTypes.LITERAL_THROW) {
271            previousStatement = currentToken;
272        }
273        else {
274            currentToken = currentToken.getPreviousSibling();
275            while (currentToken.getFirstChild() != null) {
276                currentToken = currentToken.getFirstChild();
277            }
278            previousStatement = currentToken;
279        }
280        return previousStatement;
281    }
282
283    /**
284     * Checks whether case block is empty.
285     * @param nextStmt previous statement.
286     * @param prevStmt next statement.
287     * @return true if case block is empty.
288     */
289    private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
290        return prevStmt != null
291            && nextStmt != null
292            && (prevStmt.getType() == TokenTypes.LITERAL_CASE
293                || prevStmt.getType() == TokenTypes.CASE_GROUP)
294            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
295                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
296    }
297
298    /**
299     * Checks whether single line comment is a 'fall through' comment.
300     * For example:
301     * <p>
302     * {@code
303     *    ...
304     *    case OPTION_ONE:
305     *        int someVariable = 1;
306     *        // fall through
307     *    case OPTION_TWO:
308     *        int a = 5;
309     *        break;
310     *    ...
311     * }
312     * </p>
313     * @param prevStmt previous statement.
314     * @param nextStmt next statement.
315     * @return true if a single line comment is a 'fall through' comment.
316     */
317    private static boolean isFallThroughSingleLineComment(DetailAST prevStmt, DetailAST nextStmt) {
318        return prevStmt != null
319            && nextStmt != null
320            && prevStmt.getType() != TokenTypes.LITERAL_CASE
321            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
322                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
323    }
324
325    /**
326     * Checks whether a single line comment is placed at the end of the code block.
327     * @param nextStmt next statement.
328     * @return true if a single line comment is placed at the end of the block.
329     */
330    private static boolean isSingleLineCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
331        return nextStmt != null
332            && nextStmt.getType() == TokenTypes.RCURLY;
333    }
334
335    /**
336     * Checks whether comment is placed in the empty code block.
337     * For example:
338     * <p>
339     * ...
340     * {@code
341     *  // empty code block
342     * }
343     * ...
344     * </p>
345     * Note, the method does not treat empty case blocks.
346     * @param prevStmt previous statement.
347     * @param nextStmt next statement.
348     * @return true if comment is placed in the empty code block.
349     */
350    private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
351        return prevStmt != null
352            && nextStmt != null
353            && (prevStmt.getType() == TokenTypes.SLIST
354                || prevStmt.getType() == TokenTypes.OBJBLOCK)
355            && nextStmt.getType() == TokenTypes.RCURLY;
356    }
357
358    /**
359     * Handles a single line comment which is plased within empty case block.
360     * Note, if comment is placed at the end of the empty case block, we have Checkstyle's
361     * limitations to clearly detect user intention of explanation target - above or below. The
362     * only case we can assume as a violation is when a single line comment within the empty case
363     * block has indentation level that is lower than the indentation level of the next case
364     * token. For example:
365     * <p>
366     * {@code
367     *    ...
368     *    case OPTION_ONE:
369     * // violation
370     *    case OPTION_TWO:
371     *    ...
372     * }
373     * </p>
374     * @param prevStmt previous statement.
375     * @param comment single line comment.
376     * @param nextStmt next statement.
377     */
378    private void handleSingleLineCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
379                                                         DetailAST nextStmt) {
380
381        if (comment.getColumnNo() < prevStmt.getColumnNo()
382                || comment.getColumnNo() < nextStmt.getColumnNo()) {
383            logMultilineIndentation(prevStmt, comment, nextStmt);
384        }
385    }
386
387    /**
388     * Handles 'fall through' single line comment.
389     * Note, 'fall through' and similar comments can have indentation level as next or previous
390     * statement.
391     * For example:
392     * <p>
393     * {@code
394     *    ...
395     *    case OPTION_ONE:
396     *        int someVariable = 1;
397     *        // fall through - OK
398     *    case OPTION_TWO:
399     *        int a = 5;
400     *        break;
401     *    ...
402     * }
403     * </p>
404     * <p>
405     * {@code
406     *    ...
407     *    case OPTION_ONE:
408     *        int someVariable = 1;
409     *    // than init variable a - OK
410     *    case OPTION_TWO:
411     *        int a = 5;
412     *        break;
413     *    ...
414     * }
415     * </p>
416     * @param prevStmt previous statement.
417     * @param comment single line comment.
418     * @param nextStmt next statement.
419     */
420    private void handleFallThroughtSingleLineComment(DetailAST prevStmt, DetailAST comment,
421                                                     DetailAST nextStmt) {
422
423        if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
424            logMultilineIndentation(prevStmt, comment, nextStmt);
425        }
426
427    }
428
429    /**
430     * Hendles a single line comment which is placed at the end of non empty code block.
431     * Note, if single line comment is plcaed at the end of non empty block the comment should have
432     * the same indentation level as the previous statement. For example:
433     * <p>
434     * {@code
435     *    if (a == true) {
436     *        int b = 1;
437     *        // comment
438     *    }
439     * }
440     * </p>
441     * @param prevStmt previous statement.
442     * @param comment single line statement.
443     * @param nextStmt next statement.
444     */
445    private void handleSIngleLineCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt,
446                                                               DetailAST comment,
447                                                               DetailAST nextStmt) {
448        if (prevStmt != null) {
449            if (prevStmt.getType() == TokenTypes.LITERAL_CASE
450                    || prevStmt.getType() == TokenTypes.CASE_GROUP
451                    || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT
452                    || prevStmt.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
453                if (comment.getColumnNo() < nextStmt.getColumnNo()) {
454                    log(comment.getLineNo(), MSG_KEY_SINGLE, nextStmt.getLineNo(),
455                        comment.getColumnNo(), nextStmt.getColumnNo());
456                }
457            }
458            else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
459                log(comment.getLineNo(), MSG_KEY_SINGLE, prevStmt.getLineNo(),
460                    comment.getColumnNo(), prevStmt.getColumnNo());
461            }
462        }
463
464    }
465
466    /**
467     * Handles a single line comment which is placed within the empty code block.
468     * Note, if comment is placed at the end of the empty code block, we have Checkstyle's
469     * limitations to clearly detect user intention of explanation target - above or below. The
470     * only case we can assume as a violation is when a single line comment within the empty
471     * code block has indentation level that is lower than the indentation level of the closing
472     * right curly brace. For example:
473     * <p>
474     * {@code
475     *    if (a == true) {
476     * // violation
477     *    }
478     * }
479     * </p>
480     *
481     * @param comment single line comment.
482     * @param nextStmt next statement.
483     */
484    private void handleSingleLineCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
485        if (comment.getColumnNo() < nextStmt.getColumnNo()) {
486            log(comment.getLineNo(), MSG_KEY_SINGLE, nextStmt.getLineNo(),
487                comment.getColumnNo(), nextStmt.getColumnNo());
488        }
489    }
490
491    /**
492     * Does pre-order traverse of abstract syntax tree to find the previous statement of the
493     * single line comment. If previous statement of the comment is found, then the traverse will
494     * be finished.
495     * @param comment current statement.
496     * @return previous statement of the comment or null if the comment does not have previous
497     *         statement.
498     */
499    private static DetailAST getOneLinePreviousStatementOfSingleLineComment(DetailAST comment) {
500        DetailAST previousStatement = null;
501        final Deque<DetailAST> stack = new ArrayDeque<>();
502        DetailAST root = comment.getParent();
503
504        while (root != null || !stack.isEmpty()) {
505            if (!stack.isEmpty()) {
506                root = stack.pop();
507            }
508            while (root != null) {
509                previousStatement = findPreviousStatementOfSingleLineComment(comment, root);
510                if (previousStatement != null) {
511                    root = null;
512                    stack.clear();
513                    break;
514                }
515                if (root.getNextSibling() != null) {
516                    stack.push(root.getNextSibling());
517                }
518                root = root.getFirstChild();
519            }
520        }
521        return previousStatement;
522    }
523
524    /**
525     * Finds a previous statement of the single line comment.
526     * Uses root token of the line while searching.
527     * @param comment single line comment.
528     * @param root root token of the line.
529     * @return previous statement of the single line comment or null if previous statement was not
530     *         found.
531     */
532    private static DetailAST findPreviousStatementOfSingleLineComment(DetailAST comment,
533                                                                      DetailAST root) {
534        DetailAST previousStatement = null;
535        if (root.getLineNo() >= comment.getLineNo()) {
536            // ATTENTION: parent of the comment is below the comment in case block
537            // See https://github.com/checkstyle/checkstyle/issues/851
538            previousStatement = getPrevStatementFromSwitchBlock(comment);
539        }
540        final DetailAST tokenWhichBeginsTheLine;
541        if (root.getType() == TokenTypes.EXPR
542                && root.getFirstChild().getFirstChild() != null) {
543            if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
544                tokenWhichBeginsTheLine = root.getFirstChild();
545            }
546            else {
547                tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
548            }
549        }
550        else if (root.getType() == TokenTypes.PLUS) {
551            tokenWhichBeginsTheLine = root.getFirstChild();
552        }
553        else {
554            tokenWhichBeginsTheLine = root;
555        }
556        if (tokenWhichBeginsTheLine != null
557                && isOnPreviousLine(comment, tokenWhichBeginsTheLine)) {
558            previousStatement = tokenWhichBeginsTheLine;
559        }
560        return previousStatement;
561    }
562
563    /**
564     * Finds a token which begins the line.
565     * @param root root token of the line.
566     * @return token which begins the line.
567     */
568    private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
569        final DetailAST tokenWhichBeginsTheLine;
570        if (isUsingOfObjectReferenceToInvokeMethod(root)) {
571            tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
572        }
573        else {
574            tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
575        }
576        return tokenWhichBeginsTheLine;
577    }
578
579    /**
580     * Checks whether there is a use of an object reference to invoke an object's method on line.
581     * @param root root token of the line.
582     * @return true if there is a use of an object reference to invoke an object's method on line.
583     */
584    private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
585        return root.getFirstChild().getFirstChild().getFirstChild() != null
586            && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
587    }
588
589    /**
590     * Finds the start token of method call chain.
591     * @param root root token of the line.
592     * @return the start token of method call chain.
593     */
594    private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
595        DetailAST startOfMethodCallChain = root;
596        while (startOfMethodCallChain.getFirstChild() != null
597                && startOfMethodCallChain.getFirstChild().getLineNo() == root.getLineNo()) {
598            startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
599        }
600        if (startOfMethodCallChain.getFirstChild() != null) {
601            startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
602        }
603        return startOfMethodCallChain;
604    }
605
606    /**
607     * Checks whether the checked statement is on previous line.
608     * @param currentStatement current statement.
609     * @param checkedStatement checked statement.
610     * @return true if checked statement is on the line which is previous to current statement.
611     */
612    private static boolean isOnPreviousLine(DetailAST currentStatement,
613                                            DetailAST checkedStatement) {
614        return currentStatement.getLineNo() - checkedStatement.getLineNo() == 1;
615    }
616
617    /**
618     * Logs comment which can have the same indentation level as next or previous statement.
619     * @param comment comment.
620     * @param nextStmt previous statement.
621     * @param prevStmt next statement.
622     */
623    private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
624                                         DetailAST nextStmt) {
625        final String multilineNoTemplate = "%d, %d";
626        log(comment.getLineNo(), MSG_KEY_SINGLE,
627            String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
628                nextStmt.getLineNo()), comment.getColumnNo(),
629            String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getColumnNo(),
630                nextStmt.getColumnNo()));
631    }
632
633    /**
634     * Gets comment's previous statement from switch block.
635     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}.
636     * @return comment's previous statement or null if previous statement is absent.
637     */
638    private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
639        DetailAST prevStmt = null;
640        final DetailAST parentStatement = comment.getParent();
641        if (parentStatement != null) {
642            if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
643                prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
644            }
645            else {
646                prevStmt = getPrevCaseToken(parentStatement);
647            }
648        }
649        return prevStmt;
650    }
651
652    /**
653     * Gets previous statement for comment which is placed immediately under case.
654     * @param parentStatement comment's parent statement.
655     * @return comment's previous statement or null if previous statement is absent.
656     */
657    private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
658        DetailAST prevStmt = null;
659        final DetailAST prevBlock = parentStatement.getPreviousSibling();
660        if (prevBlock.getLastChild() != null) {
661            DetailAST blockBody = prevBlock.getLastChild().getLastChild();
662            if (blockBody.getPreviousSibling() != null) {
663                blockBody = blockBody.getPreviousSibling();
664            }
665            if (blockBody.getType() == TokenTypes.EXPR) {
666                if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
667                    prevStmt = findStartTokenOfMethodCallChain(blockBody);
668                }
669                else {
670                    prevStmt = blockBody.getFirstChild().getFirstChild();
671                }
672            }
673            else {
674                if (blockBody.getType() == TokenTypes.SLIST) {
675                    prevStmt = blockBody.getParent().getParent();
676                }
677                else {
678                    prevStmt = blockBody;
679                }
680            }
681        }
682        return prevStmt;
683    }
684
685    /**
686     * Gets previous case-token for comment.
687     * @param parentStatement comment's parent statement.
688     * @return previous case-token or null if previous case-token is absent.
689     */
690    private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
691        final DetailAST prevCaseToken;
692        final DetailAST parentBlock = parentStatement.getParent();
693        if (parentBlock != null && parentBlock.getParent() != null
694                && parentBlock.getParent().getPreviousSibling() != null
695                && parentBlock.getParent().getPreviousSibling().getType()
696                    == TokenTypes.LITERAL_CASE) {
697            prevCaseToken = parentBlock.getParent().getPreviousSibling();
698        }
699        else {
700            prevCaseToken = null;
701        }
702        return prevCaseToken;
703    }
704
705    /**
706     * Checks if comment and next code statement
707     * (or previous code stmt like <b>case</b> in switch block) are indented at the same level,
708     * e.g.:
709     * <p>
710     * <pre>
711     * {@code
712     * // some comment - same indentation level
713     * int x = 10;
714     *     // some comment - different indentation level
715     * int x1 = 5;
716     * /*
717     *  *
718     *  *&#47;
719     *  boolean bool = true; - same indentation level
720     * }
721     * </pre>
722     * </p>
723     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
724     * @param prevStmt previous code statement.
725     * @param nextStmt next code statement.
726     * @return true if comment and next code statement are indented at the same level.
727     */
728    private static boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
729                                                DetailAST nextStmt) {
730        final boolean result;
731        if (prevStmt == null) {
732            result = comment.getColumnNo() == nextStmt.getColumnNo();
733        }
734        else {
735            result = comment.getColumnNo() == nextStmt.getColumnNo()
736                || comment.getColumnNo() == prevStmt.getColumnNo();
737        }
738        return result;
739    }
740
741    /**
742     * Checks if current single line comment is trailing comment, e.g.:
743     * <p>
744     * {@code
745     * double d = 3.14; // some comment
746     * }
747     * </p>
748     * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
749     * @return true if current single line comment is trailing comment.
750     */
751    private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
752        final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
753        final int commentColumnNo = singleLineComment.getColumnNo();
754        return !CommonUtils.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
755    }
756
757    /**
758     * Checks comment block indentations over surrounding code, e.g.:
759     * <p>
760     * {@code
761     * /* some comment *&#47; - this is ok
762     * double d = 3.14;
763     *     /* some comment *&#47; - this is <b>not</b> ok.
764     * double d1 = 5.0;
765     * }
766     * </p>
767     * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
768     */
769    private void visitBlockComment(DetailAST blockComment) {
770        final DetailAST nextStatement = blockComment.getNextSibling();
771        final DetailAST prevStatement = getPrevStatementFromSwitchBlock(blockComment);
772
773        if (nextStatement != null
774                && nextStatement.getType() != TokenTypes.RCURLY
775                && !isTrailingBlockComment(blockComment)
776                && !areSameLevelIndented(blockComment, prevStatement, nextStatement)) {
777            log(blockComment.getLineNo(), MSG_KEY_BLOCK, nextStatement.getLineNo(),
778                blockComment.getColumnNo(), nextStatement.getColumnNo());
779        }
780    }
781
782    /**
783     * Checks if current comment block is trailing comment, e.g.:
784     * <p>
785     * {@code
786     * double d = 3.14; /* some comment *&#47;
787     * /* some comment *&#47; double d = 18.5;
788     * }
789     * </p>
790     * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
791     * @return true if current comment block is trailing comment.
792     */
793    private boolean isTrailingBlockComment(DetailAST blockComment) {
794        final String commentLine = getLine(blockComment.getLineNo() - 1);
795        final int commentColumnNo = blockComment.getColumnNo();
796        return !CommonUtils.hasWhitespaceBefore(commentColumnNo, commentLine)
797            || blockComment.getNextSibling().getLineNo() == blockComment.getLineNo();
798    }
799}