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