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
062     * usage.
063     */
064    private static final int DEFAULT_DISTANCE = 3;
065
066    /** Tokens that should be ignored when calculating usage distance. */
067    private static final Set<Integer> ZERO_DISTANCE_TOKENS = Set.of(
068            TokenTypes.VARIABLE_DEF,
069            TokenTypes.TYPE,
070            TokenTypes.MODIFIERS,
071            TokenTypes.RESOURCE,
072            TokenTypes.EXTENDS_CLAUSE,
073            TokenTypes.IMPLEMENTS_CLAUSE,
074            TokenTypes.TYPE_PARAMETERS,
075            TokenTypes.PARAMETERS,
076            TokenTypes.LITERAL_THROWS
077    );
078
079    /**
080     * Specify the maximum distance between a variable's declaration and its first usage.
081     * Value should be greater than 0.
082     */
083    private int allowedDistance = DEFAULT_DISTANCE;
084
085    /**
086     * Define RegExp to ignore distance calculation for variables listed in
087     * this pattern.
088     */
089    private Pattern ignoreVariablePattern = Pattern.compile("");
090
091    /**
092     * Allow to calculate the distance between a variable's declaration and its first usage
093     * across different scopes.
094     */
095    private boolean validateBetweenScopes;
096
097    /** Allow to ignore variables with a 'final' modifier. */
098    private boolean ignoreFinal = true;
099
100    /**
101     * Creates a new {@code VariableDeclarationUsageDistanceCheck} instance.
102     */
103    public VariableDeclarationUsageDistanceCheck() {
104        // no code by default
105    }
106
107    /**
108     * Setter to specify the maximum distance between a variable's declaration and its first usage.
109     * Value should be greater than 0.
110     *
111     * @param allowedDistance
112     *        Allowed distance between declaration of variable and its first
113     *        usage.
114     * @since 5.8
115     */
116    public void setAllowedDistance(int allowedDistance) {
117        this.allowedDistance = allowedDistance;
118    }
119
120    /**
121     * Setter to define RegExp to ignore distance calculation for variables listed in this pattern.
122     *
123     * @param pattern a pattern.
124     * @since 5.8
125     */
126    public void setIgnoreVariablePattern(Pattern pattern) {
127        ignoreVariablePattern = pattern;
128    }
129
130    /**
131     * Setter to allow to calculate the distance between a variable's declaration
132     * and its first usage across different scopes.
133     *
134     * @param validateBetweenScopes
135     *        Defines if allow to calculate distance between declaration of
136     *        variable and its first usage in different scopes or not.
137     * @since 5.8
138     */
139    public void setValidateBetweenScopes(boolean validateBetweenScopes) {
140        this.validateBetweenScopes = validateBetweenScopes;
141    }
142
143    /**
144     * Setter to allow to ignore variables with a 'final' modifier.
145     *
146     * @param ignoreFinal
147     *        Defines if ignore variables with 'final' modifier or not.
148     * @since 5.8
149     */
150    public void setIgnoreFinal(boolean ignoreFinal) {
151        this.ignoreFinal = ignoreFinal;
152    }
153
154    @Override
155    public int[] getDefaultTokens() {
156        return getRequiredTokens();
157    }
158
159    @Override
160    public int[] getAcceptableTokens() {
161        return getRequiredTokens();
162    }
163
164    @Override
165    public int[] getRequiredTokens() {
166        return new int[] {TokenTypes.VARIABLE_DEF};
167    }
168
169    @Override
170    public void visitToken(DetailAST ast) {
171        final int parentType = ast.getParent().getType();
172        final DetailAST modifiers = ast.getFirstChild();
173
174        if (parentType != TokenTypes.OBJBLOCK
175                && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) {
176            final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT);
177
178            if (!isVariableMatchesIgnorePattern(variable.getText())) {
179                final DetailAST semicolonAst = ast.getNextSibling();
180                final Entry<DetailAST, Integer> entry;
181                if (validateBetweenScopes) {
182                    entry = calculateDistanceBetweenScopes(semicolonAst, variable);
183                }
184                else {
185                    entry = calculateDistanceInSingleScope(semicolonAst, variable);
186                }
187                final DetailAST variableUsageAst = entry.getKey();
188                final int dist = entry.getValue();
189                if (dist > allowedDistance
190                        && !isInitializationSequence(variableUsageAst, variable.getText())) {
191                    if (ignoreFinal) {
192                        log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance);
193                    }
194                    else {
195                        log(ast, MSG_KEY, variable.getText(), dist, allowedDistance);
196                    }
197                }
198            }
199        }
200    }
201
202    /**
203     * Get name of instance whose method is called.
204     *
205     * @param methodCallAst
206     *        DetailAST of METHOD_CALL.
207     * @return name of instance.
208     */
209    private static String getInstanceName(DetailAST methodCallAst) {
210        final String methodCallName =
211                FullIdent.createFullIdentBelow(methodCallAst).getText();
212        final int lastDotIndex = methodCallName.lastIndexOf('.');
213        String instanceName = "";
214        if (lastDotIndex != -1) {
215            instanceName = methodCallName.substring(0, lastDotIndex);
216        }
217        return instanceName;
218    }
219
220    /**
221     * Processes statements until usage of variable to detect sequence of
222     * initialization methods.
223     *
224     * @param variableUsageAst
225     *        DetailAST of expression that uses variable named variableName.
226     * @param variableName
227     *        name of considered variable.
228     * @return true if statements between declaration and usage of variable are
229     *         initialization methods.
230     */
231    private static boolean isInitializationSequence(
232            DetailAST variableUsageAst, String variableName) {
233        DetailAST currentAst = variableUsageAst;
234
235        boolean result = true;
236        boolean isUsedVariableDeclarationFound = false;
237        String initInstanceName = "";
238        while (result && !isUsedVariableDeclarationFound && currentAst != null) {
239            if (currentAst.getType() == TokenTypes.EXPR
240                    && currentAst.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
241                final DetailAST methodCallAst = currentAst.getFirstChild();
242                final String instanceName = getInstanceName(methodCallAst);
243                if (instanceName.isEmpty()) {
244                    result = false;
245                }
246                else if (!instanceName.equals(initInstanceName)) {
247                    if (initInstanceName.isEmpty()) {
248                        initInstanceName = instanceName;
249                    }
250                    else {
251                        result = false;
252                    }
253                }
254
255            }
256            else if (currentAst.getType() == TokenTypes.VARIABLE_DEF) {
257                final String currentVariableName =
258                        currentAst.findFirstToken(TokenTypes.IDENT).getText();
259                isUsedVariableDeclarationFound = variableName.equals(currentVariableName);
260            }
261            else {
262                result = Set.of(
263                    TokenTypes.SEMI,
264                    TokenTypes.LCURLY
265                ).contains(currentAst.getType());
266            }
267
268            currentAst = getNextNodeToCheck(currentAst);
269        }
270        return result;
271    }
272
273    /**
274     * Returns the next node to check for initialization sequence.
275     *
276     * @param currentAst The current node.
277     * @return The next node to check for initialization sequence.
278     */
279    private static DetailAST getNextNodeToCheck(DetailAST currentAst) {
280        final DetailAST nextAst;
281
282        if (currentAst.getPreviousSibling() != null) {
283            nextAst = currentAst.getPreviousSibling();
284        }
285        else {
286            // go up the tree
287            final DetailAST predecessor = getFirstPredecessorOfTypes(
288                Set.of(
289                    TokenTypes.SLIST,
290                    TokenTypes.OBJBLOCK
291                ), currentAst);
292
293            if (predecessor.getType() == TokenTypes.SLIST) {
294                nextAst = getNextNodeToCheckBetweenScopesSlist(predecessor);
295            }
296            else {
297                nextAst = predecessor.getParent().getPreviousSibling();
298            }
299        }
300        return nextAst;
301    }
302
303    /**
304     * Returns the next node to check for initialization sequence when the
305     * current node is a direct child of SLIST.
306     *
307     * @param ast The SLIST node which is the parent of the current node.
308     * @return The next node to check for initialization sequence.
309     */
310    private static DetailAST getNextNodeToCheckBetweenScopesSlist(DetailAST ast) {
311        return switch (ast.getParent().getType()) {
312            case TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_CATCH,
313                 TokenTypes.LITERAL_FINALLY, TokenTypes.CASE_GROUP ->
314                ast.getParent().getParent().getPreviousSibling();
315            case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE,
316                 TokenTypes.LITERAL_DO, TokenTypes.LITERAL_IF,
317                 TokenTypes.LITERAL_SYNCHRONIZED ->
318                ast.getParent().getPreviousSibling();
319            case TokenTypes.METHOD_DEF, TokenTypes.INSTANCE_INIT ->
320                getFirstPredecessorOfTypes(
321                Set.of(TokenTypes.CLASS_DEF), ast).getPreviousSibling();
322            default -> ast.getPreviousSibling();
323        };
324    }
325
326    /**
327     * Returns the AST node of the specified types that is the closest predecessor
328     * of the specified node.
329     *
330     * @param types The set of types of the predecessor to search for.
331     * @param ast The AST node for which predecessor is searched.
332     * @return The closest predecessor of one of the specified types;
333     *         null if no such node exists.
334     */
335    private static DetailAST getFirstPredecessorOfTypes(Set<Integer> types, DetailAST ast) {
336        DetailAST current = ast;
337        while (!types.contains(current.getType())) {
338            current = current.getParent();
339        }
340        return current;
341    }
342
343    /**
344     * Calculates distance between declaration of variable and its first usage
345     * in single scope.
346     *
347     * @param semicolonAst
348     *        Regular node of Ast which is checked for content of checking
349     *        variable.
350     * @param variableIdentAst
351     *        Variable which distance is calculated for.
352     * @return entry which contains expression with variable usage and distance.
353     *         If variable usage is not found, then the expression node is null,
354     *         although the distance can be greater than zero.
355     */
356    private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
357            DetailAST semicolonAst, DetailAST variableIdentAst) {
358        int dist = 0;
359        boolean firstUsageFound = false;
360        DetailAST currentAst = semicolonAst;
361        DetailAST variableUsageAst = null;
362
363        while (!firstUsageFound && currentAst != null) {
364            if (currentAst.getFirstChild() != null) {
365                if (isChild(currentAst, variableIdentAst)) {
366                    dist = getDistToVariableUsageInChildNode(currentAst, dist);
367                    variableUsageAst = currentAst;
368                    firstUsageFound = true;
369                }
370                else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
371                    dist++;
372                }
373            }
374            currentAst = currentAst.getNextSibling();
375        }
376
377        return new SimpleEntry<>(variableUsageAst, dist);
378    }
379
380    /**
381     * Returns the distance to variable usage for in the child node.
382     *
383     * @param childNode child node.
384     * @param currentDistToVarUsage current distance to the variable usage.
385     * @return the distance to variable usage for in the child node.
386     */
387    private static int getDistToVariableUsageInChildNode(DetailAST childNode,
388                                                         int currentDistToVarUsage) {
389        return switch (childNode.getType()) {
390            case TokenTypes.SLIST -> 0;
391            case TokenTypes.LITERAL_FOR,
392                 TokenTypes.LITERAL_WHILE,
393                 TokenTypes.LITERAL_DO,
394                 TokenTypes.LITERAL_IF,
395                 TokenTypes.LITERAL_TRY -> currentDistToVarUsage + 1;
396            default -> {
397                if (childNode.findFirstToken(TokenTypes.SLIST) == null) {
398                    yield currentDistToVarUsage + 1;
399                }
400                yield 0;
401            }
402        };
403    }
404
405    /**
406     * Calculates distance between declaration of variable and its first usage
407     * in multiple scopes.
408     *
409     * @param ast
410     *        Regular node of Ast which is checked for content of checking
411     *        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
713     *         - false.
714     */
715    private static boolean isVariableInOperatorExpr(
716            DetailAST operator, DetailAST variable) {
717        boolean isVarInOperatorDeclaration = false;
718
719        DetailAST ast = operator.findFirstToken(TokenTypes.LPAREN);
720
721        // Look if variable is in operator expression
722        while (ast.getType() != TokenTypes.RPAREN) {
723            if (isChild(ast, variable)) {
724                isVarInOperatorDeclaration = true;
725                break;
726            }
727            ast = ast.getNextSibling();
728        }
729
730        return isVarInOperatorDeclaration;
731    }
732
733    /**
734     * Checks if Ast node contains given element.
735     *
736     * @param parent
737     *        Node of AST.
738     * @param ast
739     *        Ast element which is checked for content in Ast node.
740     * @return true if Ast element was found in Ast node, otherwise - false.
741     */
742    private static boolean isChild(DetailAST parent, DetailAST ast) {
743        boolean isChild = false;
744        DetailAST curNode = parent.getFirstChild();
745
746        while (curNode != null) {
747            if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) {
748                isChild = true;
749                break;
750            }
751
752            DetailAST toVisit = curNode.getFirstChild();
753            while (toVisit == null) {
754                toVisit = curNode.getNextSibling();
755                curNode = curNode.getParent();
756
757                if (curNode == parent) {
758                    break;
759                }
760            }
761
762            curNode = toVisit;
763        }
764
765        return isChild;
766    }
767
768    /**
769     * Checks if entrance variable is contained in ignored pattern.
770     *
771     * @param variable
772     *        Variable which is checked for content in ignored pattern.
773     * @return true if variable was found, otherwise - false.
774     */
775    private boolean isVariableMatchesIgnorePattern(String variable) {
776        final Matcher matcher = ignoreVariablePattern.matcher(variable);
777        return matcher.matches();
778    }
779
780    /**
781     * Check if the token should be ignored for distance counting.
782     * For example,
783     * {@snippet lang="text" :
784     *     try (final AutoCloseable t = new java.io.StringReader(a);) {
785     *     }
786     * }
787     * final is a zero-distance token and should be ignored for distance counting.
788     * {@snippet lang="text" :
789     *     class Table implements Comparator<Integer>{
790     *     }
791     * }
792     * An inner class may be defined. Both tokens implements and extends
793     * are zero-distance tokens.
794     * {@snippet lang="text" :
795     *     public int method(Object b){
796     *     }
797     * }
798     * public is a modifier and zero-distance token. int is a type and
799     * zero-distance token.
800     *
801     * @param type
802     *        Token type of the ast node.
803     * @return true if it should be ignored for distance counting, otherwise false.
804     */
805    private static boolean isZeroDistanceToken(int type) {
806        return ZERO_DISTANCE_TOKENS.contains(type);
807    }
808
809}