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