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