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}