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