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