001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.Objects; 027import java.util.Optional; 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 * <p> 040 * Checks the distance between declaration of variable and its first usage. 041 * Note : Variable declaration/initialization statements are not counted while calculating length. 042 * </p> 043 * <ul> 044 * <li> 045 * Property {@code allowedDistance} - Specify distance between declaration 046 * of variable and its first usage. Values should be greater than 0. 047 * Type is {@code int}. 048 * Default value is {@code 3}. 049 * </li> 050 * <li> 051 * Property {@code ignoreVariablePattern} - Define RegExp to ignore distance calculation 052 * for variables listed in this pattern. 053 * Type is {@code java.util.regex.Pattern}. 054 * Default value is {@code ""}. 055 * </li> 056 * <li> 057 * Property {@code validateBetweenScopes} - Allow to calculate the distance between 058 * declaration of variable and its first usage in the different scopes. 059 * Type is {@code boolean}. 060 * Default value is {@code false}. 061 * </li> 062 * <li> 063 * Property {@code ignoreFinal} - Allow to ignore variables with a 'final' modifier. 064 * Type is {@code boolean}. 065 * Default value is {@code true}. 066 * </li> 067 * </ul> 068 * <p> 069 * To configure the check with default config: 070 * </p> 071 * <pre> 072 * <module name="VariableDeclarationUsageDistance"/> 073 * </pre> 074 * <p>Example:</p> 075 * <pre> 076 * public class Test { 077 * 078 * public void foo1() { 079 * int num; // violation, distance = 4 080 * final int PI; // OK, final variables not checked 081 * System.out.println("Statement 1"); 082 * System.out.println("Statement 2"); 083 * System.out.println("Statement 3"); 084 * num = 1; 085 * PI = 3.14; 086 * } 087 * 088 * public void foo2() { 089 * int a; // OK, used in different scope 090 * int b; // OK, used in different scope 091 * int count = 0; // OK, used in different scope 092 * 093 * { 094 * System.out.println("Inside inner scope"); 095 * a = 1; 096 * b = 2; 097 * count++; 098 * } 099 * } 100 * } 101 * </pre> 102 * <p> 103 * Check can detect a block of initialization methods. If a variable is used in 104 * such a block and there are no other statements after variable declaration, then distance = 1. 105 * </p> 106 * <p>Case #1:</p> 107 * <pre> 108 * int minutes = 5; 109 * Calendar cal = Calendar.getInstance(); 110 * cal.setTimeInMillis(timeNow); 111 * cal.set(Calendar.SECOND, 0); 112 * cal.set(Calendar.MILLISECOND, 0); 113 * cal.set(Calendar.HOUR_OF_DAY, hh); 114 * cal.set(Calendar.MINUTE, minutes); 115 * </pre> 116 * <p> 117 * The distance for the variable "minutes" is 1 even 118 * though this variable is used in the fifth method's call. 119 * </p> 120 * <p>Case #2:</p> 121 * <pre> 122 * int minutes = 5; 123 * Calendar cal = Calendar.getInstance(); 124 * cal.setTimeInMillis(timeNow); 125 * cal.set(Calendar.SECOND, 0); 126 * cal.set(Calendar.MILLISECOND, 0); 127 * <i>System.out.println(cal);</i> 128 * cal.set(Calendar.HOUR_OF_DAY, hh); 129 * cal.set(Calendar.MINUTE, minutes); 130 * </pre> 131 * <p> 132 * The distance for the variable "minutes" is 6 because there is one more expression 133 * (except the initialization block) between the declaration of this variable and its usage. 134 * </p> 135 * <p> 136 * To configure the check to set allowed distance: 137 * </p> 138 * <pre> 139 * <module name="VariableDeclarationUsageDistance"> 140 * <property name="allowedDistance" value="4"/> 141 * </module> 142 * </pre> 143 * <p>Example:</p> 144 * <pre> 145 * public class Test { 146 * 147 * public void foo1() { 148 * int num; // OK, distance = 4 149 * final int PI; // OK, final variables not checked 150 * System.out.println("Statement 1"); 151 * System.out.println("Statement 2"); 152 * System.out.println("Statement 3"); 153 * num = 1; 154 * PI = 3.14; 155 * } 156 * 157 * public void foo2() { 158 * int a; // OK, used in different scope 159 * int b; // OK, used in different scope 160 * int count = 0; // OK, used in different scope 161 * 162 * { 163 * System.out.println("Inside inner scope"); 164 * a = 1; 165 * b = 2; 166 * count++; 167 * } 168 * } 169 * } 170 * </pre> 171 * <p> 172 * To configure the check to ignore certain variables: 173 * </p> 174 * <pre> 175 * <module name="VariableDeclarationUsageDistance"> 176 * <property name="ignoreVariablePattern" value="^num$"/> 177 * </module> 178 * </pre> 179 * <p> 180 * This configuration ignores variables named "num". 181 * </p> 182 * <p>Example:</p> 183 * <pre> 184 * public class Test { 185 * 186 * public void foo1() { 187 * int num; // OK, variable ignored 188 * final int PI; // OK, final variables not checked 189 * System.out.println("Statement 1"); 190 * System.out.println("Statement 2"); 191 * System.out.println("Statement 3"); 192 * num = 1; 193 * PI = 3.14; 194 * } 195 * 196 * public void foo2() { 197 * int a; // OK, used in different scope 198 * int b; // OK, used in different scope 199 * int count = 0; // OK, used in different scope 200 * 201 * { 202 * System.out.println("Inside inner scope"); 203 * a = 1; 204 * b = 2; 205 * count++; 206 * } 207 * } 208 * } 209 * </pre> 210 * <p> 211 * To configure the check to force validation between scopes: 212 * </p> 213 * <pre> 214 * <module name="VariableDeclarationUsageDistance"> 215 * <property name="validateBetweenScopes" value="true"/> 216 * </module> 217 * </pre> 218 * <p>Example:</p> 219 * <pre> 220 * public class Test { 221 * 222 * public void foo1() { 223 * int num; // violation, distance = 4 224 * final int PI; // OK, final variables not checked 225 * System.out.println("Statement 1"); 226 * System.out.println("Statement 2"); 227 * System.out.println("Statement 3"); 228 * num = 1; 229 * PI = 3.14; 230 * } 231 * 232 * public void foo2() { 233 * int a; // OK, distance = 2 234 * int b; // OK, distance = 3 235 * int count = 0; // violation, distance = 4 236 * 237 * { 238 * System.out.println("Inside inner scope"); 239 * a = 1; 240 * b = 2; 241 * count++; 242 * } 243 * } 244 * } 245 * </pre> 246 * <p> 247 * To configure the check to check final variables: 248 * </p> 249 * <pre> 250 * <module name="VariableDeclarationUsageDistance"> 251 * <property name="ignoreFinal" value="false"/> 252 * </module> 253 * </pre> 254 * <p>Example:</p> 255 * <pre> 256 * public class Test { 257 * 258 * public void foo1() { 259 * int num; // violation, distance = 4 260 * final int PI; // violation, distance = 5 261 * System.out.println("Statement 1"); 262 * System.out.println("Statement 2"); 263 * System.out.println("Statement 3"); 264 * num = 1; 265 * PI = 3.14; 266 * } 267 * 268 * public void foo2() { 269 * int a; // OK, used in different scope 270 * int b; // OK, used in different scope 271 * int count = 0; // OK, used in different scope 272 * 273 * { 274 * System.out.println("Inside inner scope"); 275 * a = 1; 276 * b = 2; 277 * count++; 278 * } 279 * } 280 * } 281 * </pre> 282 * <p> 283 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 284 * </p> 285 * <p> 286 * Violation Message Keys: 287 * </p> 288 * <ul> 289 * <li> 290 * {@code variable.declaration.usage.distance} 291 * </li> 292 * <li> 293 * {@code variable.declaration.usage.distance.extend} 294 * </li> 295 * </ul> 296 * 297 * @since 5.8 298 */ 299@StatelessCheck 300public class VariableDeclarationUsageDistanceCheck extends AbstractCheck { 301 302 /** 303 * Warning message key. 304 */ 305 public static final String MSG_KEY = "variable.declaration.usage.distance"; 306 307 /** 308 * Warning message key. 309 */ 310 public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend"; 311 312 /** 313 * Default value of distance between declaration of variable and its first 314 * usage. 315 */ 316 private static final int DEFAULT_DISTANCE = 3; 317 318 /** 319 * Specify distance between declaration of variable and its first usage. 320 * Values should be greater than 0. 321 */ 322 private int allowedDistance = DEFAULT_DISTANCE; 323 324 /** 325 * Define RegExp to ignore distance calculation for variables listed in 326 * this pattern. 327 */ 328 private Pattern ignoreVariablePattern = Pattern.compile(""); 329 330 /** 331 * Allow to calculate the distance between declaration of variable and its 332 * first usage in the different scopes. 333 */ 334 private boolean validateBetweenScopes; 335 336 /** Allow to ignore variables with a 'final' modifier. */ 337 private boolean ignoreFinal = true; 338 339 /** 340 * Setter to specify distance between declaration of variable and its first usage. 341 * Values should be greater than 0. 342 * 343 * @param allowedDistance 344 * Allowed distance between declaration of variable and its first 345 * usage. 346 */ 347 public void setAllowedDistance(int allowedDistance) { 348 this.allowedDistance = allowedDistance; 349 } 350 351 /** 352 * Setter to define RegExp to ignore distance calculation for variables listed in this pattern. 353 * 354 * @param pattern a pattern. 355 */ 356 public void setIgnoreVariablePattern(Pattern pattern) { 357 ignoreVariablePattern = pattern; 358 } 359 360 /** 361 * Setter to allow to calculate the distance between declaration of 362 * variable and its first usage in the different scopes. 363 * 364 * @param validateBetweenScopes 365 * Defines if allow to calculate distance between declaration of 366 * variable and its first usage in different scopes or not. 367 */ 368 public void setValidateBetweenScopes(boolean validateBetweenScopes) { 369 this.validateBetweenScopes = validateBetweenScopes; 370 } 371 372 /** 373 * Setter to allow to ignore variables with a 'final' modifier. 374 * 375 * @param ignoreFinal 376 * Defines if ignore variables with 'final' modifier or not. 377 */ 378 public void setIgnoreFinal(boolean ignoreFinal) { 379 this.ignoreFinal = ignoreFinal; 380 } 381 382 @Override 383 public int[] getDefaultTokens() { 384 return getRequiredTokens(); 385 } 386 387 @Override 388 public int[] getAcceptableTokens() { 389 return getRequiredTokens(); 390 } 391 392 @Override 393 public int[] getRequiredTokens() { 394 return new int[] {TokenTypes.VARIABLE_DEF}; 395 } 396 397 @Override 398 public void visitToken(DetailAST ast) { 399 final int parentType = ast.getParent().getType(); 400 final DetailAST modifiers = ast.getFirstChild(); 401 402 if (parentType != TokenTypes.OBJBLOCK 403 && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) { 404 final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT); 405 406 if (!isVariableMatchesIgnorePattern(variable.getText())) { 407 final DetailAST semicolonAst = ast.getNextSibling(); 408 final Entry<DetailAST, Integer> entry; 409 if (validateBetweenScopes) { 410 entry = calculateDistanceBetweenScopes(semicolonAst, variable); 411 } 412 else { 413 entry = calculateDistanceInSingleScope(semicolonAst, variable); 414 } 415 final DetailAST variableUsageAst = entry.getKey(); 416 final int dist = entry.getValue(); 417 if (dist > allowedDistance 418 && !isInitializationSequence(variableUsageAst, variable.getText())) { 419 if (ignoreFinal) { 420 log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance); 421 } 422 else { 423 log(ast, MSG_KEY, variable.getText(), dist, allowedDistance); 424 } 425 } 426 } 427 } 428 } 429 430 /** 431 * Get name of instance whose method is called. 432 * 433 * @param methodCallAst 434 * DetailAST of METHOD_CALL. 435 * @return name of instance. 436 */ 437 private static String getInstanceName(DetailAST methodCallAst) { 438 final String methodCallName = 439 FullIdent.createFullIdentBelow(methodCallAst).getText(); 440 final int lastDotIndex = methodCallName.lastIndexOf('.'); 441 String instanceName = ""; 442 if (lastDotIndex != -1) { 443 instanceName = methodCallName.substring(0, lastDotIndex); 444 } 445 return instanceName; 446 } 447 448 /** 449 * Processes statements until usage of variable to detect sequence of 450 * initialization methods. 451 * 452 * @param variableUsageAst 453 * DetailAST of expression that uses variable named variableName. 454 * @param variableName 455 * name of considered variable. 456 * @return true if statements between declaration and usage of variable are 457 * initialization methods. 458 */ 459 private static boolean isInitializationSequence( 460 DetailAST variableUsageAst, String variableName) { 461 boolean result = true; 462 boolean isUsedVariableDeclarationFound = false; 463 DetailAST currentSiblingAst = variableUsageAst; 464 String initInstanceName = ""; 465 466 while (result 467 && !isUsedVariableDeclarationFound 468 && currentSiblingAst != null) { 469 switch (currentSiblingAst.getType()) { 470 case TokenTypes.EXPR: 471 final DetailAST methodCallAst = currentSiblingAst.getFirstChild(); 472 473 if (methodCallAst.getType() == TokenTypes.METHOD_CALL) { 474 final String instanceName = 475 getInstanceName(methodCallAst); 476 // method is called without instance 477 if (instanceName.isEmpty()) { 478 result = false; 479 } 480 // differs from previous instance 481 else if (!instanceName.equals(initInstanceName)) { 482 if (initInstanceName.isEmpty()) { 483 initInstanceName = instanceName; 484 } 485 else { 486 result = false; 487 } 488 } 489 } 490 else { 491 // is not method call 492 result = false; 493 } 494 break; 495 496 case TokenTypes.VARIABLE_DEF: 497 final String currentVariableName = currentSiblingAst 498 .findFirstToken(TokenTypes.IDENT).getText(); 499 isUsedVariableDeclarationFound = variableName.equals(currentVariableName); 500 break; 501 502 case TokenTypes.SEMI: 503 break; 504 505 default: 506 result = false; 507 } 508 509 currentSiblingAst = currentSiblingAst.getPreviousSibling(); 510 } 511 512 return result; 513 } 514 515 /** 516 * Calculates distance between declaration of variable and its first usage 517 * in single scope. 518 * 519 * @param semicolonAst 520 * Regular node of Ast which is checked for content of checking 521 * variable. 522 * @param variableIdentAst 523 * Variable which distance is calculated for. 524 * @return entry which contains expression with variable usage and distance. 525 */ 526 private static Entry<DetailAST, Integer> calculateDistanceInSingleScope( 527 DetailAST semicolonAst, DetailAST variableIdentAst) { 528 int dist = 0; 529 boolean firstUsageFound = false; 530 DetailAST currentAst = semicolonAst; 531 DetailAST variableUsageAst = null; 532 533 while (!firstUsageFound && currentAst != null 534 && currentAst.getType() != TokenTypes.RCURLY) { 535 if (currentAst.getFirstChild() != null) { 536 if (isChild(currentAst, variableIdentAst)) { 537 dist = getDistToVariableUsageInChildNode(currentAst, variableIdentAst, dist); 538 variableUsageAst = currentAst; 539 firstUsageFound = true; 540 } 541 else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) { 542 dist++; 543 } 544 } 545 currentAst = currentAst.getNextSibling(); 546 } 547 548 // If variable wasn't used after its declaration, distance is 0. 549 if (!firstUsageFound) { 550 dist = 0; 551 } 552 553 return new SimpleEntry<>(variableUsageAst, dist); 554 } 555 556 /** 557 * Returns the distance to variable usage for in the child node. 558 * 559 * @param childNode child node. 560 * @param varIdent variable variable identifier. 561 * @param currentDistToVarUsage current distance to the variable usage. 562 * @return the distance to variable usage for in the child node. 563 */ 564 private static int getDistToVariableUsageInChildNode(DetailAST childNode, DetailAST varIdent, 565 int currentDistToVarUsage) { 566 DetailAST examineNode = childNode; 567 if (examineNode.getType() == TokenTypes.LABELED_STAT) { 568 examineNode = examineNode.getFirstChild().getNextSibling(); 569 } 570 571 int resultDist = currentDistToVarUsage; 572 switch (examineNode.getType()) { 573 case TokenTypes.VARIABLE_DEF: 574 resultDist++; 575 break; 576 case TokenTypes.SLIST: 577 resultDist = 0; 578 break; 579 case TokenTypes.LITERAL_FOR: 580 case TokenTypes.LITERAL_WHILE: 581 case TokenTypes.LITERAL_DO: 582 case TokenTypes.LITERAL_IF: 583 case TokenTypes.LITERAL_SWITCH: 584 if (isVariableInOperatorExpr(examineNode, varIdent)) { 585 resultDist++; 586 } 587 else { 588 // variable usage is in inner scope 589 // reset counters, because we can't determine distance 590 resultDist = 0; 591 } 592 break; 593 default: 594 if (examineNode.findFirstToken(TokenTypes.SLIST) == null) { 595 resultDist++; 596 } 597 else { 598 resultDist = 0; 599 } 600 } 601 return resultDist; 602 } 603 604 /** 605 * Calculates distance between declaration of variable and its first usage 606 * in multiple scopes. 607 * 608 * @param ast 609 * Regular node of Ast which is checked for content of checking 610 * variable. 611 * @param variable 612 * Variable which distance is calculated for. 613 * @return entry which contains expression with variable usage and distance. 614 */ 615 private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes( 616 DetailAST ast, DetailAST variable) { 617 int dist = 0; 618 DetailAST currentScopeAst = ast; 619 DetailAST variableUsageAst = null; 620 while (currentScopeAst != null) { 621 final Entry<List<DetailAST>, Integer> searchResult = 622 searchVariableUsageExpressions(variable, currentScopeAst); 623 624 currentScopeAst = null; 625 626 final List<DetailAST> variableUsageExpressions = searchResult.getKey(); 627 dist += searchResult.getValue(); 628 629 // If variable usage exists in a single scope, then look into 630 // this scope and count distance until variable usage. 631 if (variableUsageExpressions.size() == 1) { 632 final DetailAST blockWithVariableUsage = variableUsageExpressions 633 .get(0); 634 DetailAST exprWithVariableUsage = null; 635 switch (blockWithVariableUsage.getType()) { 636 case TokenTypes.VARIABLE_DEF: 637 case TokenTypes.EXPR: 638 dist++; 639 break; 640 case TokenTypes.LITERAL_FOR: 641 case TokenTypes.LITERAL_WHILE: 642 case TokenTypes.LITERAL_DO: 643 exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks( 644 blockWithVariableUsage, variable); 645 break; 646 case TokenTypes.LITERAL_IF: 647 exprWithVariableUsage = getFirstNodeInsideIfBlock( 648 blockWithVariableUsage, variable); 649 break; 650 case TokenTypes.LITERAL_SWITCH: 651 exprWithVariableUsage = getFirstNodeInsideSwitchBlock( 652 blockWithVariableUsage, variable); 653 break; 654 case TokenTypes.LITERAL_TRY: 655 exprWithVariableUsage = 656 getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage, 657 variable); 658 break; 659 default: 660 exprWithVariableUsage = blockWithVariableUsage.getFirstChild(); 661 } 662 currentScopeAst = exprWithVariableUsage; 663 variableUsageAst = 664 Objects.requireNonNullElse(exprWithVariableUsage, blockWithVariableUsage); 665 } 666 667 // If there's no any variable usage, then distance = 0. 668 else if (variableUsageExpressions.isEmpty()) { 669 variableUsageAst = null; 670 } 671 // If variable usage exists in different scopes, then distance = 672 // distance until variable first usage. 673 else { 674 dist++; 675 variableUsageAst = variableUsageExpressions.get(0); 676 } 677 } 678 return new SimpleEntry<>(variableUsageAst, dist); 679 } 680 681 /** 682 * Searches variable usages starting from specified statement. 683 * 684 * @param variableAst Variable that is used. 685 * @param statementAst DetailAST to start searching from. 686 * @return entry which contains list with found expressions that use the variable 687 * and distance from specified statement to first found expression. 688 */ 689 private static Entry<List<DetailAST>, Integer> 690 searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) { 691 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 692 int distance = 0; 693 DetailAST currentStatementAst = statementAst; 694 while (currentStatementAst != null 695 && currentStatementAst.getType() != TokenTypes.RCURLY) { 696 if (currentStatementAst.getFirstChild() != null) { 697 if (isChild(currentStatementAst, variableAst)) { 698 variableUsageExpressions.add(currentStatementAst); 699 } 700 // If expression doesn't contain variable and this variable 701 // hasn't been met yet, then distance + 1. 702 else if (variableUsageExpressions.isEmpty() 703 && currentStatementAst.getType() != TokenTypes.VARIABLE_DEF) { 704 distance++; 705 } 706 } 707 currentStatementAst = currentStatementAst.getNextSibling(); 708 } 709 return new SimpleEntry<>(variableUsageExpressions, distance); 710 } 711 712 /** 713 * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable 714 * usage is met only inside the block (not in its declaration!). 715 * 716 * @param block 717 * Ast node represents FOR, WHILE or DO-WHILE block. 718 * @param variable 719 * Variable which is checked for content in block. 720 * @return If variable usage is met only inside the block 721 * (not in its declaration!) then return the first Ast node 722 * of this block, otherwise - null. 723 */ 724 private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks( 725 DetailAST block, DetailAST variable) { 726 DetailAST firstNodeInsideBlock = null; 727 728 if (!isVariableInOperatorExpr(block, variable)) { 729 final DetailAST currentNode; 730 731 // Find currentNode for DO-WHILE block. 732 if (block.getType() == TokenTypes.LITERAL_DO) { 733 currentNode = block.getFirstChild(); 734 } 735 // Find currentNode for FOR or WHILE block. 736 else { 737 // Looking for RPAREN ( ')' ) token to mark the end of operator 738 // expression. 739 currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling(); 740 } 741 742 final int currentNodeType = currentNode.getType(); 743 744 if (currentNodeType == TokenTypes.SLIST) { 745 firstNodeInsideBlock = currentNode.getFirstChild(); 746 } 747 else if (currentNodeType != TokenTypes.EXPR) { 748 firstNodeInsideBlock = currentNode; 749 } 750 } 751 752 return firstNodeInsideBlock; 753 } 754 755 /** 756 * Gets first Ast node inside IF block if variable usage is met 757 * only inside the block (not in its declaration!). 758 * 759 * @param block 760 * Ast node represents IF block. 761 * @param variable 762 * Variable which is checked for content in block. 763 * @return If variable usage is met only inside the block 764 * (not in its declaration!) then return the first Ast node 765 * of this block, otherwise - null. 766 */ 767 private static DetailAST getFirstNodeInsideIfBlock( 768 DetailAST block, DetailAST variable) { 769 DetailAST firstNodeInsideBlock = null; 770 771 if (!isVariableInOperatorExpr(block, variable)) { 772 DetailAST currentNode = block.getLastChild(); 773 final List<DetailAST> variableUsageExpressions = 774 new ArrayList<>(); 775 776 while (currentNode != null 777 && currentNode.getType() == TokenTypes.LITERAL_ELSE) { 778 final DetailAST previousNode = 779 currentNode.getPreviousSibling(); 780 781 // Checking variable usage inside IF block. 782 if (isChild(previousNode, variable)) { 783 variableUsageExpressions.add(previousNode); 784 } 785 786 // Looking into ELSE block, get its first child and analyze it. 787 currentNode = currentNode.getFirstChild(); 788 789 if (currentNode.getType() == TokenTypes.LITERAL_IF) { 790 currentNode = currentNode.getLastChild(); 791 } 792 else if (isChild(currentNode, variable)) { 793 variableUsageExpressions.add(currentNode); 794 currentNode = null; 795 } 796 } 797 798 // If IF block doesn't include ELSE then analyze variable usage 799 // only inside IF block. 800 if (currentNode != null 801 && isChild(currentNode, variable)) { 802 variableUsageExpressions.add(currentNode); 803 } 804 805 // If variable usage exists in several related blocks, then 806 // firstNodeInsideBlock = null, otherwise if variable usage exists 807 // only inside one block, then get node from 808 // variableUsageExpressions. 809 if (variableUsageExpressions.size() == 1) { 810 firstNodeInsideBlock = variableUsageExpressions.get(0); 811 } 812 } 813 814 return firstNodeInsideBlock; 815 } 816 817 /** 818 * Gets first Ast node inside SWITCH block if variable usage is met 819 * only inside the block (not in its declaration!). 820 * 821 * @param block 822 * Ast node represents SWITCH block. 823 * @param variable 824 * Variable which is checked for content in block. 825 * @return If variable usage is met only inside the block 826 * (not in its declaration!) then return the first Ast node 827 * of this block, otherwise - null. 828 */ 829 private static DetailAST getFirstNodeInsideSwitchBlock( 830 DetailAST block, DetailAST variable) { 831 final DetailAST currentNode = getFirstCaseGroupOrSwitchRule(block); 832 final List<DetailAST> variableUsageExpressions = 833 new ArrayList<>(); 834 835 // Checking variable usage inside all CASE_GROUP and SWITCH_RULE ast's. 836 TokenUtil.forEachChild(block, currentNode.getType(), node -> { 837 final DetailAST lastNodeInCaseGroup = 838 node.getLastChild(); 839 if (isChild(lastNodeInCaseGroup, variable)) { 840 variableUsageExpressions.add(lastNodeInCaseGroup); 841 } 842 }); 843 844 // If variable usage exists in several related blocks, then 845 // firstNodeInsideBlock = null, otherwise if variable usage exists 846 // only inside one block, then get node from 847 // variableUsageExpressions. 848 DetailAST firstNodeInsideBlock = null; 849 if (variableUsageExpressions.size() == 1) { 850 firstNodeInsideBlock = variableUsageExpressions.get(0); 851 } 852 853 return firstNodeInsideBlock; 854 } 855 856 /** 857 * Helper method for getFirstNodeInsideSwitchBlock to return the first CASE_GROUP or 858 * SWITCH_RULE ast. 859 * 860 * @param block the switch block to check. 861 * @return DetailAST of the first CASE_GROUP or SWITCH_RULE. 862 */ 863 private static DetailAST getFirstCaseGroupOrSwitchRule(DetailAST block) { 864 return Optional.ofNullable(block.findFirstToken(TokenTypes.CASE_GROUP)) 865 .orElseGet(() -> block.findFirstToken(TokenTypes.SWITCH_RULE)); 866 } 867 868 /** 869 * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is 870 * met only inside the block (not in its declaration!). 871 * 872 * @param block 873 * Ast node represents TRY-CATCH-FINALLY block. 874 * @param variable 875 * Variable which is checked for content in block. 876 * @return If variable usage is met only inside the block 877 * (not in its declaration!) then return the first Ast node 878 * of this block, otherwise - null. 879 */ 880 private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks( 881 DetailAST block, DetailAST variable) { 882 DetailAST currentNode = block.getFirstChild(); 883 final List<DetailAST> variableUsageExpressions = 884 new ArrayList<>(); 885 886 // Checking variable usage inside TRY block. 887 if (isChild(currentNode, variable)) { 888 variableUsageExpressions.add(currentNode); 889 } 890 891 // Switch on CATCH block. 892 currentNode = currentNode.getNextSibling(); 893 894 // Checking variable usage inside all CATCH blocks. 895 while (currentNode != null 896 && currentNode.getType() == TokenTypes.LITERAL_CATCH) { 897 final DetailAST catchBlock = currentNode.getLastChild(); 898 899 if (isChild(catchBlock, variable)) { 900 variableUsageExpressions.add(catchBlock); 901 } 902 currentNode = currentNode.getNextSibling(); 903 } 904 905 // Checking variable usage inside FINALLY block. 906 if (currentNode != null) { 907 final DetailAST finalBlock = currentNode.getLastChild(); 908 909 if (isChild(finalBlock, variable)) { 910 variableUsageExpressions.add(finalBlock); 911 } 912 } 913 914 DetailAST variableUsageNode = null; 915 916 // If variable usage exists in several related blocks, then 917 // firstNodeInsideBlock = null, otherwise if variable usage exists 918 // only inside one block, then get node from 919 // variableUsageExpressions. 920 if (variableUsageExpressions.size() == 1) { 921 variableUsageNode = variableUsageExpressions.get(0).getFirstChild(); 922 } 923 924 return variableUsageNode; 925 } 926 927 /** 928 * Checks if variable is in operator declaration. For instance: 929 * <pre> 930 * boolean b = true; 931 * if (b) {...} 932 * </pre> 933 * Variable 'b' is in declaration of operator IF. 934 * 935 * @param operator 936 * Ast node which represents operator. 937 * @param variable 938 * Variable which is checked for content in operator. 939 * @return true if operator contains variable in its declaration, otherwise 940 * - false. 941 */ 942 private static boolean isVariableInOperatorExpr( 943 DetailAST operator, DetailAST variable) { 944 boolean isVarInOperatorDeclaration = false; 945 final DetailAST openingBracket = 946 operator.findFirstToken(TokenTypes.LPAREN); 947 948 // Get EXPR between brackets 949 DetailAST exprBetweenBrackets = openingBracket.getNextSibling(); 950 951 // Look if variable is in operator expression 952 while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) { 953 if (isChild(exprBetweenBrackets, variable)) { 954 isVarInOperatorDeclaration = true; 955 break; 956 } 957 exprBetweenBrackets = exprBetweenBrackets.getNextSibling(); 958 } 959 960 // Variable may be met in ELSE declaration 961 // So, check variable usage in these declarations. 962 if (!isVarInOperatorDeclaration && operator.getType() == TokenTypes.LITERAL_IF) { 963 final DetailAST elseBlock = operator.getLastChild(); 964 965 if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) { 966 // Get IF followed by ELSE 967 final DetailAST firstNodeInsideElseBlock = elseBlock.getFirstChild(); 968 969 if (firstNodeInsideElseBlock.getType() == TokenTypes.LITERAL_IF) { 970 isVarInOperatorDeclaration = 971 isVariableInOperatorExpr(firstNodeInsideElseBlock, variable); 972 } 973 } 974 } 975 976 return isVarInOperatorDeclaration; 977 } 978 979 /** 980 * Checks if Ast node contains given element. 981 * 982 * @param parent 983 * Node of AST. 984 * @param ast 985 * Ast element which is checked for content in Ast node. 986 * @return true if Ast element was found in Ast node, otherwise - false. 987 */ 988 private static boolean isChild(DetailAST parent, DetailAST ast) { 989 boolean isChild = false; 990 DetailAST curNode = parent.getFirstChild(); 991 992 while (curNode != null) { 993 if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) { 994 isChild = true; 995 break; 996 } 997 998 DetailAST toVisit = curNode.getFirstChild(); 999 while (toVisit == null) { 1000 toVisit = curNode.getNextSibling(); 1001 curNode = curNode.getParent(); 1002 1003 if (curNode == parent) { 1004 break; 1005 } 1006 } 1007 1008 curNode = toVisit; 1009 } 1010 1011 return isChild; 1012 } 1013 1014 /** 1015 * Checks if entrance variable is contained in ignored pattern. 1016 * 1017 * @param variable 1018 * Variable which is checked for content in ignored pattern. 1019 * @return true if variable was found, otherwise - false. 1020 */ 1021 private boolean isVariableMatchesIgnorePattern(String variable) { 1022 final Matcher matcher = ignoreVariablePattern.matcher(variable); 1023 return matcher.matches(); 1024 } 1025 1026}