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