View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.AbstractMap.SimpleEntry;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Map.Entry;
26  import java.util.Optional;
27  import java.util.Set;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import com.puppycrawl.tools.checkstyle.StatelessCheck;
32  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
33  import com.puppycrawl.tools.checkstyle.api.DetailAST;
34  import com.puppycrawl.tools.checkstyle.api.FullIdent;
35  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
37  
38  /**
39   * <div>
40   * Checks the distance between declaration of variable and its first usage.
41   * Note: Any additional variables declared or initialized between the declaration and
42   *  the first usage of the said variable are not counted when calculating the distance.
43   * </div>
44   *
45   * @since 5.8
46   */
47  @StatelessCheck
48  public class VariableDeclarationUsageDistanceCheck extends AbstractCheck {
49  
50      /**
51       * Warning message key.
52       */
53      public static final String MSG_KEY = "variable.declaration.usage.distance";
54  
55      /**
56       * Warning message key.
57       */
58      public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend";
59  
60      /**
61       * Default value of distance between declaration of variable and its first
62       * usage.
63       */
64      private static final int DEFAULT_DISTANCE = 3;
65  
66      /** Tokens that should be ignored when calculating usage distance. */
67      private static final Set<Integer> ZERO_DISTANCE_TOKENS = Set.of(
68              TokenTypes.VARIABLE_DEF,
69              TokenTypes.TYPE,
70              TokenTypes.MODIFIERS,
71              TokenTypes.RESOURCE,
72              TokenTypes.EXTENDS_CLAUSE,
73              TokenTypes.IMPLEMENTS_CLAUSE,
74              TokenTypes.TYPE_PARAMETERS,
75              TokenTypes.PARAMETERS,
76              TokenTypes.LITERAL_THROWS
77      );
78  
79      /**
80       * Specify the maximum distance between a variable's declaration and its first usage.
81       * Value should be greater than 0.
82       */
83      private int allowedDistance = DEFAULT_DISTANCE;
84  
85      /**
86       * Define RegExp to ignore distance calculation for variables listed in
87       * this pattern.
88       */
89      private Pattern ignoreVariablePattern = Pattern.compile("");
90  
91      /**
92       * Allow to calculate the distance between a variable's declaration and its first usage
93       * across different scopes.
94       */
95      private boolean validateBetweenScopes;
96  
97      /** Allow to ignore variables with a 'final' modifier. */
98      private boolean ignoreFinal = true;
99  
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 }