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