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