001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.ArrayDeque; 023import java.util.BitSet; 024import java.util.Deque; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.Optional; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 035import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 036import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 037 038/** 039 * <div> 040 * Checks that local variables that never have their values changed are declared final. 041 * The check can be configured to also check that unchanged parameters are declared final. 042 * </div> 043 * 044 * <p> 045 * When configured to check parameters, the check ignores parameters of interface 046 * methods and abstract methods. 047 * </p> 048 * <ul> 049 * <li> 050 * Property {@code validateEnhancedForLoopVariable} - Control whether to check 051 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 052 * enhanced for-loop</a> variable. 053 * Type is {@code boolean}. 054 * Default value is {@code false}. 055 * </li> 056 * <li> 057 * Property {@code validateUnnamedVariables} - Control whether to check 058 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 059 * unnamed variables</a>. 060 * Type is {@code boolean}. 061 * Default value is {@code false}. 062 * </li> 063 * <li> 064 * Property {@code tokens} - tokens to check 065 * Type is {@code java.lang.String[]}. 066 * Validation type is {@code tokenSet}. 067 * Default value is: 068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 069 * VARIABLE_DEF</a>. 070 * </li> 071 * </ul> 072 * 073 * <p> 074 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 075 * </p> 076 * 077 * <p> 078 * Violation Message Keys: 079 * </p> 080 * <ul> 081 * <li> 082 * {@code final.variable} 083 * </li> 084 * </ul> 085 * 086 * @since 3.2 087 */ 088@FileStatefulCheck 089public class FinalLocalVariableCheck extends AbstractCheck { 090 091 /** 092 * A key is pointing to the warning message text in "messages.properties" 093 * file. 094 */ 095 public static final String MSG_KEY = "final.variable"; 096 097 /** 098 * Assign operator types. 099 */ 100 private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet( 101 TokenTypes.POST_INC, 102 TokenTypes.POST_DEC, 103 TokenTypes.ASSIGN, 104 TokenTypes.PLUS_ASSIGN, 105 TokenTypes.MINUS_ASSIGN, 106 TokenTypes.STAR_ASSIGN, 107 TokenTypes.DIV_ASSIGN, 108 TokenTypes.MOD_ASSIGN, 109 TokenTypes.SR_ASSIGN, 110 TokenTypes.BSR_ASSIGN, 111 TokenTypes.SL_ASSIGN, 112 TokenTypes.BAND_ASSIGN, 113 TokenTypes.BXOR_ASSIGN, 114 TokenTypes.BOR_ASSIGN, 115 TokenTypes.INC, 116 TokenTypes.DEC 117 ); 118 119 /** 120 * Loop types. 121 */ 122 private static final BitSet LOOP_TYPES = TokenUtil.asBitSet( 123 TokenTypes.LITERAL_FOR, 124 TokenTypes.LITERAL_WHILE, 125 TokenTypes.LITERAL_DO 126 ); 127 128 /** Scope Deque. */ 129 private final Deque<ScopeData> scopeStack = new ArrayDeque<>(); 130 131 /** Assigned variables of current scope. */ 132 private final Deque<Deque<DetailAST>> currentScopeAssignedVariables = 133 new ArrayDeque<>(); 134 135 /** 136 * Control whether to check 137 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 138 * enhanced for-loop</a> variable. 139 */ 140 private boolean validateEnhancedForLoopVariable; 141 142 /** 143 * Control whether to check 144 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 145 * unnamed variables</a>. 146 */ 147 private boolean validateUnnamedVariables; 148 149 /** 150 * Setter to control whether to check 151 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 152 * enhanced for-loop</a> variable. 153 * 154 * @param validateEnhancedForLoopVariable whether to check for-loop variable 155 * @since 6.5 156 */ 157 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) { 158 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable; 159 } 160 161 /** 162 * Setter to control whether to check 163 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 164 * unnamed variables</a>. 165 * 166 * @param validateUnnamedVariables whether to check unnamed variables 167 * @since 10.18.0 168 */ 169 public final void setValidateUnnamedVariables(boolean validateUnnamedVariables) { 170 this.validateUnnamedVariables = validateUnnamedVariables; 171 } 172 173 @Override 174 public int[] getRequiredTokens() { 175 return new int[] { 176 TokenTypes.IDENT, 177 TokenTypes.CTOR_DEF, 178 TokenTypes.METHOD_DEF, 179 TokenTypes.SLIST, 180 TokenTypes.OBJBLOCK, 181 TokenTypes.LITERAL_BREAK, 182 TokenTypes.LITERAL_FOR, 183 TokenTypes.EXPR, 184 }; 185 } 186 187 @Override 188 public int[] getDefaultTokens() { 189 return new int[] { 190 TokenTypes.IDENT, 191 TokenTypes.CTOR_DEF, 192 TokenTypes.METHOD_DEF, 193 TokenTypes.SLIST, 194 TokenTypes.OBJBLOCK, 195 TokenTypes.LITERAL_BREAK, 196 TokenTypes.LITERAL_FOR, 197 TokenTypes.VARIABLE_DEF, 198 TokenTypes.EXPR, 199 }; 200 } 201 202 @Override 203 public int[] getAcceptableTokens() { 204 return new int[] { 205 TokenTypes.IDENT, 206 TokenTypes.CTOR_DEF, 207 TokenTypes.METHOD_DEF, 208 TokenTypes.SLIST, 209 TokenTypes.OBJBLOCK, 210 TokenTypes.LITERAL_BREAK, 211 TokenTypes.LITERAL_FOR, 212 TokenTypes.VARIABLE_DEF, 213 TokenTypes.PARAMETER_DEF, 214 TokenTypes.EXPR, 215 }; 216 } 217 218 // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block 219 // expressions to separate methods, but that will not increase readability. 220 @Override 221 public void visitToken(DetailAST ast) { 222 switch (ast.getType()) { 223 case TokenTypes.OBJBLOCK: 224 case TokenTypes.METHOD_DEF: 225 case TokenTypes.CTOR_DEF: 226 case TokenTypes.LITERAL_FOR: 227 scopeStack.push(new ScopeData()); 228 break; 229 case TokenTypes.SLIST: 230 currentScopeAssignedVariables.push(new ArrayDeque<>()); 231 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 232 || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP) 233 == ast.getParent()) { 234 storePrevScopeUninitializedVariableData(); 235 scopeStack.push(new ScopeData()); 236 } 237 break; 238 case TokenTypes.PARAMETER_DEF: 239 if (!isInLambda(ast) 240 && ast.findFirstToken(TokenTypes.MODIFIERS) 241 .findFirstToken(TokenTypes.FINAL) == null 242 && !isInAbstractOrNativeMethod(ast) 243 && !ScopeUtil.isInInterfaceBlock(ast) 244 && !isMultipleTypeCatch(ast) 245 && !CheckUtil.isReceiverParameter(ast)) { 246 insertParameter(ast); 247 } 248 break; 249 case TokenTypes.VARIABLE_DEF: 250 if (ast.getParent().getType() != TokenTypes.OBJBLOCK 251 && ast.findFirstToken(TokenTypes.MODIFIERS) 252 .findFirstToken(TokenTypes.FINAL) == null 253 && !isVariableInForInit(ast) 254 && shouldCheckEnhancedForLoopVariable(ast) 255 && shouldCheckUnnamedVariable(ast)) { 256 insertVariable(ast); 257 } 258 break; 259 case TokenTypes.IDENT: 260 final int parentType = ast.getParent().getType(); 261 if (isAssignOperator(parentType) && isFirstChild(ast)) { 262 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast); 263 if (candidate.isPresent()) { 264 determineAssignmentConditions(ast, candidate.orElseThrow()); 265 currentScopeAssignedVariables.peek().add(ast); 266 } 267 removeFinalVariableCandidateFromStack(ast); 268 } 269 break; 270 case TokenTypes.LITERAL_BREAK: 271 scopeStack.peek().containsBreak = true; 272 break; 273 case TokenTypes.EXPR: 274 // Switch labeled expression has no slist 275 if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) { 276 storePrevScopeUninitializedVariableData(); 277 } 278 break; 279 default: 280 throw new IllegalStateException("Incorrect token type"); 281 } 282 } 283 284 @Override 285 public void leaveToken(DetailAST ast) { 286 Map<String, FinalVariableCandidate> scope = null; 287 final DetailAST parentAst = ast.getParent(); 288 switch (ast.getType()) { 289 case TokenTypes.OBJBLOCK: 290 case TokenTypes.CTOR_DEF: 291 case TokenTypes.METHOD_DEF: 292 case TokenTypes.LITERAL_FOR: 293 scope = scopeStack.pop().scope; 294 break; 295 case TokenTypes.EXPR: 296 // Switch labeled expression has no slist 297 if (parentAst.getType() == TokenTypes.SWITCH_RULE 298 && shouldUpdateUninitializedVariables(parentAst)) { 299 updateAllUninitializedVariables(); 300 } 301 break; 302 case TokenTypes.SLIST: 303 boolean containsBreak = false; 304 if (parentAst.getType() != TokenTypes.CASE_GROUP 305 || findLastCaseGroupWhichContainsSlist(parentAst.getParent()) == parentAst) { 306 containsBreak = scopeStack.peek().containsBreak; 307 scope = scopeStack.pop().scope; 308 } 309 if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) { 310 updateAllUninitializedVariables(); 311 } 312 updateCurrentScopeAssignedVariables(); 313 break; 314 default: 315 // do nothing 316 } 317 if (scope != null) { 318 for (FinalVariableCandidate candidate : scope.values()) { 319 final DetailAST ident = candidate.variableIdent; 320 log(ident, MSG_KEY, ident.getText()); 321 } 322 } 323 } 324 325 /** 326 * Update assigned variables in a temporary stack. 327 */ 328 private void updateCurrentScopeAssignedVariables() { 329 // -@cs[MoveVariableInsideIf] assignment value is a modification call, so it can't be moved 330 final Deque<DetailAST> poppedScopeAssignedVariableData = 331 currentScopeAssignedVariables.pop(); 332 final Deque<DetailAST> currentScopeAssignedVariableData = 333 currentScopeAssignedVariables.peek(); 334 if (currentScopeAssignedVariableData != null) { 335 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData); 336 } 337 } 338 339 /** 340 * Determines identifier assignment conditions (assigned or already assigned). 341 * 342 * @param ident identifier. 343 * @param candidate final local variable candidate. 344 */ 345 private static void determineAssignmentConditions(DetailAST ident, 346 FinalVariableCandidate candidate) { 347 if (candidate.assigned) { 348 final int[] blockTypes = { 349 TokenTypes.LITERAL_ELSE, 350 TokenTypes.CASE_GROUP, 351 TokenTypes.SWITCH_RULE, 352 }; 353 if (!isInSpecificCodeBlocks(ident, blockTypes)) { 354 candidate.alreadyAssigned = true; 355 } 356 } 357 else { 358 candidate.assigned = true; 359 } 360 } 361 362 /** 363 * Checks whether the scope of a node is restricted to a specific code blocks. 364 * 365 * @param node node. 366 * @param blockTypes int array of all block types to check. 367 * @return true if the scope of a node is restricted to specific code block types. 368 */ 369 private static boolean isInSpecificCodeBlocks(DetailAST node, int... blockTypes) { 370 boolean returnValue = false; 371 for (int blockType : blockTypes) { 372 for (DetailAST token = node; token != null; token = token.getParent()) { 373 final int type = token.getType(); 374 if (type == blockType) { 375 returnValue = true; 376 break; 377 } 378 } 379 } 380 return returnValue; 381 } 382 383 /** 384 * Gets final variable candidate for ast. 385 * 386 * @param ast ast. 387 * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack. 388 */ 389 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) { 390 Optional<FinalVariableCandidate> result = Optional.empty(); 391 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 392 while (iterator.hasNext() && result.isEmpty()) { 393 final ScopeData scopeData = iterator.next(); 394 result = scopeData.findFinalVariableCandidateForAst(ast); 395 } 396 return result; 397 } 398 399 /** 400 * Store un-initialized variables in a temporary stack for future use. 401 */ 402 private void storePrevScopeUninitializedVariableData() { 403 final ScopeData scopeData = scopeStack.peek(); 404 final Deque<DetailAST> prevScopeUninitializedVariableData = 405 new ArrayDeque<>(); 406 scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push); 407 scopeData.prevScopeUninitializedVariables = prevScopeUninitializedVariableData; 408 } 409 410 /** 411 * Update current scope data uninitialized variable according to the whole scope data. 412 */ 413 private void updateAllUninitializedVariables() { 414 final boolean hasSomeScopes = !currentScopeAssignedVariables.isEmpty(); 415 if (hasSomeScopes) { 416 scopeStack.forEach(scopeData -> { 417 updateUninitializedVariables(scopeData.prevScopeUninitializedVariables); 418 }); 419 } 420 } 421 422 /** 423 * Update current scope data uninitialized variable according to the specific scope data. 424 * 425 * @param scopeUninitializedVariableData variable for specific stack of uninitialized variables 426 */ 427 private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) { 428 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator(); 429 while (iterator.hasNext()) { 430 final DetailAST assignedVariable = iterator.next(); 431 boolean shouldRemove = false; 432 for (DetailAST variable : scopeUninitializedVariableData) { 433 for (ScopeData scopeData : scopeStack) { 434 final FinalVariableCandidate candidate = 435 scopeData.scope.get(variable.getText()); 436 DetailAST storedVariable = null; 437 if (candidate != null) { 438 storedVariable = candidate.variableIdent; 439 } 440 if (storedVariable != null 441 && isSameVariables(assignedVariable, variable)) { 442 scopeData.uninitializedVariables.push(variable); 443 shouldRemove = true; 444 } 445 } 446 } 447 if (shouldRemove) { 448 iterator.remove(); 449 } 450 } 451 } 452 453 /** 454 * If there is an {@code else} following or token is CASE_GROUP or 455 * SWITCH_RULE and there is another {@code case} following, then update the 456 * uninitialized variables. 457 * 458 * @param ast token to be checked 459 * @return true if should be updated, else false 460 */ 461 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) { 462 return ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE 463 || isCaseTokenWithAnotherCaseFollowing(ast); 464 } 465 466 /** 467 * If token is CASE_GROUP or SWITCH_RULE and there is another {@code case} following. 468 * 469 * @param ast token to be checked 470 * @return true if token is CASE_GROUP or SWITCH_RULE and there is another {@code case} 471 * following, else false 472 */ 473 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) { 474 boolean result = false; 475 if (ast.getType() == TokenTypes.CASE_GROUP) { 476 result = findLastCaseGroupWhichContainsSlist(ast.getParent()) != ast; 477 } 478 else if (ast.getType() == TokenTypes.SWITCH_RULE) { 479 result = ast.getNextSibling().getType() == TokenTypes.SWITCH_RULE; 480 } 481 return result; 482 } 483 484 /** 485 * Returns the last token of type {@link TokenTypes#CASE_GROUP} which contains 486 * {@link TokenTypes#SLIST}. 487 * 488 * @param literalSwitchAst ast node of type {@link TokenTypes#LITERAL_SWITCH} 489 * @return the matching token, or null if no match 490 */ 491 private static DetailAST findLastCaseGroupWhichContainsSlist(DetailAST literalSwitchAst) { 492 DetailAST returnValue = null; 493 for (DetailAST astIterator = literalSwitchAst.getFirstChild(); astIterator != null; 494 astIterator = astIterator.getNextSibling()) { 495 if (astIterator.findFirstToken(TokenTypes.SLIST) != null) { 496 returnValue = astIterator; 497 } 498 } 499 return returnValue; 500 } 501 502 /** 503 * Determines whether enhanced for-loop variable should be checked or not. 504 * 505 * @param ast The ast to compare. 506 * @return true if enhanced for-loop variable should be checked. 507 */ 508 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) { 509 return validateEnhancedForLoopVariable 510 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE; 511 } 512 513 /** 514 * Determines whether unnamed variable should be checked or not. 515 * 516 * @param ast The ast to compare. 517 * @return true if unnamed variable should be checked. 518 */ 519 private boolean shouldCheckUnnamedVariable(DetailAST ast) { 520 return validateUnnamedVariables 521 || !"_".equals(ast.findFirstToken(TokenTypes.IDENT).getText()); 522 } 523 524 /** 525 * Insert a parameter at the topmost scope stack. 526 * 527 * @param ast the variable to insert. 528 */ 529 private void insertParameter(DetailAST ast) { 530 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 531 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 532 scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); 533 } 534 535 /** 536 * Insert a variable at the topmost scope stack. 537 * 538 * @param ast the variable to insert. 539 */ 540 private void insertVariable(DetailAST ast) { 541 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 542 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 543 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode); 544 // for-each variables are implicitly assigned 545 candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE; 546 scope.put(astNode.getText(), candidate); 547 if (!isInitialized(astNode)) { 548 scopeStack.peek().uninitializedVariables.add(astNode); 549 } 550 } 551 552 /** 553 * Check if VARIABLE_DEF is initialized or not. 554 * 555 * @param ast VARIABLE_DEF to be checked 556 * @return true if initialized 557 */ 558 private static boolean isInitialized(DetailAST ast) { 559 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN; 560 } 561 562 /** 563 * Whether the ast is the first child of its parent. 564 * 565 * @param ast the ast to check. 566 * @return true if the ast is the first child of its parent. 567 */ 568 private static boolean isFirstChild(DetailAST ast) { 569 return ast.getPreviousSibling() == null; 570 } 571 572 /** 573 * Removes the final variable candidate from the Stack. 574 * 575 * @param ast variable to remove. 576 */ 577 private void removeFinalVariableCandidateFromStack(DetailAST ast) { 578 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 579 while (iterator.hasNext()) { 580 final ScopeData scopeData = iterator.next(); 581 final Map<String, FinalVariableCandidate> scope = scopeData.scope; 582 final FinalVariableCandidate candidate = scope.get(ast.getText()); 583 DetailAST storedVariable = null; 584 if (candidate != null) { 585 storedVariable = candidate.variableIdent; 586 } 587 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 588 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { 589 scope.remove(ast.getText()); 590 } 591 break; 592 } 593 } 594 } 595 596 /** 597 * Check if given parameter definition is a multiple type catch. 598 * 599 * @param parameterDefAst parameter definition 600 * @return true if it is a multiple type catch, false otherwise 601 */ 602 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) { 603 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); 604 return typeAst.findFirstToken(TokenTypes.BOR) != null; 605 } 606 607 /** 608 * Whether the final variable candidate should be removed from the list of final local variable 609 * candidates. 610 * 611 * @param scopeData the scope data of the variable. 612 * @param ast the variable ast. 613 * @return true, if the variable should be removed. 614 */ 615 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { 616 boolean shouldRemove = true; 617 for (DetailAST variable : scopeData.uninitializedVariables) { 618 if (variable.getText().equals(ast.getText())) { 619 // if the variable is declared outside the loop and initialized inside 620 // the loop, then it cannot be declared final, as it can be initialized 621 // more than once in this case 622 final DetailAST currAstLoopAstParent = getParentLoop(ast); 623 final DetailAST currVarLoopAstParent = getParentLoop(variable); 624 if (currAstLoopAstParent == currVarLoopAstParent) { 625 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); 626 shouldRemove = candidate.alreadyAssigned; 627 } 628 scopeData.uninitializedVariables.remove(variable); 629 break; 630 } 631 } 632 return shouldRemove; 633 } 634 635 /** 636 * Get the ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 637 * of the current ast node, if there is no such node, null is returned. 638 * 639 * @param ast ast node 640 * @return ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 641 * of the current ast node, null if no such node exists 642 */ 643 private static DetailAST getParentLoop(DetailAST ast) { 644 DetailAST parentLoop = ast; 645 while (parentLoop != null 646 && !isLoopAst(parentLoop.getType())) { 647 parentLoop = parentLoop.getParent(); 648 } 649 return parentLoop; 650 } 651 652 /** 653 * Is Arithmetic operator. 654 * 655 * @param parentType token AST 656 * @return true is token type is in arithmetic operator 657 */ 658 private static boolean isAssignOperator(int parentType) { 659 return ASSIGN_OPERATOR_TYPES.get(parentType); 660 } 661 662 /** 663 * Checks if current variable is defined in 664 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: 665 * 666 * <p> 667 * {@code 668 * for (int i = 0, j = 0; i < j; i++) { . . . } 669 * } 670 * </p> 671 * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} 672 * 673 * @param variableDef variable definition node. 674 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} 675 */ 676 private static boolean isVariableInForInit(DetailAST variableDef) { 677 return variableDef.getParent().getType() == TokenTypes.FOR_INIT; 678 } 679 680 /** 681 * Determines whether an AST is a descendant of an abstract or native method. 682 * 683 * @param ast the AST to check. 684 * @return true if ast is a descendant of an abstract or native method. 685 */ 686 private static boolean isInAbstractOrNativeMethod(DetailAST ast) { 687 boolean abstractOrNative = false; 688 DetailAST currentAst = ast; 689 while (currentAst != null && !abstractOrNative) { 690 if (currentAst.getType() == TokenTypes.METHOD_DEF) { 691 final DetailAST modifiers = 692 currentAst.findFirstToken(TokenTypes.MODIFIERS); 693 abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null 694 || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null; 695 } 696 currentAst = currentAst.getParent(); 697 } 698 return abstractOrNative; 699 } 700 701 /** 702 * Check if current param is lambda's param. 703 * 704 * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. 705 * @return true if current param is lambda's param. 706 */ 707 private static boolean isInLambda(DetailAST paramDef) { 708 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; 709 } 710 711 /** 712 * Find the Class, Constructor, Enum, Method, or Field in which it is defined. 713 * 714 * @param ast Variable for which we want to find the scope in which it is defined 715 * @return ast The Class or Constructor or Method in which it is defined. 716 */ 717 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { 718 DetailAST astTraverse = ast; 719 while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF, 720 TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF) 721 && !ScopeUtil.isClassFieldDef(astTraverse)) { 722 astTraverse = astTraverse.getParent(); 723 } 724 return astTraverse; 725 } 726 727 /** 728 * Check if both the Variables are same. 729 * 730 * @param ast1 Variable to compare 731 * @param ast2 Variable to compare 732 * @return true if both the variables are same, otherwise false 733 */ 734 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { 735 final DetailAST classOrMethodOfAst1 = 736 findFirstUpperNamedBlock(ast1); 737 final DetailAST classOrMethodOfAst2 = 738 findFirstUpperNamedBlock(ast2); 739 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); 740 } 741 742 /** 743 * Checks whether the ast is a loop. 744 * 745 * @param ast the ast to check. 746 * @return true if the ast is a loop. 747 */ 748 private static boolean isLoopAst(int ast) { 749 return LOOP_TYPES.get(ast); 750 } 751 752 /** 753 * Holder for the scope data. 754 */ 755 private static final class ScopeData { 756 757 /** Contains variable definitions. */ 758 private final Map<String, FinalVariableCandidate> scope = new HashMap<>(); 759 760 /** Contains definitions of uninitialized variables. */ 761 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>(); 762 763 /** Contains definitions of previous scope uninitialized variables. */ 764 private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>(); 765 766 /** Whether there is a {@code break} in the scope. */ 767 private boolean containsBreak; 768 769 /** 770 * Searches for final local variable candidate for ast in the scope. 771 * 772 * @param ast ast. 773 * @return Optional of {@link FinalVariableCandidate}. 774 */ 775 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) { 776 Optional<FinalVariableCandidate> result = Optional.empty(); 777 DetailAST storedVariable = null; 778 final Optional<FinalVariableCandidate> candidate = 779 Optional.ofNullable(scope.get(ast.getText())); 780 if (candidate.isPresent()) { 781 storedVariable = candidate.orElseThrow().variableIdent; 782 } 783 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 784 result = candidate; 785 } 786 return result; 787 } 788 789 } 790 791 /** Represents information about final local variable candidate. */ 792 private static final class FinalVariableCandidate { 793 794 /** Identifier token. */ 795 private final DetailAST variableIdent; 796 /** Whether the variable is assigned. */ 797 private boolean assigned; 798 /** Whether the variable is already assigned. */ 799 private boolean alreadyAssigned; 800 801 /** 802 * Creates new instance. 803 * 804 * @param variableIdent variable identifier. 805 */ 806 private FinalVariableCandidate(DetailAST variableIdent) { 807 this.variableIdent = variableIdent; 808 } 809 810 } 811 812}