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