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