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