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="mIgnoreFinal" 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 mAllowedDistance = DEFAULT_DISTANCE; 188 189 /** 190 * RegExp pattern to ignore distance calculation for variables listed in 191 * this pattern. 192 */ 193 private Pattern mIgnoreVariablePattern = 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 mValidateBetweenScopes; 200 201 /** Allows to ignore variables with 'final' modifier. */ 202 private boolean mIgnoreFinal = true; 203 204 /** 205 * Sets an allowed distance between declaration of variable and its first 206 * usage. 207 * @param aAllowedDistance 208 * Allowed distance between declaration of variable and its first 209 * usage. 210 */ 211 public void setAllowedDistance(int aAllowedDistance) 212 { 213 this.mAllowedDistance = aAllowedDistance; 214 } 215 216 /** 217 * Sets RegExp pattern to ignore distance calculation for variables listed 218 * in this pattern. 219 * @param aIgnorePattern 220 * Pattern contains ignored variables. 221 */ 222 public void setIgnoreVariablePattern(String aIgnorePattern) 223 { 224 this.mIgnoreVariablePattern = Pattern.compile(aIgnorePattern); 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 aValidateBetweenScopes 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 aValidateBetweenScopes) 235 { 236 this.mValidateBetweenScopes = aValidateBetweenScopes; 237 } 238 239 /** 240 * Sets ignore option for variables with 'final' modifier. 241 * @param aIgnoreFinal 242 * Defines if ignore variables with 'final' modifier or not. 243 */ 244 public void setIgnoreFinal(boolean aIgnoreFinal) 245 { 246 this.mIgnoreFinal = aIgnoreFinal; 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 aAST) 257 { 258 final int parentType = aAST.getParent().getType(); 259 final DetailAST modifiers = aAST.getFirstChild(); 260 261 if ((mIgnoreFinal && modifiers.branchContains(TokenTypes.FINAL)) 262 || parentType == TokenTypes.OBJBLOCK) 263 { 264 ;// no code 265 } 266 else { 267 final DetailAST variable = aAST.findFirstToken(TokenTypes.IDENT); 268 269 if (!isVariableMatchesIgnorePattern(variable.getText())) { 270 final DetailAST semicolonAst = aAST.getNextSibling(); 271 Entry<DetailAST, Integer> entry = null; 272 if (mValidateBetweenScopes) { 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 > mAllowedDistance 281 && !isInitializationSequence(variableUsageAst, variable.getText())) 282 { 283 if (mIgnoreFinal) { 284 log(variable.getLineNo(), 285 MSG_KEY_EXT, variable.getText(), dist, mAllowedDistance); 286 } 287 else { 288 log(variable.getLineNo(), 289 MSG_KEY, variable.getText(), dist, mAllowedDistance); 290 } 291 } 292 } 293 } 294 } 295 296 /** 297 * Get name of instance whose method is called. 298 * @param aMethodCallAst 299 * DetailAST of METHOD_CALL. 300 * @return name of instance. 301 */ 302 private static String getInstanceName(DetailAST aMethodCallAst) 303 { 304 final String methodCallName = 305 FullIdent.createFullIdentBelow(aMethodCallAst).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 aVariableUsageAst 318 * DetailAST of expression that uses variable named aVariableName. 319 * @param aVariableName 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 aVariableUsageAst, String aVariableName) 326 { 327 boolean result = true; 328 boolean isUsedVariableDeclarationFound = false; 329 DetailAST currentSiblingAst = aVariableUsageAst; 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 = aVariableName.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 aSemicolonAst 389 * Regular node of Ast which is checked for content of checking 390 * variable. 391 * @param aVariableIdentAst 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 aSemicolonAst, DetailAST aVariableIdentAst) 397 { 398 int dist = 0; 399 boolean firstUsageFound = false; 400 DetailAST currentAst = aSemicolonAst; 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, aVariableIdentAst)) { 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, aVariableIdentAst)) { 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 aAST 460 * Regular node of Ast which is checked for content of checking 461 * variable. 462 * @param aVariable 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 aAST, DetailAST aVariable) 468 { 469 int dist = 0; 470 DetailAST currentScopeAst = aAST; 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, aVariable)) { 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, aVariable); 509 break; 510 case TokenTypes.LITERAL_IF: 511 exprWithVariableUsage = getFirstNodeInsideIfBlock( 512 blockWithVariableUsage, aVariable); 513 break; 514 case TokenTypes.LITERAL_SWITCH: 515 exprWithVariableUsage = getFirstNodeInsideSwitchBlock( 516 blockWithVariableUsage, aVariable); 517 break; 518 case TokenTypes.LITERAL_TRY: 519 exprWithVariableUsage = 520 getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage, aVariable); 521 break; 522 default: 523 exprWithVariableUsage = blockWithVariableUsage.getFirstChild(); 524 } 525 currentScopeAst = exprWithVariableUsage; 526 if (exprWithVariableUsage != null) { 527 variableUsageAst = exprWithVariableUsage; 528 } 529 else { 530 variableUsageAst = blockWithVariableUsage; 531 } 532 } 533 // If variable usage exists in different scopes, then distance = 534 // distance until variable first usage. 535 else if (variableUsageExpressions.size() > 1) { 536 dist++; 537 variableUsageAst = variableUsageExpressions.get(0); 538 } 539 // If there's no any variable usage, then distance = 0. 540 else { 541 variableUsageAst = null; 542 } 543 } 544 return new SimpleEntry<DetailAST, Integer>(variableUsageAst, dist); 545 } 546 547 /** 548 * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable 549 * usage is met only inside the block (not in its declaration!). 550 * @param aBlock 551 * Ast node represents FOR, WHILE or DO-WHILE block. 552 * @param aVariable 553 * Variable which is checked for content in block. 554 * @return If variable usage is met only inside the block 555 * (not in its declaration!) than return the first Ast node 556 * of this block, otherwise - null. 557 */ 558 private DetailAST getFirstNodeInsideForWhileDoWhileBlocks( 559 DetailAST aBlock, DetailAST aVariable) 560 { 561 DetailAST firstNodeInsideBlock = null; 562 563 if (!isVariableInOperatorExpr(aBlock, aVariable)) { 564 DetailAST currentNode = null; 565 566 // Find currentNode for DO-WHILE block. 567 if (aBlock.getType() == TokenTypes.LITERAL_DO) { 568 currentNode = aBlock.getFirstChild(); 569 } 570 // Find currentNode for FOR or WHILE block. 571 else { 572 // Looking for RPAREN ( ')' ) token to mark the end of operator 573 // expression. 574 currentNode = aBlock.findFirstToken(TokenTypes.RPAREN); 575 if (currentNode != null) { 576 currentNode = currentNode.getNextSibling(); 577 } 578 } 579 580 if (currentNode != null) { 581 final int currentNodeType = currentNode.getType(); 582 583 if (currentNodeType == TokenTypes.SLIST) { 584 firstNodeInsideBlock = currentNode.getFirstChild(); 585 } 586 else if (currentNodeType == TokenTypes.VARIABLE_DEF 587 || currentNodeType == TokenTypes.EXPR) 588 { 589 ; // no code 590 } 591 else { 592 firstNodeInsideBlock = currentNode; 593 } 594 } 595 } 596 597 return firstNodeInsideBlock; 598 } 599 600 /** 601 * Gets first Ast node inside IF block if variable usage is met 602 * only inside the block (not in its declaration!). 603 * @param aBlock 604 * Ast node represents IF block. 605 * @param aVariable 606 * Variable which is checked for content in block. 607 * @return If variable usage is met only inside the block 608 * (not in its declaration!) than return the first Ast node 609 * of this block, otherwise - null. 610 */ 611 private DetailAST getFirstNodeInsideIfBlock( 612 DetailAST aBlock, DetailAST aVariable) 613 { 614 DetailAST firstNodeInsideBlock = null; 615 616 if (!isVariableInOperatorExpr(aBlock, aVariable)) { 617 DetailAST currentNode = aBlock.getLastChild(); 618 final List<DetailAST> variableUsageExpressions = 619 new ArrayList<DetailAST>(); 620 621 while (currentNode != null 622 && currentNode.getType() == TokenTypes.LITERAL_ELSE) 623 { 624 final DetailAST previousNode = 625 currentNode.getPreviousSibling(); 626 627 // Checking variable usage inside IF block. 628 if (isChild(previousNode, aVariable)) { 629 variableUsageExpressions.add(previousNode); 630 } 631 632 // Looking into ELSE block, get its first child and analyze it. 633 currentNode = currentNode.getFirstChild(); 634 635 if (currentNode.getType() == TokenTypes.LITERAL_IF) { 636 currentNode = currentNode.getLastChild(); 637 } 638 else if (isChild(currentNode, aVariable)) { 639 variableUsageExpressions.add(currentNode); 640 currentNode = null; 641 } 642 } 643 644 // If IF block doesn't include ELSE than analyze variable usage 645 // only inside IF block. 646 if (currentNode != null 647 && isChild(currentNode, aVariable)) 648 { 649 variableUsageExpressions.add(currentNode); 650 } 651 652 // If variable usage exists in several related blocks, then 653 // firstNodeInsideBlock = null, otherwise if variable usage exists 654 // only inside one block, then get node from 655 // variableUsageExpressions. 656 if (variableUsageExpressions.size() == 1) { 657 firstNodeInsideBlock = variableUsageExpressions.get(0); 658 } 659 } 660 661 return firstNodeInsideBlock; 662 } 663 664 /** 665 * Gets first Ast node inside SWITCH block if variable usage is met 666 * only inside the block (not in its declaration!). 667 * @param aBlock 668 * Ast node represents SWITCH block. 669 * @param aVariable 670 * Variable which is checked for content in block. 671 * @return If variable usage is met only inside the block 672 * (not in its declaration!) than return the first Ast node 673 * of this block, otherwise - null. 674 */ 675 private DetailAST getFirstNodeInsideSwitchBlock( 676 DetailAST aBlock, DetailAST aVariable) 677 { 678 DetailAST firstNodeInsideBlock = null; 679 680 if (!isVariableInOperatorExpr(aBlock, aVariable)) { 681 DetailAST currentNode = aBlock 682 .findFirstToken(TokenTypes.CASE_GROUP); 683 final List<DetailAST> variableUsageExpressions = 684 new ArrayList<DetailAST>(); 685 686 // Checking variable usage inside all CASE blocks. 687 while (currentNode != null 688 && currentNode.getType() == TokenTypes.CASE_GROUP) 689 { 690 final DetailAST lastNodeInCaseGroup = 691 currentNode.getLastChild(); 692 693 if (isChild(lastNodeInCaseGroup, aVariable)) { 694 variableUsageExpressions.add(lastNodeInCaseGroup); 695 } 696 currentNode = currentNode.getNextSibling(); 697 } 698 699 // If variable usage exists in several related blocks, then 700 // firstNodeInsideBlock = null, otherwise if variable usage exists 701 // only inside one block, then get node from 702 // variableUsageExpressions. 703 if (variableUsageExpressions.size() == 1) { 704 firstNodeInsideBlock = variableUsageExpressions.get(0); 705 } 706 } 707 708 return firstNodeInsideBlock; 709 } 710 711 /** 712 * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is 713 * met only inside the block (not in its declaration!). 714 * @param aBlock 715 * Ast node represents TRY-CATCH-FINALLY block. 716 * @param aVariable 717 * Variable which is checked for content in block. 718 * @return If variable usage is met only inside the block 719 * (not in its declaration!) than return the first Ast node 720 * of this block, otherwise - null. 721 */ 722 private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks( 723 DetailAST aBlock, DetailAST aVariable) 724 { 725 DetailAST currentNode = aBlock.getFirstChild(); 726 final List<DetailAST> variableUsageExpressions = 727 new ArrayList<DetailAST>(); 728 729 // Checking variable usage inside TRY block. 730 if (isChild(currentNode, aVariable)) { 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 { 741 final DetailAST catchBlock = currentNode.getLastChild(); 742 743 if (isChild(catchBlock, aVariable)) { 744 variableUsageExpressions.add(catchBlock); 745 } 746 currentNode = currentNode.getNextSibling(); 747 } 748 749 // Checking variable usage inside FINALLY block. 750 if (currentNode != null) { 751 final DetailAST finalBlock = currentNode.getLastChild(); 752 753 if (isChild(finalBlock, aVariable)) { 754 variableUsageExpressions.add(finalBlock); 755 } 756 } 757 758 DetailAST variableUsageNode = null; 759 760 // If variable usage exists in several related blocks, then 761 // firstNodeInsideBlock = null, otherwise if variable usage exists 762 // only inside one block, then get node from 763 // variableUsageExpressions. 764 if (variableUsageExpressions.size() == 1) { 765 variableUsageNode = variableUsageExpressions.get(0).getFirstChild(); 766 } 767 768 return variableUsageNode; 769 } 770 771 /** 772 * Checks if variable is in operator declaration. For instance: 773 * <pre> 774 * boolean b = true; 775 * if (b) {...} 776 * </pre> 777 * Variable 'b' is in declaration of operator IF. 778 * @param aOperator 779 * Ast node which represents operator. 780 * @param aVariable 781 * Variable which is checked for content in operator. 782 * @return true if operator contains variable in its declaration, otherwise 783 * - false. 784 */ 785 private boolean isVariableInOperatorExpr( 786 DetailAST aOperator, DetailAST aVariable) 787 { 788 boolean isVarInOperatorDeclr = false; 789 final DetailAST openingBracket = 790 aOperator.findFirstToken(TokenTypes.LPAREN); 791 792 if (openingBracket != null) { 793 // Get EXPR between brackets 794 DetailAST exprBetweenBrackets = openingBracket 795 .getNextSibling(); 796 797 // Look if variable is in operator expression 798 while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) { 799 800 if (isChild(exprBetweenBrackets, aVariable)) { 801 isVarInOperatorDeclr = true; 802 break; 803 } 804 exprBetweenBrackets = exprBetweenBrackets.getNextSibling(); 805 } 806 807 // Variable may be met in ELSE declaration or in CASE declaration. 808 // So, check variable usage in these declarations. 809 if (!isVarInOperatorDeclr) { 810 switch (aOperator.getType()) { 811 case TokenTypes.LITERAL_IF: 812 final DetailAST elseBlock = aOperator.getLastChild(); 813 814 if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) { 815 // Get IF followed by ELSE 816 final DetailAST firstNodeInsideElseBlock = elseBlock 817 .getFirstChild(); 818 819 if (firstNodeInsideElseBlock.getType() 820 == TokenTypes.LITERAL_IF) 821 { 822 isVarInOperatorDeclr |= 823 isVariableInOperatorExpr( 824 firstNodeInsideElseBlock, 825 aVariable); 826 } 827 } 828 break; 829 830 case TokenTypes.LITERAL_SWITCH: 831 DetailAST currentCaseBlock = aOperator 832 .findFirstToken(TokenTypes.CASE_GROUP); 833 834 while (currentCaseBlock != null 835 && currentCaseBlock.getType() 836 == TokenTypes.CASE_GROUP) 837 { 838 final DetailAST firstNodeInsideCaseBlock = 839 currentCaseBlock.getFirstChild(); 840 841 if (isChild(firstNodeInsideCaseBlock, 842 aVariable)) 843 { 844 isVarInOperatorDeclr = true; 845 break; 846 } 847 currentCaseBlock = currentCaseBlock.getNextSibling(); 848 } 849 break; 850 851 default: 852 ;// no code 853 } 854 } 855 } 856 857 return isVarInOperatorDeclr; 858 } 859 860 /** 861 * Checks if Ast node contains given element. 862 * @param aParent 863 * Node of AST. 864 * @param aAST 865 * Ast element which is checked for content in Ast node. 866 * @return true if Ast element was found in Ast node, otherwise - false. 867 */ 868 private static boolean isChild(DetailAST aParent, DetailAST aAST) 869 { 870 boolean isChild = false; 871 final ASTEnumeration astList = aParent.findAllPartial(aAST); 872 873 while (astList.hasMoreNodes()) { 874 final DetailAST ast = (DetailAST) astList.nextNode(); 875 DetailAST astParent = ast.getParent(); 876 877 while (astParent != null) { 878 879 if (astParent.equals(aParent) 880 && astParent.getLineNo() == aParent.getLineNo()) 881 { 882 isChild = true; 883 break; 884 } 885 astParent = astParent.getParent(); 886 } 887 } 888 889 return isChild; 890 } 891 892 /** 893 * Checks if entrance variable is contained in ignored pattern. 894 * @param aVariable 895 * Variable which is checked for content in ignored pattern. 896 * @return true if variable was found, otherwise - false. 897 */ 898 private boolean isVariableMatchesIgnorePattern(String aVariable) 899 { 900 final Matcher matcher = mIgnoreVariablePattern.matcher(aVariable); 901 return matcher.matches(); 902 } 903}