001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.coding;
021
022import java.util.AbstractMap.SimpleEntry;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map.Entry;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import antlr.collections.ASTEnumeration;
030import com.puppycrawl.tools.checkstyle.StatelessCheck;
031import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.FullIdent;
034import com.puppycrawl.tools.checkstyle.api.TokenTypes;
035
036/**
037 * <p>
038 * Checks the distance between declaration of variable and its first usage.
039 * </p>
040 * Example #1:
041 * <pre>
042 *      {@code int count;
043 *      a = a + b;
044 *      b = a + a;
045 *      count = b; // DECLARATION OF VARIABLE 'count'
046 *                 // SHOULD BE HERE (distance = 3)}
047 * </pre>
048 * Example #2:
049 * <pre>
050 *     {@code int count;
051 *     {
052 *         a = a + b;
053 *         count = b; // DECLARATION OF VARIABLE 'count'
054 *                    // SHOULD BE HERE (distance = 2)
055 *     }}
056 * </pre>
057 *
058 * <p>
059 * Check can detect a block of initialization methods. If a variable is used in
060 * such a block and there is no other statements after this variable then distance=1.
061 * </p>
062 *
063 * <p><b>Case #1:</b>
064 * <pre>
065 * int <b>minutes</b> = 5;
066 * Calendar cal = Calendar.getInstance();
067 * cal.setTimeInMillis(timeNow);
068 * cal.set(Calendar.SECOND, 0);
069 * cal.set(Calendar.MILLISECOND, 0);
070 * cal.set(Calendar.HOUR_OF_DAY, hh);
071 * cal.set(Calendar.MINUTE, <b>minutes</b>);
072 *
073 * The distance for the variable <b>minutes</b> is 1 even
074 * though this variable is used in the fifth method's call.
075 * </pre>
076 *
077 * <p><b>Case #2:</b>
078 * <pre>
079 * int <b>minutes</b> = 5;
080 * Calendar cal = Calendar.getInstance();
081 * cal.setTimeInMillis(timeNow);
082 * cal.set(Calendar.SECOND, 0);
083 * cal.set(Calendar.MILLISECOND, 0);
084 * <i>System.out.println(cal);</i>
085 * cal.set(Calendar.HOUR_OF_DAY, hh);
086 * cal.set(Calendar.MINUTE, <b>minutes</b>);
087 *
088 * The distance for the variable <b>minutes</b> is 6 because there is one more expression
089 * (except the initialization block) between the declaration of this variable and its usage.
090 * </pre>
091 *
092 * <p>There are several additional options to configure the check:
093 * <pre>
094 * 1. allowedDistance - allows to set a distance
095 * between declaration of variable and its first usage.
096 * 2. ignoreVariablePattern - allows to set a RegEx pattern for
097 * ignoring the distance calculation for variables listed in this pattern.
098 * 3. validateBetweenScopes - allows to calculate the distance between
099 * declaration of variable and its first usage in the different scopes.
100 * 4. ignoreFinal - allows to ignore variables with a 'final' modifier.
101 * </pre>
102 * ATTENTION!! (Not supported cases)
103 * <pre>
104 * Case #1:
105 * {@code {
106 * int c;
107 * int a = 3;
108 * int b = 2;
109 *     {
110 *     a = a + b;
111 *     c = b;
112 *     }
113 * }}
114 *
115 * Distance for variable 'a' = 1;
116 * Distance for variable 'b' = 1;
117 * Distance for variable 'c' = 2.
118 * </pre>
119 * As distance by default is 1 the Check doesn't raise warning for variables 'a'
120 * and 'b' to move them into the block.
121 * <pre>
122 * Case #2:
123 * {@code int sum = 0;
124 * for (int i = 0; i &lt; 20; i++) {
125 *     a++;
126 *     b--;
127 *     sum++;
128 *     if (sum &gt; 10) {
129 *         res = true;
130 *     }
131 * }}
132 * Distance for variable 'sum' = 3.
133 * </pre>
134 * <p>
135 * As the distance is more then the default one, the Check raises warning for variable
136 * 'sum' to move it into the 'for(...)' block. But there is situation when
137 * variable 'sum' hasn't to be 0 within each iteration. So, to avoid such
138 * warnings you can use Suppression Filter, provided by Checkstyle, for the
139 * whole class.
140 * </p>
141 *
142 * <p>
143 * An example how to configure this Check:
144 * </p>
145 * <pre>
146 * &lt;module name="VariableDeclarationUsageDistance"/&gt;
147 * </pre>
148 * <p>
149 * An example of how to configure this Check:
150 *  - to set the allowed distance to 4;
151 *  - to ignore variables with prefix '^temp';
152 *  - to force the validation between scopes;
153 *  - to check the final variables;
154 * </p>
155 * <pre>
156 * &lt;module name="VariableDeclarationUsageDistance"&gt;
157 *     &lt;property name="allowedDistance" value="4"/&gt;
158 *     &lt;property name="ignoreVariablePattern" value="^temp.*"/&gt;
159 *     &lt;property name="validateBetweenScopes" value="true"/&gt;
160 *     &lt;property name="ignoreFinal" value="false"/&gt;
161 * &lt;/module&gt;
162 * </pre>
163 *
164 */
165@StatelessCheck
166public class VariableDeclarationUsageDistanceCheck extends AbstractCheck {
167
168    /**
169     * Warning message key.
170     */
171    public static final String MSG_KEY = "variable.declaration.usage.distance";
172
173    /**
174     * Warning message key.
175     */
176    public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend";
177
178    /**
179     * Default value of distance between declaration of variable and its first
180     * usage.
181     */
182    private static final int DEFAULT_DISTANCE = 3;
183
184    /** Allowed distance between declaration of variable and its first usage. */
185    private int allowedDistance = DEFAULT_DISTANCE;
186
187    /**
188     * RegExp pattern to ignore distance calculation for variables listed in
189     * this pattern.
190     */
191    private Pattern ignoreVariablePattern = Pattern.compile("");
192
193    /**
194     * Allows to calculate distance between declaration of variable and its
195     * first usage in different scopes.
196     */
197    private boolean validateBetweenScopes;
198
199    /** Allows to ignore variables with 'final' modifier. */
200    private boolean ignoreFinal = true;
201
202    /**
203     * Sets an allowed distance between declaration of variable and its first
204     * usage.
205     * @param allowedDistance
206     *        Allowed distance between declaration of variable and its first
207     *        usage.
208     */
209    public void setAllowedDistance(int allowedDistance) {
210        this.allowedDistance = allowedDistance;
211    }
212
213    /**
214     * Sets RegExp pattern to ignore distance calculation for variables listed in this pattern.
215     * @param pattern a pattern.
216     */
217    public void setIgnoreVariablePattern(Pattern pattern) {
218        ignoreVariablePattern = pattern;
219    }
220
221    /**
222     * Sets option which allows to calculate distance between declaration of
223     * variable and its first usage in different scopes.
224     * @param validateBetweenScopes
225     *        Defines if allow to calculate distance between declaration of
226     *        variable and its first usage in different scopes or not.
227     */
228    public void setValidateBetweenScopes(boolean validateBetweenScopes) {
229        this.validateBetweenScopes = validateBetweenScopes;
230    }
231
232    /**
233     * Sets ignore option for variables with 'final' modifier.
234     * @param ignoreFinal
235     *        Defines if ignore variables with 'final' modifier or not.
236     */
237    public void setIgnoreFinal(boolean ignoreFinal) {
238        this.ignoreFinal = ignoreFinal;
239    }
240
241    @Override
242    public int[] getDefaultTokens() {
243        return getRequiredTokens();
244    }
245
246    @Override
247    public int[] getAcceptableTokens() {
248        return getRequiredTokens();
249    }
250
251    @Override
252    public int[] getRequiredTokens() {
253        return new int[] {TokenTypes.VARIABLE_DEF};
254    }
255
256    @Override
257    public void visitToken(DetailAST ast) {
258        final int parentType = ast.getParent().getType();
259        final DetailAST modifiers = ast.getFirstChild();
260
261        if (parentType != TokenTypes.OBJBLOCK
262                && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) {
263            final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT);
264
265            if (!isVariableMatchesIgnorePattern(variable.getText())) {
266                final DetailAST semicolonAst = ast.getNextSibling();
267                final Entry<DetailAST, Integer> entry;
268                if (validateBetweenScopes) {
269                    entry = calculateDistanceBetweenScopes(semicolonAst, variable);
270                }
271                else {
272                    entry = calculateDistanceInSingleScope(semicolonAst, variable);
273                }
274                final DetailAST variableUsageAst = entry.getKey();
275                final int dist = entry.getValue();
276                if (dist > allowedDistance
277                        && !isInitializationSequence(variableUsageAst, variable.getText())) {
278                    if (ignoreFinal) {
279                        log(variable.getLineNo(),
280                                MSG_KEY_EXT, variable.getText(), dist, allowedDistance);
281                    }
282                    else {
283                        log(variable.getLineNo(),
284                                MSG_KEY, variable.getText(), dist, allowedDistance);
285                    }
286                }
287            }
288        }
289    }
290
291    /**
292     * Get name of instance whose method is called.
293     * @param methodCallAst
294     *        DetailAST of METHOD_CALL.
295     * @return name of instance.
296     */
297    private static String getInstanceName(DetailAST methodCallAst) {
298        final String methodCallName =
299                FullIdent.createFullIdentBelow(methodCallAst).getText();
300        final int lastDotIndex = methodCallName.lastIndexOf('.');
301        String instanceName = "";
302        if (lastDotIndex != -1) {
303            instanceName = methodCallName.substring(0, lastDotIndex);
304        }
305        return instanceName;
306    }
307
308    /**
309     * Processes statements until usage of variable to detect sequence of
310     * initialization methods.
311     * @param variableUsageAst
312     *        DetailAST of expression that uses variable named variableName.
313     * @param variableName
314     *        name of considered variable.
315     * @return true if statements between declaration and usage of variable are
316     *         initialization methods.
317     */
318    private static boolean isInitializationSequence(
319            DetailAST variableUsageAst, String variableName) {
320        boolean result = true;
321        boolean isUsedVariableDeclarationFound = false;
322        DetailAST currentSiblingAst = variableUsageAst;
323        String initInstanceName = "";
324
325        while (result
326                && !isUsedVariableDeclarationFound
327                && currentSiblingAst != null) {
328            switch (currentSiblingAst.getType()) {
329                case TokenTypes.EXPR:
330                    final DetailAST methodCallAst = currentSiblingAst.getFirstChild();
331
332                    if (methodCallAst.getType() == TokenTypes.METHOD_CALL) {
333                        final String instanceName =
334                            getInstanceName(methodCallAst);
335                        // method is called without instance
336                        if (instanceName.isEmpty()) {
337                            result = false;
338                        }
339                        // differs from previous instance
340                        else if (!instanceName.equals(initInstanceName)) {
341                            if (initInstanceName.isEmpty()) {
342                                initInstanceName = instanceName;
343                            }
344                            else {
345                                result = false;
346                            }
347                        }
348                    }
349                    else {
350                        // is not method call
351                        result = false;
352                    }
353                    break;
354
355                case TokenTypes.VARIABLE_DEF:
356                    final String currentVariableName = currentSiblingAst
357                        .findFirstToken(TokenTypes.IDENT).getText();
358                    isUsedVariableDeclarationFound = variableName.equals(currentVariableName);
359                    break;
360
361                case TokenTypes.SEMI:
362                    break;
363
364                default:
365                    result = false;
366            }
367
368            currentSiblingAst = currentSiblingAst.getPreviousSibling();
369        }
370
371        return result;
372    }
373
374    /**
375     * Calculates distance between declaration of variable and its first usage
376     * in single scope.
377     * @param semicolonAst
378     *        Regular node of Ast which is checked for content of checking
379     *        variable.
380     * @param variableIdentAst
381     *        Variable which distance is calculated for.
382     * @return entry which contains expression with variable usage and distance.
383     */
384    private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
385            DetailAST semicolonAst, DetailAST variableIdentAst) {
386        int dist = 0;
387        boolean firstUsageFound = false;
388        DetailAST currentAst = semicolonAst;
389        DetailAST variableUsageAst = null;
390
391        while (!firstUsageFound && currentAst != null
392                && currentAst.getType() != TokenTypes.RCURLY) {
393            if (currentAst.getFirstChild() != null) {
394                if (isChild(currentAst, variableIdentAst)) {
395                    dist = getDistToVariableUsageInChildNode(currentAst, variableIdentAst, dist);
396                    variableUsageAst = currentAst;
397                    firstUsageFound = true;
398                }
399                else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
400                    dist++;
401                }
402            }
403            currentAst = currentAst.getNextSibling();
404        }
405
406        // If variable wasn't used after its declaration, distance is 0.
407        if (!firstUsageFound) {
408            dist = 0;
409        }
410
411        return new SimpleEntry<>(variableUsageAst, dist);
412    }
413
414    /**
415     * Returns the distance to variable usage for in the child node.
416     * @param childNode child node.
417     * @param varIdent variable variable identifier.
418     * @param currentDistToVarUsage current distance to the variable usage.
419     * @return the distance to variable usage for in the child node.
420     */
421    private static int getDistToVariableUsageInChildNode(DetailAST childNode, DetailAST varIdent,
422                                                         int currentDistToVarUsage) {
423        DetailAST examineNode = childNode;
424        if (examineNode.getType() == TokenTypes.LABELED_STAT) {
425            examineNode = examineNode.getFirstChild().getNextSibling();
426        }
427
428        int resultDist = currentDistToVarUsage;
429        switch (examineNode.getType()) {
430            case TokenTypes.VARIABLE_DEF:
431                resultDist++;
432                break;
433            case TokenTypes.SLIST:
434                resultDist = 0;
435                break;
436            case TokenTypes.LITERAL_FOR:
437            case TokenTypes.LITERAL_WHILE:
438            case TokenTypes.LITERAL_DO:
439            case TokenTypes.LITERAL_IF:
440            case TokenTypes.LITERAL_SWITCH:
441                if (isVariableInOperatorExpr(examineNode, varIdent)) {
442                    resultDist++;
443                }
444                else {
445                    // variable usage is in inner scope
446                    // reset counters, because we can't determine distance
447                    resultDist = 0;
448                }
449                break;
450            default:
451                if (examineNode.findFirstToken(TokenTypes.SLIST) == null) {
452                    resultDist++;
453                }
454                else {
455                    resultDist = 0;
456                }
457        }
458        return resultDist;
459    }
460
461    /**
462     * Calculates distance between declaration of variable and its first usage
463     * in multiple scopes.
464     * @param ast
465     *        Regular node of Ast which is checked for content of checking
466     *        variable.
467     * @param variable
468     *        Variable which distance is calculated for.
469     * @return entry which contains expression with variable usage and distance.
470     */
471    private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes(
472            DetailAST ast, DetailAST variable) {
473        int dist = 0;
474        DetailAST currentScopeAst = ast;
475        DetailAST variableUsageAst = null;
476        while (currentScopeAst != null) {
477            final Entry<List<DetailAST>, Integer> searchResult =
478                    searchVariableUsageExpressions(variable, currentScopeAst);
479
480            currentScopeAst = null;
481
482            final List<DetailAST> variableUsageExpressions = searchResult.getKey();
483            dist += searchResult.getValue();
484
485            // If variable usage exists in a single scope, then look into
486            // this scope and count distance until variable usage.
487            if (variableUsageExpressions.size() == 1) {
488                final DetailAST blockWithVariableUsage = variableUsageExpressions
489                        .get(0);
490                DetailAST exprWithVariableUsage = null;
491                switch (blockWithVariableUsage.getType()) {
492                    case TokenTypes.VARIABLE_DEF:
493                    case TokenTypes.EXPR:
494                        dist++;
495                        break;
496                    case TokenTypes.LITERAL_FOR:
497                    case TokenTypes.LITERAL_WHILE:
498                    case TokenTypes.LITERAL_DO:
499                        exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks(
500                            blockWithVariableUsage, variable);
501                        break;
502                    case TokenTypes.LITERAL_IF:
503                        exprWithVariableUsage = getFirstNodeInsideIfBlock(
504                            blockWithVariableUsage, variable);
505                        break;
506                    case TokenTypes.LITERAL_SWITCH:
507                        exprWithVariableUsage = getFirstNodeInsideSwitchBlock(
508                            blockWithVariableUsage, variable);
509                        break;
510                    case TokenTypes.LITERAL_TRY:
511                        exprWithVariableUsage =
512                            getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage,
513                                variable);
514                        break;
515                    default:
516                        exprWithVariableUsage = blockWithVariableUsage.getFirstChild();
517                }
518                currentScopeAst = exprWithVariableUsage;
519                if (exprWithVariableUsage == null) {
520                    variableUsageAst = blockWithVariableUsage;
521                }
522                else {
523                    variableUsageAst = exprWithVariableUsage;
524                }
525            }
526
527            // If there's no any variable usage, then distance = 0.
528            else if (variableUsageExpressions.isEmpty()) {
529                variableUsageAst = null;
530            }
531            // If variable usage exists in different scopes, then distance =
532            // distance until variable first usage.
533            else {
534                dist++;
535                variableUsageAst = variableUsageExpressions.get(0);
536            }
537        }
538        return new SimpleEntry<>(variableUsageAst, dist);
539    }
540
541    /**
542     * Searches variable usages starting from specified statement.
543     * @param variableAst Variable that is used.
544     * @param statementAst DetailAST to start searching from.
545     * @return entry which contains list with found expressions that use the variable
546     *     and distance from specified statement to first found expression.
547     */
548    private static Entry<List<DetailAST>, Integer>
549        searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) {
550        final List<DetailAST> variableUsageExpressions = new ArrayList<>();
551        int distance = 0;
552        DetailAST currentStatementAst = statementAst;
553        while (currentStatementAst != null
554                && currentStatementAst.getType() != TokenTypes.RCURLY) {
555            if (currentStatementAst.getFirstChild() != null) {
556                if (isChild(currentStatementAst, variableAst)) {
557                    variableUsageExpressions.add(currentStatementAst);
558                }
559                // If expression doesn't contain variable and this variable
560                // hasn't been met yet, than distance + 1.
561                else if (variableUsageExpressions.isEmpty()
562                        && currentStatementAst.getType() != TokenTypes.VARIABLE_DEF) {
563                    distance++;
564                }
565            }
566            currentStatementAst = currentStatementAst.getNextSibling();
567        }
568        return new SimpleEntry<>(variableUsageExpressions, distance);
569    }
570
571    /**
572     * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable
573     * usage is met only inside the block (not in its declaration!).
574     * @param block
575     *        Ast node represents FOR, WHILE or DO-WHILE block.
576     * @param variable
577     *        Variable which is checked for content in block.
578     * @return If variable usage is met only inside the block
579     *         (not in its declaration!) than return the first Ast node
580     *         of this block, otherwise - null.
581     */
582    private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks(
583            DetailAST block, DetailAST variable) {
584        DetailAST firstNodeInsideBlock = null;
585
586        if (!isVariableInOperatorExpr(block, variable)) {
587            final DetailAST currentNode;
588
589            // Find currentNode for DO-WHILE block.
590            if (block.getType() == TokenTypes.LITERAL_DO) {
591                currentNode = block.getFirstChild();
592            }
593            // Find currentNode for FOR or WHILE block.
594            else {
595                // Looking for RPAREN ( ')' ) token to mark the end of operator
596                // expression.
597                currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling();
598            }
599
600            final int currentNodeType = currentNode.getType();
601
602            if (currentNodeType == TokenTypes.SLIST) {
603                firstNodeInsideBlock = currentNode.getFirstChild();
604            }
605            else if (currentNodeType != TokenTypes.EXPR) {
606                firstNodeInsideBlock = currentNode;
607            }
608        }
609
610        return firstNodeInsideBlock;
611    }
612
613    /**
614     * Gets first Ast node inside IF block if variable usage is met
615     * only inside the block (not in its declaration!).
616     * @param block
617     *        Ast node represents IF block.
618     * @param variable
619     *        Variable which is checked for content in block.
620     * @return If variable usage is met only inside the block
621     *         (not in its declaration!) than return the first Ast node
622     *         of this block, otherwise - null.
623     */
624    private static DetailAST getFirstNodeInsideIfBlock(
625            DetailAST block, DetailAST variable) {
626        DetailAST firstNodeInsideBlock = null;
627
628        if (!isVariableInOperatorExpr(block, variable)) {
629            DetailAST currentNode = block.getLastChild();
630            final List<DetailAST> variableUsageExpressions =
631                    new ArrayList<>();
632
633            while (currentNode != null
634                    && currentNode.getType() == TokenTypes.LITERAL_ELSE) {
635                final DetailAST previousNode =
636                        currentNode.getPreviousSibling();
637
638                // Checking variable usage inside IF block.
639                if (isChild(previousNode, variable)) {
640                    variableUsageExpressions.add(previousNode);
641                }
642
643                // Looking into ELSE block, get its first child and analyze it.
644                currentNode = currentNode.getFirstChild();
645
646                if (currentNode.getType() == TokenTypes.LITERAL_IF) {
647                    currentNode = currentNode.getLastChild();
648                }
649                else if (isChild(currentNode, variable)) {
650                    variableUsageExpressions.add(currentNode);
651                    currentNode = null;
652                }
653            }
654
655            // If IF block doesn't include ELSE than analyze variable usage
656            // only inside IF block.
657            if (currentNode != null
658                    && isChild(currentNode, variable)) {
659                variableUsageExpressions.add(currentNode);
660            }
661
662            // If variable usage exists in several related blocks, then
663            // firstNodeInsideBlock = null, otherwise if variable usage exists
664            // only inside one block, then get node from
665            // variableUsageExpressions.
666            if (variableUsageExpressions.size() == 1) {
667                firstNodeInsideBlock = variableUsageExpressions.get(0);
668            }
669        }
670
671        return firstNodeInsideBlock;
672    }
673
674    /**
675     * Gets first Ast node inside SWITCH block if variable usage is met
676     * only inside the block (not in its declaration!).
677     * @param block
678     *        Ast node represents SWITCH block.
679     * @param variable
680     *        Variable which is checked for content in block.
681     * @return If variable usage is met only inside the block
682     *         (not in its declaration!) than return the first Ast node
683     *         of this block, otherwise - null.
684     */
685    private static DetailAST getFirstNodeInsideSwitchBlock(
686            DetailAST block, DetailAST variable) {
687        DetailAST currentNode = block
688                .findFirstToken(TokenTypes.CASE_GROUP);
689        final List<DetailAST> variableUsageExpressions =
690                new ArrayList<>();
691
692        // Checking variable usage inside all CASE blocks.
693        while (currentNode.getType() == TokenTypes.CASE_GROUP) {
694            final DetailAST lastNodeInCaseGroup =
695                    currentNode.getLastChild();
696
697            if (isChild(lastNodeInCaseGroup, variable)) {
698                variableUsageExpressions.add(lastNodeInCaseGroup);
699            }
700            currentNode = currentNode.getNextSibling();
701        }
702
703        // If variable usage exists in several related blocks, then
704        // firstNodeInsideBlock = null, otherwise if variable usage exists
705        // only inside one block, then get node from
706        // variableUsageExpressions.
707        DetailAST firstNodeInsideBlock = null;
708        if (variableUsageExpressions.size() == 1) {
709            firstNodeInsideBlock = variableUsageExpressions.get(0);
710        }
711
712        return firstNodeInsideBlock;
713    }
714
715    /**
716     * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is
717     * met only inside the block (not in its declaration!).
718     * @param block
719     *        Ast node represents TRY-CATCH-FINALLY block.
720     * @param variable
721     *        Variable which is checked for content in block.
722     * @return If variable usage is met only inside the block
723     *         (not in its declaration!) than return the first Ast node
724     *         of this block, otherwise - null.
725     */
726    private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks(
727            DetailAST block, DetailAST variable) {
728        DetailAST currentNode = block.getFirstChild();
729        final List<DetailAST> variableUsageExpressions =
730                new ArrayList<>();
731
732        // Checking variable usage inside TRY block.
733        if (isChild(currentNode, variable)) {
734            variableUsageExpressions.add(currentNode);
735        }
736
737        // Switch on CATCH block.
738        currentNode = currentNode.getNextSibling();
739
740        // Checking variable usage inside all CATCH blocks.
741        while (currentNode != null
742                && currentNode.getType() == TokenTypes.LITERAL_CATCH) {
743            final DetailAST catchBlock = currentNode.getLastChild();
744
745            if (isChild(catchBlock, variable)) {
746                variableUsageExpressions.add(catchBlock);
747            }
748            currentNode = currentNode.getNextSibling();
749        }
750
751        // Checking variable usage inside FINALLY block.
752        if (currentNode != null) {
753            final DetailAST finalBlock = currentNode.getLastChild();
754
755            if (isChild(finalBlock, variable)) {
756                variableUsageExpressions.add(finalBlock);
757            }
758        }
759
760        DetailAST variableUsageNode = null;
761
762        // If variable usage exists in several related blocks, then
763        // firstNodeInsideBlock = null, otherwise if variable usage exists
764        // only inside one block, then get node from
765        // variableUsageExpressions.
766        if (variableUsageExpressions.size() == 1) {
767            variableUsageNode = variableUsageExpressions.get(0).getFirstChild();
768        }
769
770        return variableUsageNode;
771    }
772
773    /**
774     * Checks if variable is in operator declaration. For instance:
775     * <pre>
776     * boolean b = true;
777     * if (b) {...}
778     * </pre>
779     * Variable 'b' is in declaration of operator IF.
780     * @param operator
781     *        Ast node which represents operator.
782     * @param variable
783     *        Variable which is checked for content in operator.
784     * @return true if operator contains variable in its declaration, otherwise
785     *         - false.
786     */
787    private static boolean isVariableInOperatorExpr(
788            DetailAST operator, DetailAST variable) {
789        boolean isVarInOperatorDeclaration = false;
790        final DetailAST openingBracket =
791                operator.findFirstToken(TokenTypes.LPAREN);
792
793        // Get EXPR between brackets
794        DetailAST exprBetweenBrackets = openingBracket.getNextSibling();
795
796        // Look if variable is in operator expression
797        while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) {
798            if (isChild(exprBetweenBrackets, variable)) {
799                isVarInOperatorDeclaration = true;
800                break;
801            }
802            exprBetweenBrackets = exprBetweenBrackets.getNextSibling();
803        }
804
805        // Variable may be met in ELSE declaration
806        // So, check variable usage in these declarations.
807        if (!isVarInOperatorDeclaration && operator.getType() == TokenTypes.LITERAL_IF) {
808            final DetailAST elseBlock = operator.getLastChild();
809
810            if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) {
811                // Get IF followed by ELSE
812                final DetailAST firstNodeInsideElseBlock = elseBlock.getFirstChild();
813
814                if (firstNodeInsideElseBlock.getType() == TokenTypes.LITERAL_IF) {
815                    isVarInOperatorDeclaration =
816                        isVariableInOperatorExpr(firstNodeInsideElseBlock, variable);
817                }
818            }
819        }
820
821        return isVarInOperatorDeclaration;
822    }
823
824    /**
825     * Checks if Ast node contains given element.
826     * @param parent
827     *        Node of AST.
828     * @param ast
829     *        Ast element which is checked for content in Ast node.
830     * @return true if Ast element was found in Ast node, otherwise - false.
831     */
832    private static boolean isChild(DetailAST parent, DetailAST ast) {
833        boolean isChild = false;
834        final ASTEnumeration astList = parent.findAllPartial(ast);
835
836        while (astList.hasMoreNodes()) {
837            final DetailAST astNode = (DetailAST) astList.nextNode();
838            DetailAST astParent = astNode.getParent();
839
840            while (astParent != null) {
841                if (astParent.equals(parent)
842                        && astParent.getLineNo() == parent.getLineNo()) {
843                    isChild = true;
844                    break;
845                }
846                astParent = astParent.getParent();
847            }
848        }
849
850        return isChild;
851    }
852
853    /**
854     * Checks if entrance variable is contained in ignored pattern.
855     * @param variable
856     *        Variable which is checked for content in ignored pattern.
857     * @return true if variable was found, otherwise - false.
858     */
859    private boolean isVariableMatchesIgnorePattern(String variable) {
860        final Matcher matcher = ignoreVariablePattern.matcher(variable);
861        return matcher.matches();
862    }
863
864}