Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
FinalLocalVariableCheck
100%
217/217
100%
178/178
0
FinalLocalVariableCheck$1
N/A
N/A
0
FinalLocalVariableCheck$FinalVariableCandidate
100%
4/4
N/A
0
FinalLocalVariableCheck$ScopeData
100%
12/12
100%
6/6
0
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 21  
 
 22  
 import java.util.ArrayDeque;
 23  
 import java.util.Arrays;
 24  
 import java.util.Deque;
 25  
 import java.util.HashMap;
 26  
 import java.util.Iterator;
 27  
 import java.util.Map;
 28  
 import java.util.Optional;
 29  
 
 30  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 31  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 32  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 33  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 34  
 import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
 35  
 
 36  
 /**
 37  
  * <p>
 38  
  * Ensures that local variables that never get their values changed,
 39  
  * must be declared final.
 40  
  * </p>
 41  
  * <p>
 42  
  * An example of how to configure the check to validate variable definition is:
 43  
  * </p>
 44  
  * <pre>
 45  
  * &lt;module name="FinalLocalVariable"&gt;
 46  
  *     &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
 47  
  * &lt;/module&gt;
 48  
  * </pre>
 49  
  * <p>
 50  
  * By default, this Check skip final validation on
 51  
  *  <a href = "https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2">
 52  
  * Enhanced For-Loop</a>
 53  
  * </p>
 54  
  * <p>
 55  
  * Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable
 56  
  *  from Enhanced For Loop.
 57  
  * </p>
 58  
  * <p>
 59  
  * An example of how to configure the check so that it also validates enhanced For Loop Variable is:
 60  
  * </p>
 61  
  * <pre>
 62  
  * &lt;module name="FinalLocalVariable"&gt;
 63  
  *     &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
 64  
  *     &lt;property name="validateEnhancedForLoopVariable" value="true"/&gt;
 65  
  * &lt;/module&gt;
 66  
  * </pre>
 67  
  * <p>Example:</p>
 68  
  * <p>
 69  
  * {@code
 70  
  * for (int number : myNumbers) { // violation
 71  
  *    System.out.println(number);
 72  
  * }
 73  
  * }
 74  
  * </p>
 75  
  * @author k_gibbs, r_auckenthaler
 76  
  * @author Vladislav Lisetskiy
 77  
  */
 78  
 @FileStatefulCheck
 79  468
 public class FinalLocalVariableCheck extends AbstractCheck {
 80  
 
 81  
     /**
 82  
      * A key is pointing to the warning message text in "messages.properties"
 83  
      * file.
 84  
      */
 85  
     public static final String MSG_KEY = "final.variable";
 86  
 
 87  
     /**
 88  
      * Assign operator types.
 89  
      */
 90  1
     private static final int[] ASSIGN_OPERATOR_TYPES = {
 91  
         TokenTypes.POST_INC,
 92  
         TokenTypes.POST_DEC,
 93  
         TokenTypes.ASSIGN,
 94  
         TokenTypes.PLUS_ASSIGN,
 95  
         TokenTypes.MINUS_ASSIGN,
 96  
         TokenTypes.STAR_ASSIGN,
 97  
         TokenTypes.DIV_ASSIGN,
 98  
         TokenTypes.MOD_ASSIGN,
 99  
         TokenTypes.SR_ASSIGN,
 100  
         TokenTypes.BSR_ASSIGN,
 101  
         TokenTypes.SL_ASSIGN,
 102  
         TokenTypes.BAND_ASSIGN,
 103  
         TokenTypes.BXOR_ASSIGN,
 104  
         TokenTypes.BOR_ASSIGN,
 105  
         TokenTypes.INC,
 106  
         TokenTypes.DEC,
 107  
     };
 108  
 
 109  
     /**
 110  
      * Loop types.
 111  
      */
 112  1
     private static final int[] LOOP_TYPES = {
 113  
         TokenTypes.LITERAL_FOR,
 114  
         TokenTypes.LITERAL_WHILE,
 115  
         TokenTypes.LITERAL_DO,
 116  
     };
 117  
 
 118  
     /** Scope Deque. */
 119  26
     private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
 120  
 
 121  
     /** Uninitialized variables of previous scope. */
 122  26
     private final Deque<Deque<DetailAST>> prevScopeUninitializedVariables =
 123  
             new ArrayDeque<>();
 124  
 
 125  
     /** Assigned variables of current scope. */
 126  26
     private final Deque<Deque<DetailAST>> currentScopeAssignedVariables =
 127  
             new ArrayDeque<>();
 128  
 
 129  
     /** Controls whether to check enhanced for-loop variable. */
 130  
     private boolean validateEnhancedForLoopVariable;
 131  
 
 132  
     static {
 133  
         // Array sorting for binary search
 134  1
         Arrays.sort(ASSIGN_OPERATOR_TYPES);
 135  1
         Arrays.sort(LOOP_TYPES);
 136  1
     }
 137  
 
 138  
     /**
 139  
      * Whether to check enhanced for-loop variable or not.
 140  
      * @param validateEnhancedForLoopVariable whether to check for-loop variable
 141  
      */
 142  
     public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
 143  2
         this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
 144  2
     }
 145  
 
 146  
     @Override
 147  
     public int[] getRequiredTokens() {
 148  56
         return new int[] {
 149  
             TokenTypes.IDENT,
 150  
             TokenTypes.CTOR_DEF,
 151  
             TokenTypes.METHOD_DEF,
 152  
             TokenTypes.SLIST,
 153  
             TokenTypes.OBJBLOCK,
 154  
             TokenTypes.LITERAL_BREAK,
 155  
         };
 156  
     }
 157  
 
 158  
     @Override
 159  
     public int[] getDefaultTokens() {
 160  35
         return new int[] {
 161  
             TokenTypes.IDENT,
 162  
             TokenTypes.CTOR_DEF,
 163  
             TokenTypes.METHOD_DEF,
 164  
             TokenTypes.SLIST,
 165  
             TokenTypes.OBJBLOCK,
 166  
             TokenTypes.LITERAL_BREAK,
 167  
             TokenTypes.VARIABLE_DEF,
 168  
         };
 169  
     }
 170  
 
 171  
     @Override
 172  
     public int[] getAcceptableTokens() {
 173  15
         return new int[] {
 174  
             TokenTypes.IDENT,
 175  
             TokenTypes.CTOR_DEF,
 176  
             TokenTypes.METHOD_DEF,
 177  
             TokenTypes.SLIST,
 178  
             TokenTypes.OBJBLOCK,
 179  
             TokenTypes.LITERAL_BREAK,
 180  
             TokenTypes.VARIABLE_DEF,
 181  
             TokenTypes.PARAMETER_DEF,
 182  
         };
 183  
     }
 184  
 
 185  
     // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block
 186  
     // expressions to separate methods, but that will not increase readability.
 187  
     @Override
 188  
     public void visitToken(DetailAST ast) {
 189  4426
         switch (ast.getType()) {
 190  
             case TokenTypes.OBJBLOCK:
 191  
             case TokenTypes.METHOD_DEF:
 192  
             case TokenTypes.CTOR_DEF:
 193  282
                 scopeStack.push(new ScopeData());
 194  282
                 break;
 195  
             case TokenTypes.SLIST:
 196  1154
                 currentScopeAssignedVariables.push(new ArrayDeque<>());
 197  1154
                 if (ast.getParent().getType() != TokenTypes.CASE_GROUP
 198  104
                     || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP)
 199  104
                     == ast.getParent()) {
 200  1081
                     storePrevScopeUninitializedVariableData();
 201  1081
                     scopeStack.push(new ScopeData());
 202  
                 }
 203  
                 break;
 204  
             case TokenTypes.PARAMETER_DEF:
 205  29
                 if (!isInLambda(ast)
 206  27
                         && ast.findFirstToken(TokenTypes.MODIFIERS)
 207  27
                             .findFirstToken(TokenTypes.FINAL) == null
 208  15
                         && !isInAbstractOrNativeMethod(ast)
 209  10
                         && !ScopeUtils.isInInterfaceBlock(ast)
 210  7
                         && !isMultipleTypeCatch(ast)) {
 211  6
                     insertParameter(ast);
 212  
                 }
 213  
                 break;
 214  
             case TokenTypes.VARIABLE_DEF:
 215  330
                 if (ast.getParent().getType() != TokenTypes.OBJBLOCK
 216  321
                         && ast.findFirstToken(TokenTypes.MODIFIERS)
 217  321
                             .findFirstToken(TokenTypes.FINAL) == null
 218  250
                         && !isVariableInForInit(ast)
 219  213
                         && shouldCheckEnhancedForLoopVariable(ast)) {
 220  209
                     insertVariable(ast);
 221  
                 }
 222  
                 break;
 223  
             case TokenTypes.IDENT:
 224  2526
                 final int parentType = ast.getParent().getType();
 225  2526
                 if (isAssignOperator(parentType) && isFirstChild(ast)) {
 226  610
                     final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast);
 227  610
                     if (candidate.isPresent()) {
 228  431
                         determineAssignmentConditions(ast, candidate.get());
 229  431
                         currentScopeAssignedVariables.peek().add(ast);
 230  
                     }
 231  610
                     removeFinalVariableCandidateFromStack(ast);
 232  610
                 }
 233  
                 break;
 234  
             case TokenTypes.LITERAL_BREAK:
 235  104
                 scopeStack.peek().containsBreak = true;
 236  104
                 break;
 237  
             default:
 238  1
                 throw new IllegalStateException("Incorrect token type");
 239  
         }
 240  4425
     }
 241  
 
 242  
     @Override
 243  
     public void leaveToken(DetailAST ast) {
 244  4425
         Map<String, FinalVariableCandidate> scope = null;
 245  4425
         switch (ast.getType()) {
 246  
             case TokenTypes.OBJBLOCK:
 247  
             case TokenTypes.CTOR_DEF:
 248  
             case TokenTypes.METHOD_DEF:
 249  282
                 scope = scopeStack.pop().scope;
 250  282
                 break;
 251  
             case TokenTypes.SLIST:
 252  
                 // -@cs[MoveVariableInsideIf] assignment value is modified later so it can't be
 253  
                 // moved
 254  1154
                 final Deque<DetailAST> prevScopeUnitializedVariableData =
 255  1154
                     prevScopeUninitializedVariables.peek();
 256  1154
                 boolean containsBreak = false;
 257  1154
                 if (ast.getParent().getType() != TokenTypes.CASE_GROUP
 258  104
                     || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(),
 259  104
                             TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) {
 260  1081
                     containsBreak = scopeStack.peek().containsBreak;
 261  1081
                     scope = scopeStack.pop().scope;
 262  1081
                     prevScopeUninitializedVariables.pop();
 263  
                 }
 264  1154
                 final DetailAST parent = ast.getParent();
 265  1154
                 if (containsBreak || shouldUpdateUninitializedVariables(parent)) {
 266  281
                     updateAllUninitializedVariables(prevScopeUnitializedVariableData);
 267  
                 }
 268  1154
                 updateCurrentScopeAssignedVariables();
 269  1154
                 break;
 270  
             default:
 271  
                 // do nothing
 272  
         }
 273  4425
         if (scope != null) {
 274  1363
             for (FinalVariableCandidate candidate : scope.values()) {
 275  57
                 final DetailAST ident = candidate.variableIdent;
 276  57
                 log(ident.getLineNo(), ident.getColumnNo(), MSG_KEY, ident.getText());
 277  57
             }
 278  
         }
 279  4425
     }
 280  
 
 281  
     /**
 282  
      * Update assigned variables in a temporary stack.
 283  
      */
 284  
     private void updateCurrentScopeAssignedVariables() {
 285  
         // -@cs[MoveVariableInsideIf] assignment value is a modification call so it can't be moved
 286  1154
         final Deque<DetailAST> poppedScopeAssignedVariableData =
 287  1154
                 currentScopeAssignedVariables.pop();
 288  1154
         final Deque<DetailAST> currentScopeAssignedVariableData =
 289  1154
                 currentScopeAssignedVariables.peek();
 290  1154
         if (currentScopeAssignedVariableData != null) {
 291  949
             currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData);
 292  
         }
 293  1154
     }
 294  
 
 295  
     /**
 296  
      * Determines identifier assignment conditions (assigned or already assigned).
 297  
      * @param ident identifier.
 298  
      * @param candidate final local variable candidate.
 299  
      */
 300  
     private static void determineAssignmentConditions(DetailAST ident,
 301  
                                                       FinalVariableCandidate candidate) {
 302  431
         if (candidate.assigned) {
 303  248
             if (!isInSpecificCodeBlock(ident, TokenTypes.LITERAL_ELSE)
 304  166
                     && !isInSpecificCodeBlock(ident, TokenTypes.CASE_GROUP)) {
 305  116
                 candidate.alreadyAssigned = true;
 306  
             }
 307  
         }
 308  
         else {
 309  183
             candidate.assigned = true;
 310  
         }
 311  431
     }
 312  
 
 313  
     /**
 314  
      * Checks whether the scope of a node is restricted to a specific code block.
 315  
      * @param node node.
 316  
      * @param blockType block type.
 317  
      * @return true if the scope of a node is restricted to a specific code block.
 318  
      */
 319  
     private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
 320  414
         boolean returnValue = false;
 321  3393
         for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
 322  3111
             final int type = token.getType();
 323  3111
             if (type == blockType) {
 324  132
                 returnValue = true;
 325  132
                 break;
 326  
             }
 327  
         }
 328  414
         return returnValue;
 329  
     }
 330  
 
 331  
     /**
 332  
      * Gets final variable candidate for ast.
 333  
      * @param ast ast.
 334  
      * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack.
 335  
      */
 336  
     private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) {
 337  610
         Optional<FinalVariableCandidate> result = Optional.empty();
 338  610
         final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
 339  2786
         while (iterator.hasNext() && !result.isPresent()) {
 340  2176
             final ScopeData scopeData = iterator.next();
 341  2176
             result = scopeData.findFinalVariableCandidateForAst(ast);
 342  2176
         }
 343  610
         return result;
 344  
     }
 345  
 
 346  
     /**
 347  
      * Store un-initialized variables in a temporary stack for future use.
 348  
      */
 349  
     private void storePrevScopeUninitializedVariableData() {
 350  1081
         final ScopeData scopeData = scopeStack.peek();
 351  1081
         final Deque<DetailAST> prevScopeUnitializedVariableData =
 352  
                 new ArrayDeque<>();
 353  1081
         scopeData.uninitializedVariables.forEach(prevScopeUnitializedVariableData::push);
 354  1081
         prevScopeUninitializedVariables.push(prevScopeUnitializedVariableData);
 355  1081
     }
 356  
 
 357  
     /**
 358  
      * Update current scope data uninitialized variable according to the whole scope data.
 359  
      * @param prevScopeUnitializedVariableData variable for previous stack of uninitialized
 360  
      *     variables
 361  
      */
 362  
     // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation.
 363  
     private void updateAllUninitializedVariables(
 364  
             Deque<DetailAST> prevScopeUnitializedVariableData) {
 365  
         // Check for only previous scope
 366  281
         updateUninitializedVariables(prevScopeUnitializedVariableData);
 367  
         // Check for rest of the scope
 368  281
         prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables);
 369  281
     }
 370  
 
 371  
     /**
 372  
      * Update current scope data uninitialized variable according to the specific scope data.
 373  
      * @param scopeUnitializedVariableData variable for specific stack of uninitialized variables
 374  
      */
 375  
     private void updateUninitializedVariables(Deque<DetailAST> scopeUnitializedVariableData) {
 376  822
         final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator();
 377  1174
         while (iterator.hasNext()) {
 378  352
             final DetailAST assignedVariable = iterator.next();
 379  352
             for (DetailAST variable : scopeUnitializedVariableData) {
 380  245
                 for (ScopeData scopeData : scopeStack) {
 381  940
                     final FinalVariableCandidate candidate =
 382  940
                         scopeData.scope.get(variable.getText());
 383  940
                     DetailAST storedVariable = null;
 384  940
                     if (candidate != null) {
 385  207
                         storedVariable = candidate.variableIdent;
 386  
                     }
 387  940
                     if (storedVariable != null
 388  207
                             && isSameVariables(storedVariable, variable)
 389  204
                             && isSameVariables(assignedVariable, variable)) {
 390  154
                         scopeData.uninitializedVariables.push(variable);
 391  154
                         iterator.remove();
 392  
                     }
 393  940
                 }
 394  245
             }
 395  352
         }
 396  822
     }
 397  
 
 398  
     /**
 399  
      * If token is LITERAL_IF and there is an {@code else} following or token is CASE_GROUP and
 400  
      * there is another {@code case} following, then update the uninitialized variables.
 401  
      * @param ast token to be checked
 402  
      * @return true if should be updated, else false
 403  
      */
 404  
     private static boolean shouldUpdateUninitializedVariables(DetailAST ast) {
 405  1116
         return isIfTokenWithAnElseFollowing(ast) || isCaseTokenWithAnotherCaseFollowing(ast);
 406  
     }
 407  
 
 408  
     /**
 409  
      * If token is LITERAL_IF and there is an {@code else} following.
 410  
      * @param ast token to be checked
 411  
      * @return true if token is LITERAL_IF and there is an {@code else} following, else false
 412  
      */
 413  
     private static boolean isIfTokenWithAnElseFollowing(DetailAST ast) {
 414  2232
         return ast.getType() == TokenTypes.LITERAL_IF
 415  487
                 && ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE;
 416  
     }
 417  
 
 418  
     /**
 419  
      * If token is CASE_GROUP and there is another {@code case} following.
 420  
      * @param ast token to be checked
 421  
      * @return true if token is CASE_GROUP and there is another {@code case} following, else false
 422  
      */
 423  
     private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) {
 424  1892
         return ast.getType() == TokenTypes.CASE_GROUP
 425  78
                 && findLastChildWhichContainsSpecifiedToken(
 426  78
                         ast.getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) != ast;
 427  
     }
 428  
 
 429  
     /**
 430  
      * Returns the last child token that makes a specified type and contains containType in
 431  
      * its branch.
 432  
      * @param ast token to be tested
 433  
      * @param childType the token type to match
 434  
      * @param containType the token type which has to be present in the branch
 435  
      * @return the matching token, or null if no match
 436  
      */
 437  
     private static DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType,
 438  
                                                               int containType) {
 439  182
         DetailAST returnValue = null;
 440  182
         for (DetailAST astIterator = ast.getFirstChild(); astIterator != null;
 441  2103
                 astIterator = astIterator.getNextSibling()) {
 442  2103
             if (astIterator.getType() == childType
 443  1193
                     && astIterator.findFirstToken(containType) != null) {
 444  1182
                 returnValue = astIterator;
 445  
             }
 446  
         }
 447  182
         return returnValue;
 448  
     }
 449  
 
 450  
     /**
 451  
      * Determines whether enhanced for-loop variable should be checked or not.
 452  
      * @param ast The ast to compare.
 453  
      * @return true if enhanced for-loop variable should be checked.
 454  
      */
 455  
     private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
 456  426
         return validateEnhancedForLoopVariable
 457  211
                 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
 458  
     }
 459  
 
 460  
     /**
 461  
      * Insert a parameter at the topmost scope stack.
 462  
      * @param ast the variable to insert.
 463  
      */
 464  
     private void insertParameter(DetailAST ast) {
 465  6
         final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
 466  6
         final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
 467  6
         scope.put(astNode.getText(), new FinalVariableCandidate(astNode));
 468  6
     }
 469  
 
 470  
     /**
 471  
      * Insert a variable at the topmost scope stack.
 472  
      * @param ast the variable to insert.
 473  
      */
 474  
     private void insertVariable(DetailAST ast) {
 475  209
         final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
 476  209
         final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
 477  209
         scope.put(astNode.getText(), new FinalVariableCandidate(astNode));
 478  209
         if (!isInitialized(astNode)) {
 479  170
             scopeStack.peek().uninitializedVariables.add(astNode);
 480  
         }
 481  209
     }
 482  
 
 483  
     /**
 484  
      * Check if VARIABLE_DEF is initialized or not.
 485  
      * @param ast VARIABLE_DEF to be checked
 486  
      * @return true if initialized
 487  
      */
 488  
     private static boolean isInitialized(DetailAST ast) {
 489  209
         return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN;
 490  
     }
 491  
 
 492  
     /**
 493  
      * Whether the ast is the first child of its parent.
 494  
      * @param ast the ast to check.
 495  
      * @return true if the ast is the first child of its parent.
 496  
      */
 497  
     private static boolean isFirstChild(DetailAST ast) {
 498  619
         return ast.getPreviousSibling() == null;
 499  
     }
 500  
 
 501  
     /**
 502  
      * Removes the final variable candidate from the Stack.
 503  
      * @param ast variable to remove.
 504  
      */
 505  
     private void removeFinalVariableCandidateFromStack(DetailAST ast) {
 506  610
         final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
 507  2355
         while (iterator.hasNext()) {
 508  2176
             final ScopeData scopeData = iterator.next();
 509  2176
             final Map<String, FinalVariableCandidate> scope = scopeData.scope;
 510  2176
             final FinalVariableCandidate candidate = scope.get(ast.getText());
 511  2176
             DetailAST storedVariable = null;
 512  2176
             if (candidate != null) {
 513  442
                 storedVariable = candidate.variableIdent;
 514  
             }
 515  2176
             if (storedVariable != null && isSameVariables(storedVariable, ast)) {
 516  431
                 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) {
 517  158
                     scope.remove(ast.getText());
 518  
                 }
 519  
                 break;
 520  
             }
 521  1745
         }
 522  610
     }
 523  
 
 524  
     /**
 525  
      * Check if given parameter definition is a multiple type catch.
 526  
      * @param parameterDefAst parameter definition
 527  
      * @return true if it is a multiple type catch, false otherwise
 528  
      */
 529  
     private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) {
 530  7
         final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE);
 531  7
         return typeAst.getFirstChild().getType() == TokenTypes.BOR;
 532  
     }
 533  
 
 534  
     /**
 535  
      * Whether the final variable candidate should be removed from the list of final local variable
 536  
      * candidates.
 537  
      * @param scopeData the scope data of the variable.
 538  
      * @param ast the variable ast.
 539  
      * @return true, if the variable should be removed.
 540  
      */
 541  
     private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) {
 542  431
         boolean shouldRemove = true;
 543  431
         for (DetailAST variable : scopeData.uninitializedVariables) {
 544  328
             if (variable.getText().equals(ast.getText())) {
 545  
                 // if the variable is declared outside the loop and initialized inside
 546  
                 // the loop, then it cannot be declared final, as it can be initialized
 547  
                 // more than once in this case
 548  307
                 if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) {
 549  301
                     final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText());
 550  301
                     shouldRemove = candidate.alreadyAssigned;
 551  
                 }
 552  307
                 scopeData.uninitializedVariables.remove(variable);
 553  307
                 break;
 554  
             }
 555  21
         }
 556  431
         return shouldRemove;
 557  
     }
 558  
 
 559  
     /**
 560  
      * Checks whether a variable which is declared outside loop is used inside loop.
 561  
      * For example:
 562  
      * <p>
 563  
      * {@code
 564  
      * int x;
 565  
      * for (int i = 0, j = 0; i < j; i++) {
 566  
      *     x = 5;
 567  
      * }
 568  
      * }
 569  
      * </p>
 570  
      * @param variable variable.
 571  
      * @return true if a variable which is declared outside loop is used inside loop.
 572  
      */
 573  
     private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) {
 574  297
         DetailAST loop2 = variable.getParent();
 575  3106
         while (loop2 != null
 576  2815
             && !isLoopAst(loop2.getType())) {
 577  2809
             loop2 = loop2.getParent();
 578  
         }
 579  297
         return loop2 != null;
 580  
     }
 581  
 
 582  
     /**
 583  
      * Is Arithmetic operator.
 584  
      * @param parentType token AST
 585  
      * @return true is token type is in arithmetic operator
 586  
      */
 587  
     private static boolean isAssignOperator(int parentType) {
 588  2526
         return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0;
 589  
     }
 590  
 
 591  
     /**
 592  
      * Checks if current variable is defined in
 593  
      *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
 594  
      * <p>
 595  
      * {@code
 596  
      * for (int i = 0, j = 0; i < j; i++) { . . . }
 597  
      * }
 598  
      * </p>
 599  
      * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init}
 600  
      * @param variableDef variable definition node.
 601  
      * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
 602  
      */
 603  
     private static boolean isVariableInForInit(DetailAST variableDef) {
 604  250
         return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
 605  
     }
 606  
 
 607  
     /**
 608  
      * Determines whether an AST is a descendant of an abstract or native method.
 609  
      * @param ast the AST to check.
 610  
      * @return true if ast is a descendant of an abstract or native method.
 611  
      */
 612  
     private static boolean isInAbstractOrNativeMethod(DetailAST ast) {
 613  15
         boolean abstractOrNative = false;
 614  15
         DetailAST parent = ast.getParent();
 615  75
         while (parent != null && !abstractOrNative) {
 616  60
             if (parent.getType() == TokenTypes.METHOD_DEF) {
 617  16
                 final DetailAST modifiers =
 618  16
                     parent.findFirstToken(TokenTypes.MODIFIERS);
 619  16
                 abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null
 620  15
                         || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null;
 621  
             }
 622  60
             parent = parent.getParent();
 623  
         }
 624  15
         return abstractOrNative;
 625  
     }
 626  
 
 627  
     /**
 628  
      * Check if current param is lambda's param.
 629  
      * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}.
 630  
      * @return true if current param is lambda's param.
 631  
      */
 632  
     private static boolean isInLambda(DetailAST paramDef) {
 633  29
         return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
 634  
     }
 635  
 
 636  
     /**
 637  
      * Find the Class, Constructor, Enum, Method, or Field in which it is defined.
 638  
      * @param ast Variable for which we want to find the scope in which it is defined
 639  
      * @return ast The Class or Constructor or Method in which it is defined.
 640  
      */
 641  
     private static DetailAST findFirstUpperNamedBlock(DetailAST ast) {
 642  2590
         DetailAST astTraverse = ast;
 643  15986
         while (astTraverse.getType() != TokenTypes.METHOD_DEF
 644  13416
                 && astTraverse.getType() != TokenTypes.CLASS_DEF
 645  13412
                 && astTraverse.getType() != TokenTypes.ENUM_DEF
 646  13408
                 && astTraverse.getType() != TokenTypes.CTOR_DEF
 647  13400
                 && !ScopeUtils.isClassFieldDef(astTraverse)) {
 648  13396
             astTraverse = astTraverse.getParent();
 649  
         }
 650  2590
         return astTraverse;
 651  
     }
 652  
 
 653  
     /**
 654  
      * Check if both the Variables are same.
 655  
      * @param ast1 Variable to compare
 656  
      * @param ast2 Variable to compare
 657  
      * @return true if both the variables are same, otherwise false
 658  
      */
 659  
     private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
 660  1295
         final DetailAST classOrMethodOfAst1 =
 661  1295
             findFirstUpperNamedBlock(ast1);
 662  1295
         final DetailAST classOrMethodOfAst2 =
 663  1295
             findFirstUpperNamedBlock(ast2);
 664  1295
         return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText());
 665  
     }
 666  
 
 667  
     /**
 668  
      * Check if both the variables are in the same loop.
 669  
      * @param ast1 variable to compare.
 670  
      * @param ast2 variable to compare.
 671  
      * @return true if both the variables are in the same loop.
 672  
      */
 673  
     private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) {
 674  307
         DetailAST loop1 = ast1.getParent();
 675  1907
         while (loop1 != null && !isLoopAst(loop1.getType())) {
 676  1600
             loop1 = loop1.getParent();
 677  
         }
 678  307
         DetailAST loop2 = ast2.getParent();
 679  3166
         while (loop2 != null && !isLoopAst(loop2.getType())) {
 680  2859
             loop2 = loop2.getParent();
 681  
         }
 682  307
         return loop1 != null && loop1 == loop2;
 683  
     }
 684  
 
 685  
     /**
 686  
      * Checks whether the ast is a loop.
 687  
      * @param ast the ast to check.
 688  
      * @return true if the ast is a loop.
 689  
      */
 690  
     private static boolean isLoopAst(int ast) {
 691  7301
         return Arrays.binarySearch(LOOP_TYPES, ast) >= 0;
 692  
     }
 693  
 
 694  
     /**
 695  
      * Holder for the scope data.
 696  
      */
 697  11049
     private static class ScopeData {
 698  
         /** Contains variable definitions. */
 699  1363
         private final Map<String, FinalVariableCandidate> scope = new HashMap<>();
 700  
 
 701  
         /** Contains definitions of uninitialized variables. */
 702  1363
         private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>();
 703  
 
 704  
         /** Whether there is a {@code break} in the scope. */
 705  
         private boolean containsBreak;
 706  
 
 707  
         /**
 708  
          * Searches for final local variable candidate for ast in the scope.
 709  
          * @param ast ast.
 710  
          * @return Optional of {@link FinalVariableCandidate}.
 711  
          */
 712  
         public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) {
 713  2176
             Optional<FinalVariableCandidate> result = Optional.empty();
 714  2176
             DetailAST storedVariable = null;
 715  2176
             final Optional<FinalVariableCandidate> candidate =
 716  2176
                 Optional.ofNullable(scope.get(ast.getText()));
 717  2176
             if (candidate.isPresent()) {
 718  442
                 storedVariable = candidate.get().variableIdent;
 719  
             }
 720  2176
             if (storedVariable != null && isSameVariables(storedVariable, ast)) {
 721  431
                 result = candidate;
 722  
             }
 723  2176
             return result;
 724  
         }
 725  
     }
 726  
 
 727  
     /**Represents information about final local variable candidate. */
 728  2179
     private static class FinalVariableCandidate {
 729  
         /** Identifier token. */
 730  
         private final DetailAST variableIdent;
 731  
         /** Whether the variable is assigned. */
 732  
         private boolean assigned;
 733  
         /** Whether the variable is already assigned. */
 734  
         private boolean alreadyAssigned;
 735  
 
 736  
         /**
 737  
          * Creates new instance.
 738  
          * @param variableIdent variable identifier.
 739  
          */
 740  215
         FinalVariableCandidate(DetailAST variableIdent) {
 741  215
             this.variableIdent = variableIdent;
 742  215
         }
 743  
     }
 744  
 }