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