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