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