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