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