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.branchContains(TokenTypes.FINAL) 214 && !isVariableInForInit(ast) 215 && shouldCheckEnhancedForLoopVariable(ast)) { 216 insertVariable(ast); 217 } 218 break; 219 case TokenTypes.IDENT: 220 final int parentType = ast.getParent().getType(); 221 if (isAssignOperator(parentType) && isFirstChild(ast)) { 222 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast); 223 if (candidate.isPresent()) { 224 determineAssignmentConditions(ast, candidate.get()); 225 currentScopeAssignedVariables.peek().add(ast); 226 } 227 removeFinalVariableCandidateFromStack(ast); 228 } 229 break; 230 case TokenTypes.LITERAL_BREAK: 231 scopeStack.peek().containsBreak = true; 232 break; 233 default: 234 throw new IllegalStateException("Incorrect token type"); 235 } 236 } 237 238 @Override 239 public void leaveToken(DetailAST ast) { 240 Map<String, FinalVariableCandidate> scope = null; 241 switch (ast.getType()) { 242 case TokenTypes.OBJBLOCK: 243 case TokenTypes.CTOR_DEF: 244 case TokenTypes.METHOD_DEF: 245 scope = scopeStack.pop().scope; 246 break; 247 case TokenTypes.SLIST: 248 // -@cs[MoveVariableInsideIf] assignment value is modified later so it can't be 249 // moved 250 final Deque<DetailAST> prevScopeUnitializedVariableData = 251 prevScopeUninitializedVariables.peek(); 252 boolean containsBreak = false; 253 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 254 || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(), 255 TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) { 256 containsBreak = scopeStack.peek().containsBreak; 257 scope = scopeStack.pop().scope; 258 prevScopeUninitializedVariables.pop(); 259 } 260 final DetailAST parent = ast.getParent(); 261 if (containsBreak || shouldUpdateUninitializedVariables(parent)) { 262 updateAllUninitializedVariables(prevScopeUnitializedVariableData); 263 } 264 updateCurrentScopeAssignedVariables(); 265 break; 266 default: 267 // do nothing 268 } 269 if (scope != null) { 270 for (FinalVariableCandidate candidate : scope.values()) { 271 final DetailAST ident = candidate.variableIdent; 272 log(ident.getLineNo(), ident.getColumnNo(), MSG_KEY, ident.getText()); 273 } 274 } 275 } 276 277 /** 278 * Update assigned variables in a temporary stack. 279 */ 280 private void updateCurrentScopeAssignedVariables() { 281 // -@cs[MoveVariableInsideIf] assignment value is a modification call so it can't be moved 282 final Deque<DetailAST> poppedScopeAssignedVariableData = 283 currentScopeAssignedVariables.pop(); 284 final Deque<DetailAST> currentScopeAssignedVariableData = 285 currentScopeAssignedVariables.peek(); 286 if (currentScopeAssignedVariableData != null) { 287 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData); 288 } 289 } 290 291 /** 292 * Determines identifier assignment conditions (assigned or already assigned). 293 * @param ident identifier. 294 * @param candidate final local variable candidate. 295 */ 296 private static void determineAssignmentConditions(DetailAST ident, 297 FinalVariableCandidate candidate) { 298 if (candidate.assigned) { 299 if (!isInSpecificCodeBlock(ident, TokenTypes.LITERAL_ELSE) 300 && !isInSpecificCodeBlock(ident, TokenTypes.CASE_GROUP)) { 301 candidate.alreadyAssigned = true; 302 } 303 } 304 else { 305 candidate.assigned = true; 306 } 307 } 308 309 /** 310 * Checks whether the scope of a node is restricted to a specific code block. 311 * @param node node. 312 * @param blockType block type. 313 * @return true if the scope of a node is restricted to a specific code block. 314 */ 315 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { 316 boolean returnValue = false; 317 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { 318 final int type = token.getType(); 319 if (type == blockType) { 320 returnValue = true; 321 break; 322 } 323 } 324 return returnValue; 325 } 326 327 /** 328 * Gets final variable candidate for ast. 329 * @param ast ast. 330 * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack. 331 */ 332 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) { 333 Optional<FinalVariableCandidate> result = Optional.empty(); 334 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 335 while (iterator.hasNext() && !result.isPresent()) { 336 final ScopeData scopeData = iterator.next(); 337 result = scopeData.findFinalVariableCandidateForAst(ast); 338 } 339 return result; 340 } 341 342 /** 343 * Store un-initialized variables in a temporary stack for future use. 344 */ 345 private void storePrevScopeUninitializedVariableData() { 346 final ScopeData scopeData = scopeStack.peek(); 347 final Deque<DetailAST> prevScopeUnitializedVariableData = 348 new ArrayDeque<>(); 349 scopeData.uninitializedVariables.forEach(prevScopeUnitializedVariableData::push); 350 prevScopeUninitializedVariables.push(prevScopeUnitializedVariableData); 351 } 352 353 /** 354 * Update current scope data uninitialized variable according to the whole scope data. 355 * @param prevScopeUnitializedVariableData variable for previous stack of uninitialized 356 * variables 357 */ 358 // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation. 359 private void updateAllUninitializedVariables( 360 Deque<DetailAST> prevScopeUnitializedVariableData) { 361 // Check for only previous scope 362 updateUninitializedVariables(prevScopeUnitializedVariableData); 363 // Check for rest of the scope 364 prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables); 365 } 366 367 /** 368 * Update current scope data uninitialized variable according to the specific scope data. 369 * @param scopeUnitializedVariableData variable for specific stack of uninitialized variables 370 */ 371 private void updateUninitializedVariables(Deque<DetailAST> scopeUnitializedVariableData) { 372 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator(); 373 while (iterator.hasNext()) { 374 final DetailAST assignedVariable = iterator.next(); 375 for (DetailAST variable : scopeUnitializedVariableData) { 376 for (ScopeData scopeData : scopeStack) { 377 final FinalVariableCandidate candidate = 378 scopeData.scope.get(variable.getText()); 379 DetailAST storedVariable = null; 380 if (candidate != null) { 381 storedVariable = candidate.variableIdent; 382 } 383 if (storedVariable != null 384 && isSameVariables(storedVariable, variable) 385 && isSameVariables(assignedVariable, variable)) { 386 scopeData.uninitializedVariables.push(variable); 387 iterator.remove(); 388 } 389 } 390 } 391 } 392 } 393 394 /** 395 * If token is LITERAL_IF and there is an {@code else} following or token is CASE_GROUP and 396 * there is another {@code case} following, then update the uninitialized variables. 397 * @param ast token to be checked 398 * @return true if should be updated, else false 399 */ 400 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) { 401 return isIfTokenWithAnElseFollowing(ast) || isCaseTokenWithAnotherCaseFollowing(ast); 402 } 403 404 /** 405 * If token is LITERAL_IF and there is an {@code else} following. 406 * @param ast token to be checked 407 * @return true if token is LITERAL_IF and there is an {@code else} following, else false 408 */ 409 private static boolean isIfTokenWithAnElseFollowing(DetailAST ast) { 410 return ast.getType() == TokenTypes.LITERAL_IF 411 && ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE; 412 } 413 414 /** 415 * If token is CASE_GROUP and there is another {@code case} following. 416 * @param ast token to be checked 417 * @return true if token is CASE_GROUP and there is another {@code case} following, else false 418 */ 419 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) { 420 return ast.getType() == TokenTypes.CASE_GROUP 421 && findLastChildWhichContainsSpecifiedToken( 422 ast.getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) != ast; 423 } 424 425 /** 426 * Returns the last child token that makes a specified type and contains containType in 427 * its branch. 428 * @param ast token to be tested 429 * @param childType the token type to match 430 * @param containType the token type which has to be present in the branch 431 * @return the matching token, or null if no match 432 */ 433 private static DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType, 434 int containType) { 435 DetailAST returnValue = null; 436 for (DetailAST astIterator = ast.getFirstChild(); astIterator != null; 437 astIterator = astIterator.getNextSibling()) { 438 if (astIterator.getType() == childType && astIterator.branchContains(containType)) { 439 returnValue = astIterator; 440 } 441 } 442 return returnValue; 443 } 444 445 /** 446 * Determines whether enhanced for-loop variable should be checked or not. 447 * @param ast The ast to compare. 448 * @return true if enhanced for-loop variable should be checked. 449 */ 450 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) { 451 return validateEnhancedForLoopVariable 452 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE; 453 } 454 455 /** 456 * Insert a parameter at the topmost scope stack. 457 * @param ast the variable to insert. 458 */ 459 private void insertParameter(DetailAST ast) { 460 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 461 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 462 scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); 463 } 464 465 /** 466 * Insert a variable at the topmost scope stack. 467 * @param ast the variable to insert. 468 */ 469 private void insertVariable(DetailAST ast) { 470 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 471 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 472 scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); 473 if (!isInitialized(astNode)) { 474 scopeStack.peek().uninitializedVariables.add(astNode); 475 } 476 } 477 478 /** 479 * Check if VARIABLE_DEF is initialized or not. 480 * @param ast VARIABLE_DEF to be checked 481 * @return true if initialized 482 */ 483 private static boolean isInitialized(DetailAST ast) { 484 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN; 485 } 486 487 /** 488 * Whether the ast is the first child of its parent. 489 * @param ast the ast to check. 490 * @return true if the ast is the first child of its parent. 491 */ 492 private static boolean isFirstChild(DetailAST ast) { 493 return ast.getPreviousSibling() == null; 494 } 495 496 /** 497 * Removes the final variable candidate from the Stack. 498 * @param ast variable to remove. 499 */ 500 private void removeFinalVariableCandidateFromStack(DetailAST ast) { 501 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 502 while (iterator.hasNext()) { 503 final ScopeData scopeData = iterator.next(); 504 final Map<String, FinalVariableCandidate> scope = scopeData.scope; 505 final FinalVariableCandidate candidate = scope.get(ast.getText()); 506 DetailAST storedVariable = null; 507 if (candidate != null) { 508 storedVariable = candidate.variableIdent; 509 } 510 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 511 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { 512 scope.remove(ast.getText()); 513 } 514 break; 515 } 516 } 517 } 518 519 /** 520 * Check if given parameter definition is a multiple type catch. 521 * @param parameterDefAst parameter definition 522 * @return true if it is a multiple type catch, false otherwise 523 */ 524 private boolean isMultipleTypeCatch(DetailAST parameterDefAst) { 525 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); 526 return typeAst.getFirstChild().getType() == TokenTypes.BOR; 527 } 528 529 /** 530 * Whether the final variable candidate should be removed from the list of final local variable 531 * candidates. 532 * @param scopeData the scope data of the variable. 533 * @param ast the variable ast. 534 * @return true, if the variable should be removed. 535 */ 536 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { 537 boolean shouldRemove = true; 538 for (DetailAST variable : scopeData.uninitializedVariables) { 539 if (variable.getText().equals(ast.getText())) { 540 // if the variable is declared outside the loop and initialized inside 541 // the loop, then it cannot be declared final, as it can be initialized 542 // more than once in this case 543 if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) { 544 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); 545 shouldRemove = candidate.alreadyAssigned; 546 } 547 scopeData.uninitializedVariables.remove(variable); 548 break; 549 } 550 } 551 return shouldRemove; 552 } 553 554 /** 555 * Checks whether a variable which is declared outside loop is used inside loop. 556 * For example: 557 * <p> 558 * {@code 559 * int x; 560 * for (int i = 0, j = 0; i < j; i++) { 561 * x = 5; 562 * } 563 * } 564 * </p> 565 * @param variable variable. 566 * @return true if a variable which is declared outside loop is used inside loop. 567 */ 568 private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) { 569 DetailAST loop2 = variable.getParent(); 570 while (loop2 != null 571 && !isLoopAst(loop2.getType())) { 572 loop2 = loop2.getParent(); 573 } 574 return loop2 != null; 575 } 576 577 /** 578 * Is Arithmetic operator. 579 * @param parentType token AST 580 * @return true is token type is in arithmetic operator 581 */ 582 private static boolean isAssignOperator(int parentType) { 583 return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0; 584 } 585 586 /** 587 * Checks if current variable is defined in 588 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: 589 * <p> 590 * {@code 591 * for (int i = 0, j = 0; i < j; i++) { . . . } 592 * } 593 * </p> 594 * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} 595 * @param variableDef variable definition node. 596 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} 597 */ 598 private static boolean isVariableInForInit(DetailAST variableDef) { 599 return variableDef.getParent().getType() == TokenTypes.FOR_INIT; 600 } 601 602 /** 603 * Determines whether an AST is a descendant of an abstract or native method. 604 * @param ast the AST to check. 605 * @return true if ast is a descendant of an abstract or native method. 606 */ 607 private static boolean isInAbstractOrNativeMethod(DetailAST ast) { 608 boolean abstractOrNative = false; 609 DetailAST parent = ast.getParent(); 610 while (parent != null && !abstractOrNative) { 611 if (parent.getType() == TokenTypes.METHOD_DEF) { 612 final DetailAST modifiers = 613 parent.findFirstToken(TokenTypes.MODIFIERS); 614 abstractOrNative = modifiers.branchContains(TokenTypes.ABSTRACT) 615 || modifiers.branchContains(TokenTypes.LITERAL_NATIVE); 616 } 617 parent = parent.getParent(); 618 } 619 return abstractOrNative; 620 } 621 622 /** 623 * Check if current param is lambda's param. 624 * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. 625 * @return true if current param is lambda's param. 626 */ 627 private static boolean isInLambda(DetailAST paramDef) { 628 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; 629 } 630 631 /** 632 * Find the Class, Constructor, Enum, Method, or Field in which it is defined. 633 * @param ast Variable for which we want to find the scope in which it is defined 634 * @return ast The Class or Constructor or Method in which it is defined. 635 */ 636 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { 637 DetailAST astTraverse = ast; 638 while (astTraverse.getType() != TokenTypes.METHOD_DEF 639 && astTraverse.getType() != TokenTypes.CLASS_DEF 640 && astTraverse.getType() != TokenTypes.ENUM_DEF 641 && astTraverse.getType() != TokenTypes.CTOR_DEF 642 && !ScopeUtils.isClassFieldDef(astTraverse)) { 643 astTraverse = astTraverse.getParent(); 644 } 645 return astTraverse; 646 } 647 648 /** 649 * Check if both the Variables are same. 650 * @param ast1 Variable to compare 651 * @param ast2 Variable to compare 652 * @return true if both the variables are same, otherwise false 653 */ 654 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { 655 final DetailAST classOrMethodOfAst1 = 656 findFirstUpperNamedBlock(ast1); 657 final DetailAST classOrMethodOfAst2 = 658 findFirstUpperNamedBlock(ast2); 659 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); 660 } 661 662 /** 663 * Check if both the variables are in the same loop. 664 * @param ast1 variable to compare. 665 * @param ast2 variable to compare. 666 * @return true if both the variables are in the same loop. 667 */ 668 private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) { 669 DetailAST loop1 = ast1.getParent(); 670 while (loop1 != null && !isLoopAst(loop1.getType())) { 671 loop1 = loop1.getParent(); 672 } 673 DetailAST loop2 = ast2.getParent(); 674 while (loop2 != null && !isLoopAst(loop2.getType())) { 675 loop2 = loop2.getParent(); 676 } 677 return loop1 != null && loop1 == loop2; 678 } 679 680 /** 681 * Checks whether the ast is a loop. 682 * @param ast the ast to check. 683 * @return true if the ast is a loop. 684 */ 685 private static boolean isLoopAst(int ast) { 686 return Arrays.binarySearch(LOOP_TYPES, ast) >= 0; 687 } 688 689 /** 690 * Holder for the scope data. 691 */ 692 private static class ScopeData { 693 /** Contains variable definitions. */ 694 private final Map<String, FinalVariableCandidate> scope = new HashMap<>(); 695 696 /** Contains definitions of uninitialized variables. */ 697 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>(); 698 699 /** Whether there is a {@code break} in the scope. */ 700 private boolean containsBreak; 701 702 /** 703 * Searches for final local variable candidate for ast in the scope. 704 * @param ast ast. 705 * @return Optional of {@link FinalVariableCandidate}. 706 */ 707 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) { 708 Optional<FinalVariableCandidate> result = Optional.empty(); 709 DetailAST storedVariable = null; 710 final Optional<FinalVariableCandidate> candidate = 711 Optional.ofNullable(scope.get(ast.getText())); 712 if (candidate.isPresent()) { 713 storedVariable = candidate.get().variableIdent; 714 } 715 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 716 result = candidate; 717 } 718 return result; 719 } 720 } 721 722 /**Represents information about final local variable candidate. */ 723 private static class FinalVariableCandidate { 724 /** Identifier token. */ 725 private final DetailAST variableIdent; 726 /** Whether the variable is assigned. */ 727 private boolean assigned; 728 /** Whether the variable is already assigned. */ 729 private boolean alreadyAssigned; 730 731 /** 732 * Creates new instance. 733 * @param variableIdent variable identifier. 734 */ 735 FinalVariableCandidate(DetailAST variableIdent) { 736 this.variableIdent = variableIdent; 737 } 738 } 739}