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