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