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