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