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