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