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