001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.ArrayDeque;
023import java.util.BitSet;
024import java.util.Deque;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Optional;
029
030import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
031import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
035import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
036import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
037
038/**
039 * <p>
040 * Checks that local variables that never have their values changed are declared final.
041 * The check can be configured to also check that unchanged parameters are declared final.
042 * </p>
043 * <p>
044 * When configured to check parameters, the check ignores parameters of interface
045 * methods and abstract methods.
046 * </p>
047 * <ul>
048 * <li>
049 * Property {@code validateEnhancedForLoopVariable} - Control whether to check
050 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
051 * enhanced for-loop</a> variable.
052 * Type is {@code boolean}.
053 * Default value is {@code false}.
054 * </li>
055 * <li>
056 * Property {@code tokens} - tokens to check
057 * Type is {@code java.lang.String[]}.
058 * Validation type is {@code tokenSet}.
059 * Default value is:
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
061 * VARIABLE_DEF</a>.
062 * </li>
063 * </ul>
064 * <p>
065 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
066 * </p>
067 * <p>
068 * Violation Message Keys:
069 * </p>
070 * <ul>
071 * <li>
072 * {@code final.variable}
073 * </li>
074 * </ul>
075 *
076 * @since 3.2
077 */
078@FileStatefulCheck
079public class FinalLocalVariableCheck extends AbstractCheck {
080
081    /**
082     * A key is pointing to the warning message text in "messages.properties"
083     * file.
084     */
085    public static final String MSG_KEY = "final.variable";
086
087    /**
088     * Assign operator types.
089     */
090    private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet(
091        TokenTypes.POST_INC,
092        TokenTypes.POST_DEC,
093        TokenTypes.ASSIGN,
094        TokenTypes.PLUS_ASSIGN,
095        TokenTypes.MINUS_ASSIGN,
096        TokenTypes.STAR_ASSIGN,
097        TokenTypes.DIV_ASSIGN,
098        TokenTypes.MOD_ASSIGN,
099        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}