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