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