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