001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 * <div>
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 * </div>
043 *
044 * <p>
045 * Notes:
046 * When configured to check parameters, the check ignores parameters of interface
047 * methods and abstract methods.
048 * </p>
049 *
050 * @since 3.2
051 */
052@FileStatefulCheck
053public class FinalLocalVariableCheck extends AbstractCheck {
054
055    /**
056     * A key is pointing to the warning message text in "messages.properties"
057     * file.
058     */
059    public static final String MSG_KEY = "final.variable";
060
061    /**
062     * Assign operator types.
063     */
064    private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet(
065        TokenTypes.POST_INC,
066        TokenTypes.POST_DEC,
067        TokenTypes.ASSIGN,
068        TokenTypes.PLUS_ASSIGN,
069        TokenTypes.MINUS_ASSIGN,
070        TokenTypes.STAR_ASSIGN,
071        TokenTypes.DIV_ASSIGN,
072        TokenTypes.MOD_ASSIGN,
073        TokenTypes.SR_ASSIGN,
074        TokenTypes.BSR_ASSIGN,
075        TokenTypes.SL_ASSIGN,
076        TokenTypes.BAND_ASSIGN,
077        TokenTypes.BXOR_ASSIGN,
078        TokenTypes.BOR_ASSIGN,
079        TokenTypes.INC,
080        TokenTypes.DEC
081    );
082
083    /**
084     * Loop types.
085     */
086    private static final BitSet LOOP_TYPES = TokenUtil.asBitSet(
087        TokenTypes.LITERAL_FOR,
088        TokenTypes.LITERAL_WHILE,
089        TokenTypes.LITERAL_DO
090    );
091
092    /** Scope Deque. */
093    private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
094
095    /** Assigned variables of current scope. */
096    private final Deque<Deque<DetailAST>> currentScopeAssignedVariables =
097            new ArrayDeque<>();
098
099    /**
100     * Control whether to check
101     * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
102     * enhanced for-loop</a> variable.
103     */
104    private boolean validateEnhancedForLoopVariable;
105
106    /**
107     * Control whether to check
108     * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html">
109     * unnamed variables</a>.
110     */
111    private boolean validateUnnamedVariables;
112
113    /**
114     * Control whether to check
115     * <a href="https://openjdk.org/jeps/394">
116     * pattern variables</a>.
117     */
118    private boolean validatePatternVariables;
119
120    /**
121     * Setter to control whether to check
122     * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
123     * enhanced for-loop</a> variable.
124     *
125     * @param validateEnhancedForLoopVariable whether to check for-loop variable
126     * @since 6.5
127     */
128    public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
129        this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
130    }
131
132    /**
133     * Setter to control whether to check
134     * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html">
135     * unnamed variables</a>.
136     *
137     * @param validateUnnamedVariables whether to check unnamed variables
138     * @since 10.18.0
139     */
140    public final void setValidateUnnamedVariables(boolean validateUnnamedVariables) {
141        this.validateUnnamedVariables = validateUnnamedVariables;
142    }
143
144    /**
145     * Setter to control whether to check
146     * <a href="https://openjdk.org/jeps/394">
147     * pattern variables></a>.
148     *
149     * @param validatePatternVariables whether to check pattern variables
150     * @since 13.4.0
151     */
152    public final void setValidatePatternVariables(boolean validatePatternVariables) {
153        this.validatePatternVariables = validatePatternVariables;
154    }
155
156    @Override
157    public int[] getRequiredTokens() {
158        return new int[] {
159            TokenTypes.IDENT,
160            TokenTypes.CTOR_DEF,
161            TokenTypes.METHOD_DEF,
162            TokenTypes.SLIST,
163            TokenTypes.OBJBLOCK,
164            TokenTypes.LITERAL_BREAK,
165            TokenTypes.LITERAL_FOR,
166            TokenTypes.EXPR,
167            TokenTypes.PATTERN_VARIABLE_DEF,
168        };
169    }
170
171    @Override
172    public int[] getDefaultTokens() {
173        return new int[] {
174            TokenTypes.IDENT,
175            TokenTypes.CTOR_DEF,
176            TokenTypes.METHOD_DEF,
177            TokenTypes.SLIST,
178            TokenTypes.OBJBLOCK,
179            TokenTypes.LITERAL_BREAK,
180            TokenTypes.LITERAL_FOR,
181            TokenTypes.VARIABLE_DEF,
182            TokenTypes.EXPR,
183            TokenTypes.PATTERN_VARIABLE_DEF,
184        };
185    }
186
187    @Override
188    public int[] getAcceptableTokens() {
189        return new int[] {
190            TokenTypes.IDENT,
191            TokenTypes.CTOR_DEF,
192            TokenTypes.METHOD_DEF,
193            TokenTypes.SLIST,
194            TokenTypes.OBJBLOCK,
195            TokenTypes.LITERAL_BREAK,
196            TokenTypes.LITERAL_FOR,
197            TokenTypes.VARIABLE_DEF,
198            TokenTypes.PARAMETER_DEF,
199            TokenTypes.EXPR,
200            TokenTypes.PATTERN_VARIABLE_DEF,
201        };
202    }
203
204    // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block
205    // expressions to separate methods, but that will not increase readability.
206    @Override
207    public void visitToken(DetailAST ast) {
208        switch (ast.getType()) {
209            case TokenTypes.OBJBLOCK, TokenTypes.METHOD_DEF,
210                 TokenTypes.CTOR_DEF, TokenTypes.LITERAL_FOR ->
211                scopeStack.push(new ScopeData());
212
213            case TokenTypes.SLIST -> {
214                currentScopeAssignedVariables.push(new ArrayDeque<>());
215                if (ast.getParent().getType() != TokenTypes.CASE_GROUP
216                    || ast.getParent().getParent()
217                    .findFirstToken(TokenTypes.CASE_GROUP) == ast.getParent()) {
218                    storePrevScopeUninitializedVariableData();
219                    scopeStack.push(new ScopeData());
220                }
221            }
222
223            case TokenTypes.PARAMETER_DEF -> {
224                if (!isInLambda(ast)
225                        && ast.findFirstToken(TokenTypes.MODIFIERS)
226                            .findFirstToken(TokenTypes.FINAL) == null
227                        && !isInMethodWithoutBody(ast)
228                        && !isMultipleTypeCatch(ast)
229                        && !CheckUtil.isReceiverParameter(ast)) {
230                    insertParameter(ast);
231                }
232            }
233
234            case TokenTypes.VARIABLE_DEF -> {
235                if (ast.getParent().getType() != TokenTypes.OBJBLOCK
236                        && ast.findFirstToken(TokenTypes.MODIFIERS)
237                            .findFirstToken(TokenTypes.FINAL) == null
238                        && !isVariableInForInit(ast)
239                        && shouldCheckEnhancedForLoopVariable(ast)
240                        && shouldCheckUnnamedVariable(ast)) {
241                    insertVariable(ast);
242                }
243            }
244
245            case TokenTypes.IDENT -> {
246                final int parentType = ast.getParent().getType();
247                if (isAssignOperator(parentType) && isFirstChild(ast)) {
248                    final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast);
249                    if (candidate.isPresent()) {
250                        determineAssignmentConditions(ast, candidate.orElseThrow());
251                        currentScopeAssignedVariables.peek().add(ast);
252                    }
253                    removeFinalVariableCandidateFromStack(ast);
254                }
255            }
256
257            case TokenTypes.LITERAL_BREAK -> scopeStack.peek().containsBreak = true;
258
259            case TokenTypes.EXPR -> {
260                // Switch labeled expression has no slist
261                if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) {
262                    storePrevScopeUninitializedVariableData();
263                }
264            }
265
266            case TokenTypes.PATTERN_VARIABLE_DEF -> {
267                if (validatePatternVariables && isNotChildOfAssign(ast)
268                        && ast.findFirstToken(TokenTypes.MODIFIERS)
269                        .findFirstToken(TokenTypes.FINAL) == null) {
270                    insertPatternVariable(ast);
271                }
272            }
273
274            default -> throw new IllegalStateException("Incorrect token type");
275        }
276    }
277
278    @Override
279    public void leaveToken(DetailAST ast) {
280        Map<String, FinalVariableCandidate> scope = null;
281        final DetailAST parentAst = ast.getParent();
282        switch (ast.getType()) {
283            case TokenTypes.OBJBLOCK, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF,
284                 TokenTypes.LITERAL_FOR ->
285                scope = scopeStack.pop().scope;
286
287            case TokenTypes.EXPR -> {
288                // Switch labeled expression has no slist
289                if (parentAst.getType() == TokenTypes.SWITCH_RULE
290                    && shouldUpdateUninitializedVariables(parentAst)) {
291                    updateAllUninitializedVariables();
292                }
293            }
294
295            case TokenTypes.SLIST -> {
296                boolean containsBreak = false;
297                if (parentAst.getType() != TokenTypes.CASE_GROUP
298                    || findLastCaseGroupWhichContainsSlist(parentAst.getParent())
299                    == parentAst) {
300                    containsBreak = scopeStack.peek().containsBreak;
301                    scope = scopeStack.pop().scope;
302                }
303                if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) {
304                    updateAllUninitializedVariables();
305                }
306                updateCurrentScopeAssignedVariables();
307            }
308
309            default -> {
310                // do nothing
311            }
312        }
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(ast)) {
545            scopeStack.peek().uninitializedVariables.add(astNode);
546        }
547    }
548
549    /**
550     * Insert a pattern variable at the topmost scope stack.
551     *
552     * @param ast the pattern variable to insert.
553     */
554    private void insertPatternVariable(DetailAST ast) {
555        final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
556        final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
557        final FinalVariableCandidate previousCandidate = scope.get(astNode.getText());
558        if (previousCandidate != null) {
559            final DetailAST ident = previousCandidate.variableIdent;
560            log(ident, MSG_KEY, ident.getText());
561        }
562        final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode);
563        scope.put(astNode.getText(), candidate);
564    }
565
566    /**
567     * Check if VARIABLE_DEF is initialized or not.
568     *
569     * @param ast VARIABLE_DEF to be checked
570     * @return true if initialized
571     */
572    private static boolean isInitialized(DetailAST ast) {
573        return ast.getLastChild().getType() == TokenTypes.ASSIGN;
574    }
575
576    /**
577     * Whether the ast is the first child of its parent.
578     *
579     * @param ast the ast to check.
580     * @return true if the ast is the first child of its parent.
581     */
582    private static boolean isFirstChild(DetailAST ast) {
583        return ast.getPreviousSibling() == null;
584    }
585
586    /**
587     * Checks if pattern variable is not used for assign.
588     *
589     * @param ast PATTERN_VARIABLE_DEF.
590     * @return true if it's in conditional statement.
591     */
592    private static boolean isNotChildOfAssign(DetailAST ast) {
593        return ast.getParent().getParent()
594                .getParent().getType() != TokenTypes.ASSIGN;
595    }
596
597    /**
598     * Removes the final variable candidate from the Stack.
599     *
600     * @param ast variable to remove.
601     */
602    private void removeFinalVariableCandidateFromStack(DetailAST ast) {
603        final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
604        while (iterator.hasNext()) {
605            final ScopeData scopeData = iterator.next();
606            final Map<String, FinalVariableCandidate> scope = scopeData.scope;
607            final FinalVariableCandidate candidate = scope.get(ast.getText());
608            DetailAST storedVariable = null;
609            if (candidate != null) {
610                storedVariable = candidate.variableIdent;
611            }
612            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
613                if (shouldRemoveFinalVariableCandidate(scopeData, ast)) {
614                    scope.remove(ast.getText());
615                }
616                break;
617            }
618        }
619    }
620
621    /**
622     * Check if given parameter definition is a multiple type catch.
623     *
624     * @param parameterDefAst parameter definition
625     * @return true if it is a multiple type catch, false otherwise
626     */
627    private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) {
628        final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE);
629        return typeAst.findFirstToken(TokenTypes.BOR) != null;
630    }
631
632    /**
633     * Whether the final variable candidate should be removed from the list of final local variable
634     * candidates.
635     *
636     * @param scopeData the scope data of the variable.
637     * @param ast the variable ast.
638     * @return true, if the variable should be removed.
639     */
640    private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) {
641        boolean shouldRemove = true;
642        for (DetailAST variable : scopeData.uninitializedVariables) {
643            if (variable.getText().equals(ast.getText())) {
644                // if the variable is declared outside the loop and initialized inside
645                // the loop, then it cannot be declared final, as it can be initialized
646                // more than once in this case
647                final DetailAST currAstLoopAstParent = getParentLoop(ast);
648                final DetailAST currVarLoopAstParent = getParentLoop(variable);
649                if (currAstLoopAstParent == currVarLoopAstParent) {
650                    final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText());
651                    shouldRemove = candidate.alreadyAssigned;
652                }
653                scopeData.uninitializedVariables.remove(variable);
654                break;
655            }
656        }
657        return shouldRemove;
658    }
659
660    /**
661     * Get the ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor
662     * of the current ast node, if there is no such node, null is returned.
663     *
664     * @param ast ast node
665     * @return ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor
666     *         of the current ast node, null if no such node exists
667     */
668    private static DetailAST getParentLoop(DetailAST ast) {
669        DetailAST parentLoop = ast;
670        while (parentLoop != null
671            && !isLoopAst(parentLoop.getType())) {
672            parentLoop = parentLoop.getParent();
673        }
674        return parentLoop;
675    }
676
677    /**
678     * Is Arithmetic operator.
679     *
680     * @param parentType token AST
681     * @return true is token type is in arithmetic operator
682     */
683    private static boolean isAssignOperator(int parentType) {
684        return ASSIGN_OPERATOR_TYPES.get(parentType);
685    }
686
687    /**
688     * Checks if current variable is defined in
689     *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
690     *
691     * <p>
692     * {@code
693     * for (int i = 0, j = 0; i < j; i++) { . . . }
694     * }
695     * </p>
696     * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init}
697     *
698     * @param variableDef variable definition node.
699     * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
700     */
701    private static boolean isVariableInForInit(DetailAST variableDef) {
702        return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
703    }
704
705    /**
706     * Checks if a parameter is within a method that has no implementation body.
707     *
708     * @param parameterDefAst the AST node representing the parameter definition
709     * @return true if the parameter is in a method without a body
710     */
711    private static boolean isInMethodWithoutBody(DetailAST parameterDefAst) {
712        final DetailAST methodDefAst = parameterDefAst.getParent().getParent();
713        return methodDefAst.findFirstToken(TokenTypes.SLIST) == null;
714    }
715
716    /**
717     * Check if current param is lambda's param.
718     *
719     * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}.
720     * @return true if current param is lambda's param.
721     */
722    private static boolean isInLambda(DetailAST paramDef) {
723        return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
724    }
725
726    /**
727     * Find the Class, Constructor, Enum, Method, or Field in which it is defined.
728     *
729     * @param ast Variable for which we want to find the scope in which it is defined
730     * @return ast The Class or Constructor or Method in which it is defined.
731     */
732    private static DetailAST findFirstUpperNamedBlock(DetailAST ast) {
733        DetailAST astTraverse = ast;
734        while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF,
735                TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF)
736                && !ScopeUtil.isClassFieldDef(astTraverse)) {
737            astTraverse = astTraverse.getParent();
738        }
739        return astTraverse;
740    }
741
742    /**
743     * Check if both the Variables are same.
744     *
745     * @param ast1 Variable to compare
746     * @param ast2 Variable to compare
747     * @return true if both the variables are same, otherwise false
748     */
749    private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
750        final DetailAST classOrMethodOfAst1 =
751            findFirstUpperNamedBlock(ast1);
752        final DetailAST classOrMethodOfAst2 =
753            findFirstUpperNamedBlock(ast2);
754        return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText());
755    }
756
757    /**
758     * Checks whether the ast is a loop.
759     *
760     * @param ast the ast to check.
761     * @return true if the ast is a loop.
762     */
763    private static boolean isLoopAst(int ast) {
764        return LOOP_TYPES.get(ast);
765    }
766
767    /**
768     * Holder for the scope data.
769     */
770    private static final class ScopeData {
771
772        /** Contains variable definitions. */
773        private final Map<String, FinalVariableCandidate> scope = new HashMap<>();
774
775        /** Contains definitions of uninitialized variables. */
776        private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>();
777
778        /** Contains definitions of previous scope uninitialized variables. */
779        private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>();
780
781        /** Whether there is a {@code break} in the scope. */
782        private boolean containsBreak;
783
784        /**
785         * Searches for final local variable candidate for ast in the scope.
786         *
787         * @param ast ast.
788         * @return Optional of {@link FinalVariableCandidate}.
789         */
790        /* package */ Optional<FinalVariableCandidate>
791            findFinalVariableCandidateForAst(DetailAST ast) {
792            Optional<FinalVariableCandidate> result = Optional.empty();
793            DetailAST storedVariable = null;
794            final Optional<FinalVariableCandidate> candidate =
795                Optional.ofNullable(scope.get(ast.getText()));
796            if (candidate.isPresent()) {
797                storedVariable = candidate.orElseThrow().variableIdent;
798            }
799            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
800                result = candidate;
801            }
802            return result;
803        }
804
805    }
806
807    /** Represents information about final local variable candidate. */
808    private static final class FinalVariableCandidate {
809
810        /** Identifier token. */
811        private final DetailAST variableIdent;
812        /** Whether the variable is assigned. */
813        private boolean assigned;
814        /** Whether the variable is already assigned. */
815        private boolean alreadyAssigned;
816
817        /**
818         * Creates new instance.
819         *
820         * @param variableIdent variable identifier.
821         */
822        private FinalVariableCandidate(DetailAST variableIdent) {
823            this.variableIdent = variableIdent;
824        }
825
826    }
827
828}