001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files 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     *         If variable usage is not found, then the expression node is null,
526     *         although the distance can be greater than zero.
527     */
528    private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
529            DetailAST semicolonAst, DetailAST variableIdentAst) {
530        int dist = 0;
531        boolean firstUsageFound = false;
532        DetailAST currentAst = semicolonAst;
533        DetailAST variableUsageAst = null;
534
535        while (!firstUsageFound && currentAst != null) {
536            if (currentAst.getFirstChild() != null) {
537                if (isChild(currentAst, variableIdentAst)) {
538                    dist = getDistToVariableUsageInChildNode(currentAst, variableIdentAst, dist);
539                    variableUsageAst = currentAst;
540                    firstUsageFound = true;
541                }
542                else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
543                    dist++;
544                }
545            }
546            currentAst = currentAst.getNextSibling();
547        }
548
549        return new SimpleEntry<>(variableUsageAst, dist);
550    }
551
552    /**
553     * Returns the distance to variable usage for in the child node.
554     *
555     * @param childNode child node.
556     * @param varIdent variable variable identifier.
557     * @param currentDistToVarUsage current distance to the variable usage.
558     * @return the distance to variable usage for in the child node.
559     */
560    private static int getDistToVariableUsageInChildNode(DetailAST childNode, DetailAST varIdent,
561                                                         int currentDistToVarUsage) {
562        DetailAST examineNode = childNode;
563        if (examineNode.getType() == TokenTypes.LABELED_STAT) {
564            examineNode = examineNode.getFirstChild().getNextSibling();
565        }
566
567        int resultDist = currentDistToVarUsage;
568        switch (examineNode.getType()) {
569            case TokenTypes.VARIABLE_DEF:
570                resultDist++;
571                break;
572            case TokenTypes.SLIST:
573                resultDist = 0;
574                break;
575            case TokenTypes.LITERAL_FOR:
576            case TokenTypes.LITERAL_WHILE:
577            case TokenTypes.LITERAL_DO:
578            case TokenTypes.LITERAL_IF:
579            case TokenTypes.LITERAL_SWITCH:
580                if (isVariableInOperatorExpr(examineNode, varIdent)) {
581                    resultDist++;
582                }
583                else {
584                    // variable usage is in inner scope
585                    // reset counters, because we can't determine distance
586                    resultDist = 0;
587                }
588                break;
589            default:
590                if (examineNode.findFirstToken(TokenTypes.SLIST) == null) {
591                    resultDist++;
592                }
593                else {
594                    resultDist = 0;
595                }
596        }
597        return resultDist;
598    }
599
600    /**
601     * Calculates distance between declaration of variable and its first usage
602     * in multiple scopes.
603     *
604     * @param ast
605     *        Regular node of Ast which is checked for content of checking
606     *        variable.
607     * @param variable
608     *        Variable which distance is calculated for.
609     * @return entry which contains expression with variable usage and distance.
610     */
611    private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes(
612            DetailAST ast, DetailAST variable) {
613        int dist = 0;
614        DetailAST currentScopeAst = ast;
615        DetailAST variableUsageAst = null;
616        while (currentScopeAst != null) {
617            final Entry<List<DetailAST>, Integer> searchResult =
618                    searchVariableUsageExpressions(variable, currentScopeAst);
619
620            currentScopeAst = null;
621
622            final List<DetailAST> variableUsageExpressions = searchResult.getKey();
623            dist += searchResult.getValue();
624
625            // If variable usage exists in a single scope, then look into
626            // this scope and count distance until variable usage.
627            if (variableUsageExpressions.size() == 1) {
628                final DetailAST blockWithVariableUsage = variableUsageExpressions
629                        .get(0);
630                DetailAST exprWithVariableUsage = null;
631                switch (blockWithVariableUsage.getType()) {
632                    case TokenTypes.VARIABLE_DEF:
633                    case TokenTypes.EXPR:
634                        dist++;
635                        break;
636                    case TokenTypes.LITERAL_FOR:
637                    case TokenTypes.LITERAL_WHILE:
638                    case TokenTypes.LITERAL_DO:
639                        exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks(
640                            blockWithVariableUsage, variable);
641                        break;
642                    case TokenTypes.LITERAL_IF:
643                        exprWithVariableUsage = getFirstNodeInsideIfBlock(
644                            blockWithVariableUsage, variable);
645                        break;
646                    case TokenTypes.LITERAL_SWITCH:
647                        exprWithVariableUsage = getFirstNodeInsideSwitchBlock(
648                            blockWithVariableUsage, variable);
649                        break;
650                    case TokenTypes.LITERAL_TRY:
651                        exprWithVariableUsage =
652                            getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage,
653                                variable);
654                        break;
655                    default:
656                        exprWithVariableUsage = blockWithVariableUsage.getFirstChild();
657                }
658                currentScopeAst = exprWithVariableUsage;
659                variableUsageAst =
660                        Objects.requireNonNullElse(exprWithVariableUsage, blockWithVariableUsage);
661            }
662
663            // If there's no any variable usage, then distance = 0.
664            else if (variableUsageExpressions.isEmpty()) {
665                variableUsageAst = null;
666            }
667            // If variable usage exists in different scopes, then distance =
668            // distance until variable first usage.
669            else {
670                dist++;
671                variableUsageAst = variableUsageExpressions.get(0);
672            }
673        }
674        return new SimpleEntry<>(variableUsageAst, dist);
675    }
676
677    /**
678     * Searches variable usages starting from specified statement.
679     *
680     * @param variableAst Variable that is used.
681     * @param statementAst DetailAST to start searching from.
682     * @return entry which contains list with found expressions that use the variable
683     *     and distance from specified statement to first found expression.
684     */
685    private static Entry<List<DetailAST>, Integer>
686        searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) {
687        final List<DetailAST> variableUsageExpressions = new ArrayList<>();
688        int distance = 0;
689        DetailAST currentStatementAst = statementAst;
690        while (currentStatementAst != null
691                && currentStatementAst.getType() != TokenTypes.RCURLY) {
692            if (currentStatementAst.getFirstChild() != null) {
693                if (isChild(currentStatementAst, variableAst)) {
694                    variableUsageExpressions.add(currentStatementAst);
695                }
696                // If expression doesn't contain variable and this variable
697                // hasn't been met yet, then distance + 1.
698                else if (variableUsageExpressions.isEmpty()
699                        && currentStatementAst.getType() != TokenTypes.VARIABLE_DEF) {
700                    distance++;
701                }
702            }
703            currentStatementAst = currentStatementAst.getNextSibling();
704        }
705        return new SimpleEntry<>(variableUsageExpressions, distance);
706    }
707
708    /**
709     * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable
710     * usage is met only inside the block (not in its declaration!).
711     *
712     * @param block
713     *        Ast node represents FOR, WHILE or DO-WHILE block.
714     * @param variable
715     *        Variable which is checked for content in block.
716     * @return If variable usage is met only inside the block
717     *         (not in its declaration!) then return the first Ast node
718     *         of this block, otherwise - null.
719     */
720    private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks(
721            DetailAST block, DetailAST variable) {
722        DetailAST firstNodeInsideBlock = null;
723
724        if (!isVariableInOperatorExpr(block, variable)) {
725            final DetailAST currentNode;
726
727            // Find currentNode for DO-WHILE block.
728            if (block.getType() == TokenTypes.LITERAL_DO) {
729                currentNode = block.getFirstChild();
730            }
731            // Find currentNode for FOR or WHILE block.
732            else {
733                // Looking for RPAREN ( ')' ) token to mark the end of operator
734                // expression.
735                currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling();
736            }
737
738            final int currentNodeType = currentNode.getType();
739
740            if (currentNodeType != TokenTypes.EXPR) {
741                firstNodeInsideBlock = currentNode;
742            }
743        }
744
745        return firstNodeInsideBlock;
746    }
747
748    /**
749     * Gets first Ast node inside IF block if variable usage is met
750     * only inside the block (not in its declaration!).
751     *
752     * @param block
753     *        Ast node represents IF block.
754     * @param variable
755     *        Variable which is checked for content in block.
756     * @return If variable usage is met only inside the block
757     *         (not in its declaration!) then return the first Ast node
758     *         of this block, otherwise - null.
759     */
760    private static DetailAST getFirstNodeInsideIfBlock(
761            DetailAST block, DetailAST variable) {
762        DetailAST firstNodeInsideBlock = null;
763
764        if (!isVariableInOperatorExpr(block, variable)) {
765            DetailAST currentNode = block.getLastChild();
766            final List<DetailAST> variableUsageExpressions =
767                    new ArrayList<>();
768
769            while (currentNode != null
770                    && currentNode.getType() == TokenTypes.LITERAL_ELSE) {
771                final DetailAST previousNode =
772                        currentNode.getPreviousSibling();
773
774                // Checking variable usage inside IF block.
775                if (isChild(previousNode, variable)) {
776                    variableUsageExpressions.add(previousNode);
777                }
778
779                // Looking into ELSE block, get its first child and analyze it.
780                currentNode = currentNode.getFirstChild();
781
782                if (currentNode.getType() == TokenTypes.LITERAL_IF) {
783                    currentNode = currentNode.getLastChild();
784                }
785                else if (isChild(currentNode, variable)) {
786                    variableUsageExpressions.add(currentNode);
787                    currentNode = null;
788                }
789            }
790
791            // If IF block doesn't include ELSE then analyze variable usage
792            // only inside IF block.
793            if (currentNode != null
794                    && isChild(currentNode, variable)) {
795                variableUsageExpressions.add(currentNode);
796            }
797
798            // If variable usage exists in several related blocks, then
799            // firstNodeInsideBlock = null, otherwise if variable usage exists
800            // only inside one block, then get node from
801            // variableUsageExpressions.
802            if (variableUsageExpressions.size() == 1) {
803                firstNodeInsideBlock = variableUsageExpressions.get(0);
804            }
805        }
806
807        return firstNodeInsideBlock;
808    }
809
810    /**
811     * Gets first Ast node inside SWITCH block if variable usage is met
812     * only inside the block (not in its declaration!).
813     *
814     * @param block
815     *        Ast node represents SWITCH block.
816     * @param variable
817     *        Variable which is checked for content in block.
818     * @return If variable usage is met only inside the block
819     *         (not in its declaration!) then return the first Ast node
820     *         of this block, otherwise - null.
821     */
822    private static DetailAST getFirstNodeInsideSwitchBlock(
823            DetailAST block, DetailAST variable) {
824        final DetailAST currentNode = getFirstCaseGroupOrSwitchRule(block);
825        final List<DetailAST> variableUsageExpressions =
826                new ArrayList<>();
827
828        // Checking variable usage inside all CASE_GROUP and SWITCH_RULE ast's.
829        TokenUtil.forEachChild(block, currentNode.getType(), node -> {
830            final DetailAST lastNodeInCaseGroup =
831                node.getLastChild();
832            if (isChild(lastNodeInCaseGroup, variable)) {
833                variableUsageExpressions.add(lastNodeInCaseGroup);
834            }
835        });
836
837        // If variable usage exists in several related blocks, then
838        // firstNodeInsideBlock = null, otherwise if variable usage exists
839        // only inside one block, then get node from
840        // variableUsageExpressions.
841        DetailAST firstNodeInsideBlock = null;
842        if (variableUsageExpressions.size() == 1) {
843            firstNodeInsideBlock = variableUsageExpressions.get(0);
844        }
845
846        return firstNodeInsideBlock;
847    }
848
849    /**
850     * Helper method for getFirstNodeInsideSwitchBlock to return the first CASE_GROUP or
851     * SWITCH_RULE ast.
852     *
853     * @param block the switch block to check.
854     * @return DetailAST of the first CASE_GROUP or SWITCH_RULE.
855     */
856    private static DetailAST getFirstCaseGroupOrSwitchRule(DetailAST block) {
857        return Optional.ofNullable(block.findFirstToken(TokenTypes.CASE_GROUP))
858            .orElseGet(() -> block.findFirstToken(TokenTypes.SWITCH_RULE));
859    }
860
861    /**
862     * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is
863     * met only inside the block (not in its declaration!).
864     *
865     * @param block
866     *        Ast node represents TRY-CATCH-FINALLY block.
867     * @param variable
868     *        Variable which is checked for content in block.
869     * @return If variable usage is met only inside the block
870     *         (not in its declaration!) then return the first Ast node
871     *         of this block, otherwise - null.
872     */
873    private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks(
874            DetailAST block, DetailAST variable) {
875        DetailAST currentNode = block.getFirstChild();
876        final List<DetailAST> variableUsageExpressions =
877                new ArrayList<>();
878
879        // Checking variable usage inside TRY block.
880        if (isChild(currentNode, variable)) {
881            variableUsageExpressions.add(currentNode);
882        }
883
884        // Switch on CATCH block.
885        currentNode = currentNode.getNextSibling();
886
887        // Checking variable usage inside all CATCH blocks.
888        while (currentNode != null
889                && currentNode.getType() == TokenTypes.LITERAL_CATCH) {
890            final DetailAST catchBlock = currentNode.getLastChild();
891
892            if (isChild(catchBlock, variable)) {
893                variableUsageExpressions.add(catchBlock);
894            }
895            currentNode = currentNode.getNextSibling();
896        }
897
898        // Checking variable usage inside FINALLY block.
899        if (currentNode != null) {
900            final DetailAST finalBlock = currentNode.getLastChild();
901
902            if (isChild(finalBlock, variable)) {
903                variableUsageExpressions.add(finalBlock);
904            }
905        }
906
907        DetailAST variableUsageNode = null;
908
909        // If variable usage exists in several related blocks, then
910        // firstNodeInsideBlock = null, otherwise if variable usage exists
911        // only inside one block, then get node from
912        // variableUsageExpressions.
913        if (variableUsageExpressions.size() == 1) {
914            variableUsageNode = variableUsageExpressions.get(0).getFirstChild();
915        }
916
917        return variableUsageNode;
918    }
919
920    /**
921     * Checks if variable is in operator declaration. For instance:
922     * <pre>
923     * boolean b = true;
924     * if (b) {...}
925     * </pre>
926     * Variable 'b' is in declaration of operator IF.
927     *
928     * @param operator
929     *        Ast node which represents operator.
930     * @param variable
931     *        Variable which is checked for content in operator.
932     * @return true if operator contains variable in its declaration, otherwise
933     *         - false.
934     */
935    private static boolean isVariableInOperatorExpr(
936            DetailAST operator, DetailAST variable) {
937        boolean isVarInOperatorDeclaration = false;
938        final DetailAST openingBracket =
939                operator.findFirstToken(TokenTypes.LPAREN);
940
941        // Get EXPR between brackets
942        DetailAST exprBetweenBrackets = openingBracket.getNextSibling();
943
944        // Look if variable is in operator expression
945        while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) {
946            if (isChild(exprBetweenBrackets, variable)) {
947                isVarInOperatorDeclaration = true;
948                break;
949            }
950            exprBetweenBrackets = exprBetweenBrackets.getNextSibling();
951        }
952
953        // Variable may be met in ELSE declaration
954        // So, check variable usage in these declarations.
955        if (!isVarInOperatorDeclaration) {
956            final DetailAST elseBlock = operator.getLastChild();
957
958            if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) {
959                // Get IF followed by ELSE
960                final DetailAST firstNodeInsideElseBlock = elseBlock.getFirstChild();
961
962                if (firstNodeInsideElseBlock.getType() == TokenTypes.LITERAL_IF) {
963                    isVarInOperatorDeclaration =
964                        isVariableInOperatorExpr(firstNodeInsideElseBlock, variable);
965                }
966            }
967        }
968
969        return isVarInOperatorDeclaration;
970    }
971
972    /**
973     * Checks if Ast node contains given element.
974     *
975     * @param parent
976     *        Node of AST.
977     * @param ast
978     *        Ast element which is checked for content in Ast node.
979     * @return true if Ast element was found in Ast node, otherwise - false.
980     */
981    private static boolean isChild(DetailAST parent, DetailAST ast) {
982        boolean isChild = false;
983        DetailAST curNode = parent.getFirstChild();
984
985        while (curNode != null) {
986            if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) {
987                isChild = true;
988                break;
989            }
990
991            DetailAST toVisit = curNode.getFirstChild();
992            while (toVisit == null) {
993                toVisit = curNode.getNextSibling();
994                curNode = curNode.getParent();
995
996                if (curNode == parent) {
997                    break;
998                }
999            }
1000
1001            curNode = toVisit;
1002        }
1003
1004        return isChild;
1005    }
1006
1007    /**
1008     * Checks if entrance variable is contained in ignored pattern.
1009     *
1010     * @param variable
1011     *        Variable which is checked for content in ignored pattern.
1012     * @return true if variable was found, otherwise - false.
1013     */
1014    private boolean isVariableMatchesIgnorePattern(String variable) {
1015        final Matcher matcher = ignoreVariablePattern.matcher(variable);
1016        return matcher.matches();
1017    }
1018
1019}