001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.Set;
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 * <div>
040 * Checks the distance between declaration of variable and its first usage.
041 * Note: Any additional variables declared or initialized between the declaration and
042 *  the first usage of the said variable are not counted when calculating the distance.
043 * </div>
044 *
045 * @since 5.8
046 */
047@StatelessCheck
048public class VariableDeclarationUsageDistanceCheck extends AbstractCheck {
049
050    /**
051     * Warning message key.
052     */
053    public static final String MSG_KEY = "variable.declaration.usage.distance";
054
055    /**
056     * Warning message key.
057     */
058    public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend";
059
060    /**
061     * Default value of distance between declaration of variable and its first usage.
062     */
063    private static final int DEFAULT_DISTANCE = 3;
064
065    /** Tokens that should be ignored when calculating usage distance. */
066    private static final Set<Integer> ZERO_DISTANCE_TOKENS = Set.of(
067            TokenTypes.VARIABLE_DEF,
068            TokenTypes.TYPE,
069            TokenTypes.MODIFIERS,
070            TokenTypes.RESOURCE,
071            TokenTypes.EXTENDS_CLAUSE,
072            TokenTypes.IMPLEMENTS_CLAUSE,
073            TokenTypes.TYPE_PARAMETERS,
074            TokenTypes.PARAMETERS,
075            TokenTypes.LITERAL_THROWS
076    );
077
078    /** Tokens of type definitions and compact source file that can contain methods. */
079    private static final Set<Integer> TYPE_DEFINITION_TOKENS = Set.of(
080            TokenTypes.CLASS_DEF,
081            TokenTypes.INTERFACE_DEF,
082            TokenTypes.ANNOTATION_DEF,
083            TokenTypes.ENUM_DEF,
084            TokenTypes.RECORD_DEF,
085            TokenTypes.COMPACT_COMPILATION_UNIT
086    );
087
088    /**
089     * Specify the maximum distance between a variable's declaration and its first usage.
090     * Value should be greater than 0.
091     */
092    private int allowedDistance = DEFAULT_DISTANCE;
093
094    /**
095     * Define RegExp to ignore distance calculation for variables listed in this pattern.
096     */
097    private Pattern ignoreVariablePattern = Pattern.compile("");
098
099    /**
100     * Allow to calculate the distance between a variable's declaration and its first usage
101     * across different scopes.
102     */
103    private boolean validateBetweenScopes;
104
105    /** Allow to ignore variables with a 'final' modifier. */
106    private boolean ignoreFinal = true;
107
108    /**
109     * Creates a new {@code VariableDeclarationUsageDistanceCheck} instance.
110     */
111    public VariableDeclarationUsageDistanceCheck() {
112        // no code by default
113    }
114
115    /**
116     * Setter to specify the maximum distance between a variable's declaration and its first usage.
117     * Value should be greater than 0.
118     *
119     * @param allowedDistance
120     *        Allowed distance between declaration of variable and its first usage.
121     * @since 5.8
122     */
123    public void setAllowedDistance(int allowedDistance) {
124        this.allowedDistance = allowedDistance;
125    }
126
127    /**
128     * Setter to define RegExp to ignore distance calculation for variables listed in this pattern.
129     *
130     * @param pattern a pattern.
131     * @since 5.8
132     */
133    public void setIgnoreVariablePattern(Pattern pattern) {
134        ignoreVariablePattern = pattern;
135    }
136
137    /**
138     * Setter to allow to calculate the distance between a variable's declaration
139     * and its first usage across different scopes.
140     *
141     * @param validateBetweenScopes
142     *        Defines if allow to calculate distance between declaration of
143     *        variable and its first usage in different scopes or not.
144     * @since 5.8
145     */
146    public void setValidateBetweenScopes(boolean validateBetweenScopes) {
147        this.validateBetweenScopes = validateBetweenScopes;
148    }
149
150    /**
151     * Setter to allow to ignore variables with a 'final' modifier.
152     *
153     * @param ignoreFinal
154     *        Defines if ignore variables with 'final' modifier or not.
155     * @since 5.8
156     */
157    public void setIgnoreFinal(boolean ignoreFinal) {
158        this.ignoreFinal = ignoreFinal;
159    }
160
161    @Override
162    public int[] getDefaultTokens() {
163        return getRequiredTokens();
164    }
165
166    @Override
167    public int[] getAcceptableTokens() {
168        return getRequiredTokens();
169    }
170
171    @Override
172    public int[] getRequiredTokens() {
173        return new int[] {TokenTypes.VARIABLE_DEF};
174    }
175
176    @Override
177    public void visitToken(DetailAST ast) {
178        final int parentType = ast.getParent().getType();
179        final DetailAST modifiers = ast.getFirstChild();
180
181        if (parentType != TokenTypes.OBJBLOCK
182                && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) {
183            final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT);
184
185            if (!isVariableMatchesIgnorePattern(variable.getText())) {
186                final DetailAST semicolonAst = ast.getNextSibling();
187                final Entry<DetailAST, Integer> entry;
188                if (validateBetweenScopes) {
189                    entry = calculateDistanceBetweenScopes(semicolonAst, variable);
190                }
191                else {
192                    entry = calculateDistanceInSingleScope(semicolonAst, variable);
193                }
194                final DetailAST variableUsageAst = entry.getKey();
195                final int dist = entry.getValue();
196                if (dist > allowedDistance
197                        && !isInitializationSequence(variableUsageAst, variable.getText())) {
198                    if (ignoreFinal) {
199                        log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance);
200                    }
201                    else {
202                        log(ast, MSG_KEY, variable.getText(), dist, allowedDistance);
203                    }
204                }
205            }
206        }
207    }
208
209    /**
210     * Get name of instance whose method is called.
211     *
212     * @param methodCallAst
213     *        DetailAST of METHOD_CALL.
214     * @return name of instance.
215     */
216    private static String getInstanceName(DetailAST methodCallAst) {
217        final String methodCallName =
218                FullIdent.createFullIdentBelow(methodCallAst).getText();
219        final int lastDotIndex = methodCallName.lastIndexOf('.');
220        String instanceName = "";
221        if (lastDotIndex != -1) {
222            instanceName = methodCallName.substring(0, lastDotIndex);
223        }
224        return instanceName;
225    }
226
227    /**
228     * Processes statements until usage of variable to detect sequence of initialization methods.
229     *
230     * @param variableUsageAst
231     *        DetailAST of expression that uses variable named variableName.
232     * @param variableName
233     *        name of considered variable.
234     * @return true if statements between declaration and usage of variable are
235     *         initialization methods.
236     */
237    private static boolean isInitializationSequence(
238            DetailAST variableUsageAst, String variableName) {
239        DetailAST currentAst = variableUsageAst;
240
241        boolean result = true;
242        boolean isUsedVariableDeclarationFound = false;
243        String initInstanceName = "";
244        while (result && !isUsedVariableDeclarationFound && currentAst != null) {
245            if (currentAst.getType() == TokenTypes.EXPR
246                    && currentAst.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
247                final DetailAST methodCallAst = currentAst.getFirstChild();
248                final String instanceName = getInstanceName(methodCallAst);
249                if (instanceName.isEmpty()) {
250                    result = false;
251                }
252                else if (!instanceName.equals(initInstanceName)) {
253                    if (initInstanceName.isEmpty()) {
254                        initInstanceName = instanceName;
255                    }
256                    else {
257                        result = false;
258                    }
259                }
260
261            }
262            else if (currentAst.getType() == TokenTypes.VARIABLE_DEF) {
263                final String currentVariableName =
264                        currentAst.findFirstToken(TokenTypes.IDENT).getText();
265                isUsedVariableDeclarationFound = variableName.equals(currentVariableName);
266            }
267            else {
268                result = Set.of(
269                    TokenTypes.SEMI,
270                    TokenTypes.LCURLY
271                ).contains(currentAst.getType());
272            }
273
274            currentAst = getNextNodeToCheck(currentAst);
275        }
276        return result;
277    }
278
279    /**
280     * Returns the next node to check for initialization sequence.
281     *
282     * @param currentAst The current node.
283     * @return The next node to check for initialization sequence.
284     */
285    private static DetailAST getNextNodeToCheck(DetailAST currentAst) {
286        final DetailAST nextAst;
287
288        if (currentAst.getPreviousSibling() != null) {
289            nextAst = currentAst.getPreviousSibling();
290        }
291        else {
292            // go up the tree
293            final DetailAST predecessor = getFirstPredecessorOfTypes(
294                Set.of(
295                    TokenTypes.SLIST,
296                    TokenTypes.OBJBLOCK
297                ), currentAst);
298
299            if (predecessor.getType() == TokenTypes.SLIST) {
300                nextAst = getNextNodeToCheckBetweenScopesSlist(predecessor);
301            }
302            else {
303                nextAst = predecessor.getParent().getPreviousSibling();
304            }
305        }
306        return nextAst;
307    }
308
309    /**
310     * Returns the next node to check for initialization sequence when the
311     * current node is a direct child of SLIST.
312     *
313     * @param ast The SLIST node which is the parent of the current node.
314     * @return The next node to check for initialization sequence.
315     */
316    private static DetailAST getNextNodeToCheckBetweenScopesSlist(DetailAST ast) {
317        return switch (ast.getParent().getType()) {
318            case TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_CATCH,
319                 TokenTypes.LITERAL_FINALLY, TokenTypes.CASE_GROUP ->
320                ast.getParent().getParent().getPreviousSibling();
321            case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE,
322                 TokenTypes.LITERAL_DO, TokenTypes.LITERAL_IF,
323                 TokenTypes.LITERAL_SYNCHRONIZED ->
324                ast.getParent().getPreviousSibling();
325            case TokenTypes.METHOD_DEF, TokenTypes.INSTANCE_INIT ->
326                getFirstPredecessorOfTypes(TYPE_DEFINITION_TOKENS, ast).getPreviousSibling();
327            default -> ast.getPreviousSibling();
328        };
329    }
330
331    /**
332     * Returns the AST node of the specified types that is the closest predecessor
333     * of the specified node.
334     *
335     * @param types The set of types of the predecessor to search for.
336     * @param ast The AST node for which predecessor is searched.
337     * @return The closest predecessor of one of the specified types; null if no such node exists.
338     */
339    private static DetailAST getFirstPredecessorOfTypes(Set<Integer> types, DetailAST ast) {
340        DetailAST current = ast;
341        while (!types.contains(current.getType())) {
342            current = current.getParent();
343        }
344        return current;
345    }
346
347    /**
348     * Calculates distance between declaration of variable and its first usage in single scope.
349     *
350     * @param semicolonAst
351     *        Regular node of Ast which is checked for content of checking variable.
352     * @param variableIdentAst
353     *        Variable which distance is calculated for.
354     * @return entry which contains expression with variable usage and distance.
355     *         If variable usage is not found, then the expression node is null,
356     *         although the distance can be greater than zero.
357     */
358    private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
359            DetailAST semicolonAst, DetailAST variableIdentAst) {
360        int dist = 0;
361        boolean firstUsageFound = false;
362        DetailAST currentAst = semicolonAst;
363        DetailAST variableUsageAst = null;
364
365        while (!firstUsageFound && currentAst != null) {
366            if (currentAst.getFirstChild() != null) {
367                if (isChild(currentAst, variableIdentAst)) {
368                    dist = getDistToVariableUsageInChildNode(currentAst, dist);
369                    variableUsageAst = currentAst;
370                    firstUsageFound = true;
371                }
372                else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
373                    dist++;
374                }
375            }
376            currentAst = currentAst.getNextSibling();
377        }
378
379        return new SimpleEntry<>(variableUsageAst, dist);
380    }
381
382    /**
383     * Returns the distance to variable usage for in the child node.
384     *
385     * @param childNode child node.
386     * @param currentDistToVarUsage current distance to the variable usage.
387     * @return the distance to variable usage for in the child node.
388     */
389    private static int getDistToVariableUsageInChildNode(DetailAST childNode,
390                                                         int currentDistToVarUsage) {
391        return switch (childNode.getType()) {
392            case TokenTypes.SLIST -> 0;
393            case TokenTypes.LITERAL_FOR,
394                 TokenTypes.LITERAL_WHILE,
395                 TokenTypes.LITERAL_DO,
396                 TokenTypes.LITERAL_IF,
397                 TokenTypes.LITERAL_TRY -> currentDistToVarUsage + 1;
398            default -> {
399                if (childNode.findFirstToken(TokenTypes.SLIST) == null) {
400                    yield currentDistToVarUsage + 1;
401                }
402                yield 0;
403            }
404        };
405    }
406
407    /**
408     * Calculates distance between declaration of variable and its first usage in multiple scopes.
409     *
410     * @param ast
411     *        Regular node of Ast which is checked for content of checking variable.
412     * @param variable
413     *        Variable which distance is calculated for.
414     * @return entry which contains expression with variable usage and distance.
415     */
416    private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes(
417            DetailAST ast, DetailAST variable) {
418        int dist = 0;
419        DetailAST currentScopeAst = ast;
420        DetailAST variableUsageAst = null;
421        while (currentScopeAst != null) {
422            final Entry<List<DetailAST>, Integer> searchResult =
423                    searchVariableUsageExpressions(variable, currentScopeAst);
424
425            currentScopeAst = null;
426
427            final List<DetailAST> variableUsageExpressions = searchResult.getKey();
428            dist += searchResult.getValue();
429
430            // If variable usage exists in a single scope, then look into
431            // this scope and count distance until variable usage.
432            if (variableUsageExpressions.size() == 1) {
433                final DetailAST blockWithVariableUsage = variableUsageExpressions.getFirst();
434                currentScopeAst = switch (blockWithVariableUsage.getType()) {
435                    case TokenTypes.VARIABLE_DEF, TokenTypes.EXPR -> {
436                        dist++;
437                        yield null;
438                    }
439                    case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
440                        getFirstNodeInsideForWhileDoWhileBlocks(blockWithVariableUsage, variable);
441                    case TokenTypes.LITERAL_IF ->
442                        getFirstNodeInsideIfBlock(blockWithVariableUsage, variable);
443                    case TokenTypes.LITERAL_SWITCH ->
444                        getFirstNodeInsideSwitchBlock(blockWithVariableUsage, variable);
445                    case TokenTypes.LITERAL_TRY ->
446                        getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage, variable);
447                    default -> blockWithVariableUsage.getFirstChild();
448                };
449                variableUsageAst = blockWithVariableUsage;
450            }
451
452            // If there's no any variable usage, then distance = 0.
453            else if (variableUsageExpressions.isEmpty()) {
454                variableUsageAst = null;
455                dist = 0;
456            }
457            // If variable usage exists in different scopes, then distance =
458            // distance until variable first usage.
459            else {
460                dist++;
461                variableUsageAst = variableUsageExpressions.getFirst();
462            }
463        }
464        return new SimpleEntry<>(variableUsageAst, dist);
465    }
466
467    /**
468     * Searches variable usages starting from specified statement.
469     *
470     * @param variableAst Variable that is used.
471     * @param statementAst DetailAST to start searching from.
472     * @return entry which contains list with found expressions that use the variable
473     *     and distance from specified statement to first found expression.
474     */
475    private static Entry<List<DetailAST>, Integer>
476        searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) {
477        final List<DetailAST> variableUsageExpressions = new ArrayList<>();
478        int distance = 0;
479        DetailAST currentStatementAst = statementAst;
480        while (currentStatementAst != null) {
481            if (currentStatementAst.getFirstChild() != null) {
482                if (isChild(currentStatementAst, variableAst)) {
483                    variableUsageExpressions.add(currentStatementAst);
484                }
485                // If expression hasn't been met yet, then distance + 1.
486                else if (variableUsageExpressions.isEmpty()
487                        && !isZeroDistanceToken(currentStatementAst.getType())) {
488                    distance++;
489                }
490            }
491            currentStatementAst = currentStatementAst.getNextSibling();
492        }
493        return new SimpleEntry<>(variableUsageExpressions, distance);
494    }
495
496    /**
497     * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable
498     * usage is met only inside the block (not in its declaration!).
499     *
500     * @param block
501     *        Ast node represents FOR, WHILE or DO-WHILE block.
502     * @param variable
503     *        Variable which is checked for content in block.
504     * @return If variable usage is met only inside the block
505     *         (not in its declaration!) then return the first Ast node
506     *         of this block, otherwise - null.
507     */
508    private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks(
509            DetailAST block, DetailAST variable) {
510        DetailAST firstNodeInsideBlock = null;
511
512        if (!isVariableInOperatorExpr(block, variable)) {
513            final DetailAST currentNode;
514
515            // Find currentNode for DO-WHILE block.
516            if (block.getType() == TokenTypes.LITERAL_DO) {
517                currentNode = block.getFirstChild();
518            }
519            // Find currentNode for FOR or WHILE block.
520            else {
521                // Looking for RPAREN ( ')' ) token to mark the end of operator
522                // expression.
523                currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling();
524            }
525
526            final int currentNodeType = currentNode.getType();
527
528            if (currentNodeType != TokenTypes.EXPR) {
529                firstNodeInsideBlock = currentNode;
530            }
531        }
532
533        return firstNodeInsideBlock;
534    }
535
536    /**
537     * Gets first Ast node inside IF block if variable usage is met
538     * only inside the block (not in its declaration!).
539     *
540     * @param block
541     *        Ast node represents IF block.
542     * @param variable
543     *        Variable which is checked for content in block.
544     * @return If variable usage is met only inside the block
545     *         (not in its declaration!) then return the first Ast node
546     *         of this block, otherwise - null.
547     */
548    private static DetailAST getFirstNodeInsideIfBlock(
549            DetailAST block, DetailAST variable) {
550        DetailAST firstNodeInsideBlock = null;
551
552        if (!isVariableInOperatorExpr(block, variable)) {
553            final Optional<DetailAST> slistToken = TokenUtil
554                .findFirstTokenByPredicate(block, token -> token.getType() == TokenTypes.SLIST);
555            final DetailAST lastNode = block.getLastChild();
556            DetailAST previousNode = lastNode.getPreviousSibling();
557
558            if (slistToken.isEmpty()
559                && lastNode.getType() == TokenTypes.LITERAL_ELSE) {
560
561                // Is if statement without '{}' and has a following else branch,
562                // then change previousNode to the if statement body.
563                previousNode = previousNode.getPreviousSibling();
564            }
565
566            final List<DetailAST> variableUsageExpressions = new ArrayList<>();
567            if (isChild(previousNode, variable)) {
568                variableUsageExpressions.add(previousNode);
569            }
570
571            if (isChild(lastNode, variable)) {
572                variableUsageExpressions.add(lastNode);
573            }
574
575            // If variable usage exists in several related blocks, then
576            // firstNodeInsideBlock = null, otherwise if variable usage exists
577            // only inside one block, then get node from
578            // variableUsageExpressions.
579            if (variableUsageExpressions.size() == 1) {
580                firstNodeInsideBlock = variableUsageExpressions.getFirst();
581            }
582        }
583
584        return firstNodeInsideBlock;
585    }
586
587    /**
588     * Gets first Ast node inside SWITCH block if variable usage is met
589     * only inside the block (not in its declaration!).
590     *
591     * @param block
592     *        Ast node represents SWITCH block.
593     * @param variable
594     *        Variable which is checked for content in block.
595     * @return If variable usage is met only inside the block
596     *         (not in its declaration!) then return the first Ast node
597     *         of this block, otherwise - null.
598     */
599    private static DetailAST getFirstNodeInsideSwitchBlock(
600            DetailAST block, DetailAST variable) {
601        final List<DetailAST> variableUsageExpressions =
602                getVariableUsageExpressionsInsideSwitchBlock(block, variable);
603
604        // If variable usage exists in several related blocks, then
605        // firstNodeInsideBlock = null, otherwise if variable usage exists
606        // only inside one block, then get node from
607        // variableUsageExpressions.
608        DetailAST firstNodeInsideBlock = null;
609        if (variableUsageExpressions.size() == 1) {
610            firstNodeInsideBlock = variableUsageExpressions.getFirst();
611        }
612
613        return firstNodeInsideBlock;
614    }
615
616    /**
617     * Helper method for getFirstNodeInsideSwitchBlock to return all variable
618     * usage expressions inside a given switch block.
619     *
620     * @param block the switch block to check.
621     * @param variable variable which is checked for in switch block.
622     * @return List of usages or empty list if none are found.
623     */
624    private static List<DetailAST> getVariableUsageExpressionsInsideSwitchBlock(DetailAST block,
625                                                                            DetailAST variable) {
626        final Optional<DetailAST> firstToken = TokenUtil.findFirstTokenByPredicate(block, child -> {
627            return child.getType() == TokenTypes.SWITCH_RULE
628                    || child.getType() == TokenTypes.CASE_GROUP;
629        });
630
631        final List<DetailAST> variableUsageExpressions = new ArrayList<>();
632
633        firstToken.ifPresent(token -> {
634            TokenUtil.forEachChild(block, token.getType(), child -> {
635                final DetailAST lastNodeInCaseGroup = child.getLastChild();
636                if (isChild(lastNodeInCaseGroup, variable)) {
637                    variableUsageExpressions.add(lastNodeInCaseGroup);
638                }
639            });
640        });
641
642        return variableUsageExpressions;
643    }
644
645    /**
646     * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is
647     * met only inside the block (not in its declaration!).
648     *
649     * @param block
650     *        Ast node represents TRY-CATCH-FINALLY block.
651     * @param variable
652     *        Variable which is checked for content in block.
653     * @return If variable usage is met only inside the block
654     *         (not in its declaration!) then return the first Ast node
655     *         of this block, otherwise - null.
656     */
657    private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks(
658            DetailAST block, DetailAST variable) {
659        DetailAST variableUsageNode = null;
660
661        final DetailAST resourceSpec = block.findFirstToken(TokenTypes.RESOURCE_SPECIFICATION);
662        if (resourceSpec == null || !isVariableInOperatorExpr(resourceSpec, variable)) {
663            DetailAST currentNode = block.getFirstChild();
664            // Skip resource specification if exists.
665            if (currentNode.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
666                currentNode = currentNode.getNextSibling();
667            }
668
669            final List<DetailAST> variableUsageExpressions = new ArrayList<>();
670            // Checking variable usage inside TRY block.
671            if (isChild(currentNode, variable)) {
672                variableUsageExpressions.add(currentNode);
673            }
674
675            // Switch on CATCH block.
676            currentNode = currentNode.getNextSibling();
677
678            // Checking variable usage inside all CATCH and FINALLY blocks.
679            while (currentNode != null) {
680                final DetailAST catchOrFinallyBlock = currentNode.findFirstToken(TokenTypes.SLIST);
681
682                if (isChild(catchOrFinallyBlock, variable)) {
683                    variableUsageExpressions.add(catchOrFinallyBlock);
684                }
685                currentNode = currentNode.getNextSibling();
686            }
687
688            // If variable usage exists in several related blocks, then
689            // firstNodeInsideBlock = null, otherwise if variable usage exists
690            // only inside one block, then get node from
691            // variableUsageExpressions.
692            if (variableUsageExpressions.size() == 1) {
693                variableUsageNode = variableUsageExpressions.getFirst();
694            }
695        }
696
697        return variableUsageNode;
698    }
699
700    /**
701     * Checks if variable is in operator declaration. For instance:
702     * {@snippet lang="text" :
703     * boolean b = true;
704     * if (b) {...}
705     * }
706     * Variable 'b' is in declaration of operator IF.
707     *
708     * @param operator
709     *        Ast node which represents operator.
710     * @param variable
711     *        Variable which is checked for content in operator.
712     * @return true if operator contains variable in its declaration, otherwise - false.
713     */
714    private static boolean isVariableInOperatorExpr(
715            DetailAST operator, DetailAST variable) {
716        boolean isVarInOperatorDeclaration = false;
717
718        DetailAST ast = operator.findFirstToken(TokenTypes.LPAREN);
719
720        // Look if variable is in operator expression
721        while (ast.getType() != TokenTypes.RPAREN) {
722            if (isChild(ast, variable)) {
723                isVarInOperatorDeclaration = true;
724                break;
725            }
726            ast = ast.getNextSibling();
727        }
728
729        return isVarInOperatorDeclaration;
730    }
731
732    /**
733     * Checks if Ast node contains given element.
734     *
735     * @param parent
736     *        Node of AST.
737     * @param ast
738     *        Ast element which is checked for content in Ast node.
739     * @return true if Ast element was found in Ast node, otherwise - false.
740     */
741    private static boolean isChild(DetailAST parent, DetailAST ast) {
742        boolean isChild = false;
743        DetailAST curNode = parent.getFirstChild();
744
745        while (curNode != null) {
746            if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) {
747                isChild = true;
748                break;
749            }
750
751            DetailAST toVisit = curNode.getFirstChild();
752            while (toVisit == null) {
753                toVisit = curNode.getNextSibling();
754                curNode = curNode.getParent();
755
756                if (curNode == parent) {
757                    break;
758                }
759            }
760
761            curNode = toVisit;
762        }
763
764        return isChild;
765    }
766
767    /**
768     * Checks if entrance variable is contained in ignored pattern.
769     *
770     * @param variable
771     *        Variable which is checked for content in ignored pattern.
772     * @return true if variable was found, otherwise - false.
773     */
774    private boolean isVariableMatchesIgnorePattern(String variable) {
775        final Matcher matcher = ignoreVariablePattern.matcher(variable);
776        return matcher.matches();
777    }
778
779    /**
780     * Check if the token should be ignored for distance counting.
781     * For example,
782     * {@snippet lang="text" :
783     *     try (final AutoCloseable t = new java.io.StringReader(a);) {
784     *     }
785     * }
786     * final is a zero-distance token and should be ignored for distance counting.
787     * {@snippet lang="text" :
788     *     class Table implements Comparator<Integer>{
789     *     }
790     * }
791     * An inner class may be defined. Both tokens implements and extends are zero-distance tokens.
792     * {@snippet lang="text" :
793     *     public int method(Object b){
794     *     }
795     * }
796     * public is a modifier and zero-distance token. int is a type and zero-distance token.
797     *
798     * @param type
799     *        Token type of the ast node.
800     * @return true if it should be ignored for distance counting, otherwise false.
801     */
802    private static boolean isZeroDistanceToken(int type) {
803        return ZERO_DISTANCE_TOKENS.contains(type);
804    }
805
806}