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 }