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