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