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