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