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