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