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