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