1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.CheckUtil;
35  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
36  
37  /**
38   * <p>
39   * Checks that local variables that never have their values changed are declared final.
40   * The check can be configured to also check that unchanged parameters are declared final.
41   * </p>
42   * <p>
43   * When configured to check parameters, the check ignores parameters of interface
44   * methods and abstract methods.
45   * </p>
46   * <ul>
47   * <li>
48   * Property {@code validateEnhancedForLoopVariable} - Control whether to check
49   * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
50   * enhanced for-loop</a> variable.
51   * Default value is {@code false}.
52   * </li>
53   * <li>
54   * Property {@code tokens} - tokens to check
55   * Default value is:
56   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
57   * VARIABLE_DEF</a>.
58   * </li>
59   * </ul>
60   * <p>
61   * To configure the check:
62   * </p>
63   * <pre>
64   * &lt;module name=&quot;FinalLocalVariable&quot;/&gt;
65   * </pre>
66   * <p>
67   * To configure the check so that it checks local variables and parameters:
68   * </p>
69   * <pre>
70   * &lt;module name=&quot;FinalLocalVariable&quot;&gt;
71   *   &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF,PARAMETER_DEF&quot;/&gt;
72   * &lt;/module&gt;
73   * </pre>
74   * <p>
75   * By default, this Check skip final validation on
76   *  <a href = "https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
77   * Enhanced For-Loop</a>.
78   * </p>
79   * <p>
80   * Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable
81   *  from Enhanced For Loop.
82   * </p>
83   * <p>
84   * An example of how to configure the check so that it also validates enhanced For Loop Variable is:
85   * </p>
86   * <pre>
87   * &lt;module name=&quot;FinalLocalVariable&quot;&gt;
88   *   &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF&quot;/&gt;
89   *   &lt;property name=&quot;validateEnhancedForLoopVariable&quot; value=&quot;true&quot;/&gt;
90   * &lt;/module&gt;
91   * </pre>
92   * <p>Example:</p>
93   * <pre>
94   * for (int number : myNumbers) { // violation
95   *   System.out.println(number);
96   * }
97   * </pre>
98   * <p>
99   * An example of how to configure check on local variables and parameters
100  * but do not validate loop variables:
101  * </p>
102  * <pre>
103  * &lt;module name=&quot;FinalLocalVariable&quot;&gt;
104  *    &lt;property name=&quot;tokens&quot; value=&quot;VARIABLE_DEF,PARAMETER_DEF&quot;/&gt;
105  *    &lt;property name=&quot;validateEnhancedForLoopVariable&quot; value=&quot;false&quot;/&gt;
106  *  &lt;/module&gt;
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
128 public 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 }