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