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