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