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