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