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