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