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