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    /** Controls whether to check enhanced for-loop variable. */
124    private boolean validateEnhancedForLoopVariable;
125
126    static {
127        // Array sorting for binary search
128        Arrays.sort(ASSIGN_OPERATOR_TYPES);
129        Arrays.sort(LOOP_TYPES);
130    }
131
132    /**
133     * Whether to check enhanced for-loop variable or not.
134     * @param validateEnhancedForLoopVariable whether to check for-loop variable
135     */
136    public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
137        this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
138    }
139
140    @Override
141    public int[] getRequiredTokens() {
142        return new int[] {
143            TokenTypes.IDENT,
144            TokenTypes.CTOR_DEF,
145            TokenTypes.METHOD_DEF,
146            TokenTypes.SLIST,
147            TokenTypes.OBJBLOCK,
148        };
149    }
150
151    @Override
152    public int[] getDefaultTokens() {
153        return new int[] {
154            TokenTypes.IDENT,
155            TokenTypes.CTOR_DEF,
156            TokenTypes.METHOD_DEF,
157            TokenTypes.SLIST,
158            TokenTypes.OBJBLOCK,
159            TokenTypes.VARIABLE_DEF,
160        };
161    }
162
163    @Override
164    public int[] getAcceptableTokens() {
165        return new int[] {
166            TokenTypes.IDENT,
167            TokenTypes.CTOR_DEF,
168            TokenTypes.METHOD_DEF,
169            TokenTypes.SLIST,
170            TokenTypes.OBJBLOCK,
171            TokenTypes.VARIABLE_DEF,
172            TokenTypes.PARAMETER_DEF,
173        };
174    }
175
176    // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block
177    // expressions to separate methods, but that will not increase readability.
178    @Override
179    public void visitToken(DetailAST ast) {
180        switch (ast.getType()) {
181            case TokenTypes.OBJBLOCK:
182            case TokenTypes.METHOD_DEF:
183            case TokenTypes.CTOR_DEF:
184                scopeStack.push(new ScopeData());
185                break;
186            case TokenTypes.SLIST:
187                if (ast.getParent().getType() != TokenTypes.CASE_GROUP
188                    || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP)
189                    == ast.getParent()) {
190                    storePrevScopeUninitializedVariableData();
191                    scopeStack.push(new ScopeData());
192                }
193                break;
194            case TokenTypes.PARAMETER_DEF:
195                if (!isInLambda(ast)
196                        && !ast.branchContains(TokenTypes.FINAL)
197                        && !isInAbstractOrNativeMethod(ast)
198                        && !ScopeUtils.isInInterfaceBlock(ast)
199                        && !isMultipleTypeCatch(ast)) {
200                    insertParameter(ast);
201                }
202                break;
203            case TokenTypes.VARIABLE_DEF:
204                if (ast.getParent().getType() != TokenTypes.OBJBLOCK
205                        && !ast.branchContains(TokenTypes.FINAL)
206                        && !isVariableInForInit(ast)
207                        && shouldCheckEnhancedForLoopVariable(ast)) {
208                    insertVariable(ast);
209                }
210                break;
211
212            case TokenTypes.IDENT:
213                final int parentType = ast.getParent().getType();
214                if (isAssignOperator(parentType) && isFirstChild(ast)) {
215                    final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast);
216                    if (candidate.isPresent()) {
217                        determineAssignmentConditions(ast, candidate.get());
218                    }
219                    removeFinalVariableCandidateFromStack(ast);
220                }
221                break;
222
223            default:
224                throw new IllegalStateException("Incorrect token type");
225        }
226    }
227
228    @Override
229    public void leaveToken(DetailAST ast) {
230        Map<String, FinalVariableCandidate> scope = null;
231        switch (ast.getType()) {
232            case TokenTypes.OBJBLOCK:
233            case TokenTypes.CTOR_DEF:
234            case TokenTypes.METHOD_DEF:
235                scope = scopeStack.pop().scope;
236                break;
237            case TokenTypes.SLIST:
238                final Deque<DetailAST> prevScopeUnitializedVariableData =
239                    prevScopeUninitializedVariables.peek();
240                if (ast.getParent().getType() != TokenTypes.CASE_GROUP
241                    || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(),
242                            TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) {
243                    scope = scopeStack.pop().scope;
244                    prevScopeUninitializedVariables.pop();
245                }
246                final DetailAST parent = ast.getParent();
247                if (shouldUpdateUninitializedVariables(parent)) {
248                    updateUninitializedVariables(prevScopeUnitializedVariableData);
249                }
250                break;
251            default:
252                // do nothing
253        }
254        if (scope != null) {
255            for (FinalVariableCandidate candidate : scope.values()) {
256                final DetailAST ident = candidate.variableIdent;
257                log(ident.getLineNo(), ident.getColumnNo(), MSG_KEY, ident.getText());
258            }
259        }
260    }
261
262    /**
263     * Determines identifier assignment conditions (assigned or already assigned).
264     * @param ident identifier.
265     * @param candidate final local variable candidate.
266     */
267    private static void determineAssignmentConditions(DetailAST ident,
268                                                      FinalVariableCandidate candidate) {
269        if (candidate.assigned) {
270            if (!isInSpecificCodeBlock(ident, TokenTypes.LITERAL_ELSE)
271                    && !isInSpecificCodeBlock(ident, TokenTypes.CASE_GROUP)) {
272                candidate.alreadyAssigned = true;
273            }
274        }
275        else {
276            candidate.assigned = true;
277        }
278    }
279
280    /**
281     * Checks whether the scope of a node is restricted to a specific code block.
282     * @param node node.
283     * @param blockType block type.
284     * @return true if the scope of a node is restricted to a specific code block.
285     */
286    private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
287        boolean returnValue = false;
288        for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
289            final int type = token.getType();
290            if (type == blockType) {
291                returnValue = true;
292                break;
293            }
294        }
295        return returnValue;
296    }
297
298    /**
299     * Gets final variable candidate for ast.
300     * @param ast ast.
301     * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack.
302     */
303    private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) {
304        Optional<FinalVariableCandidate> result = Optional.empty();
305        final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
306        while (iterator.hasNext() && !result.isPresent()) {
307            final ScopeData scopeData = iterator.next();
308            result = scopeData.findFinalVariableCandidateForAst(ast);
309        }
310        return result;
311    }
312
313    /**
314     * Store un-initialized variables in a temporary stack for future use.
315     */
316    private void storePrevScopeUninitializedVariableData() {
317        final ScopeData scopeData = scopeStack.peek();
318        final Deque<DetailAST> prevScopeUnitializedVariableData =
319                new ArrayDeque<>();
320        scopeData.uninitializedVariables.forEach(prevScopeUnitializedVariableData::push);
321        prevScopeUninitializedVariables.push(prevScopeUnitializedVariableData);
322    }
323
324    /**
325     * Update current scope data uninitialized variable according to the previous scope data.
326     * @param prevScopeUnitializedVariableData variable for previous stack of uninitialized
327     *     variables
328     */
329    // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation.
330    private void updateUninitializedVariables(Deque<DetailAST> prevScopeUnitializedVariableData) {
331        // Check for only previous scope
332        for (DetailAST variable : prevScopeUnitializedVariableData) {
333            for (ScopeData scopeData : scopeStack) {
334                final FinalVariableCandidate candidate = scopeData.scope.get(variable.getText());
335                DetailAST storedVariable = null;
336                if (candidate != null) {
337                    storedVariable = candidate.variableIdent;
338                }
339                if (storedVariable != null && isSameVariables(storedVariable, variable)
340                        && !scopeData.uninitializedVariables.contains(storedVariable)) {
341                    scopeData.uninitializedVariables.push(variable);
342                }
343            }
344        }
345        // Check for rest of the scope
346        for (Deque<DetailAST> unitializedVariableData : prevScopeUninitializedVariables) {
347            for (DetailAST variable : unitializedVariableData) {
348                for (ScopeData scopeData : scopeStack) {
349                    final FinalVariableCandidate candidate =
350                        scopeData.scope.get(variable.getText());
351                    DetailAST storedVariable = null;
352                    if (candidate != null) {
353                        storedVariable = candidate.variableIdent;
354                    }
355                    if (storedVariable != null
356                            && isSameVariables(storedVariable, variable)
357                            && !scopeData.uninitializedVariables.contains(storedVariable)) {
358                        scopeData.uninitializedVariables.push(variable);
359                    }
360                }
361            }
362        }
363    }
364
365    /**
366     * If token is LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, or LITERAL_ELSE, then do not
367     * update the uninitialized variables.
368     * @param ast token to be checked
369     * @return true if should be updated, else false
370     */
371    private static boolean shouldUpdateUninitializedVariables(DetailAST ast) {
372        return ast.getType() != TokenTypes.LITERAL_TRY
373                && ast.getType() != TokenTypes.LITERAL_CATCH
374                && ast.getType() != TokenTypes.LITERAL_FINALLY
375                && ast.getType() != TokenTypes.LITERAL_ELSE;
376    }
377
378    /**
379     * Returns the last child token that makes a specified type and contains containType in
380     * its branch.
381     * @param ast token to be tested
382     * @param childType the token type to match
383     * @param containType the token type which has to be present in the branch
384     * @return the matching token, or null if no match
385     */
386    public DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType,
387                                                              int containType) {
388        DetailAST returnValue = null;
389        for (DetailAST astIterator = ast.getFirstChild(); astIterator != null;
390                astIterator = astIterator.getNextSibling()) {
391            if (astIterator.getType() == childType && astIterator.branchContains(containType)) {
392                returnValue = astIterator;
393            }
394        }
395        return returnValue;
396    }
397
398    /**
399     * Determines whether enhanced for-loop variable should be checked or not.
400     * @param ast The ast to compare.
401     * @return true if enhanced for-loop variable should be checked.
402     */
403    private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
404        return validateEnhancedForLoopVariable
405                || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
406    }
407
408    /**
409     * Insert a parameter at the topmost scope stack.
410     * @param ast the variable to insert.
411     */
412    private void insertParameter(DetailAST ast) {
413        final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
414        final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
415        scope.put(astNode.getText(), new FinalVariableCandidate(astNode));
416    }
417
418    /**
419     * Insert a variable at the topmost scope stack.
420     * @param ast the variable to insert.
421     */
422    private void insertVariable(DetailAST ast) {
423        final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
424        final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
425        scope.put(astNode.getText(), new FinalVariableCandidate(astNode));
426        if (!isInitialized(astNode)) {
427            scopeStack.peek().uninitializedVariables.add(astNode);
428        }
429    }
430
431    /**
432     * Check if VARIABLE_DEF is initialized or not.
433     * @param ast VARIABLE_DEF to be checked
434     * @return true if initialized
435     */
436    private static boolean isInitialized(DetailAST ast) {
437        return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN;
438    }
439
440    /**
441     * Whether the ast is the first child of its parent.
442     * @param ast the ast to check.
443     * @return true if the ast is the first child of its parent.
444     */
445    private static boolean isFirstChild(DetailAST ast) {
446        return ast.getPreviousSibling() == null;
447    }
448
449    /**
450     * Removes the final variable candidate from the Stack.
451     * @param ast variable to remove.
452     */
453    private void removeFinalVariableCandidateFromStack(DetailAST ast) {
454        final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
455        while (iterator.hasNext()) {
456            final ScopeData scopeData = iterator.next();
457            final Map<String, FinalVariableCandidate> scope = scopeData.scope;
458            final FinalVariableCandidate candidate = scope.get(ast.getText());
459            DetailAST storedVariable = null;
460            if (candidate != null) {
461                storedVariable = candidate.variableIdent;
462            }
463            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
464                if (shouldRemoveFinalVariableCandidate(scopeData, ast)) {
465                    scope.remove(ast.getText());
466                }
467                break;
468            }
469        }
470    }
471
472    /**
473     * Check if given parameter definition is a multiple type catch.
474     * @param parameterDefAst parameter definition
475     * @return true if it is a multiple type catch, false otherwise
476     */
477    private boolean isMultipleTypeCatch(DetailAST parameterDefAst) {
478        final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE);
479        return typeAst.getFirstChild().getType() == TokenTypes.BOR;
480    }
481
482    /**
483     * Whether the final variable candidate should be removed from the list of final local variable
484     * candidates.
485     * @param scopeData the scope data of the variable.
486     * @param ast the variable ast.
487     * @return true, if the variable should be removed.
488     */
489    private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) {
490        boolean shouldRemove = true;
491        for (DetailAST variable : scopeData.uninitializedVariables) {
492            if (variable.getText().equals(ast.getText())) {
493                // if the variable is declared outside the loop and initialized inside
494                // the loop, then it cannot be declared final, as it can be initialized
495                // more than once in this case
496                if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) {
497                    final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText());
498                    shouldRemove = candidate.alreadyAssigned;
499                }
500                scopeData.uninitializedVariables.remove(variable);
501                break;
502            }
503        }
504        return shouldRemove;
505    }
506
507    /**
508     * Checks whether a variable which is declared outside loop is used inside loop.
509     * For example:
510     * <p>
511     * {@code
512     * int x;
513     * for (int i = 0, j = 0; i < j; i++) {
514     *     x = 5;
515     * }
516     * }
517     * </p>
518     * @param variable variable.
519     * @return true if a variable which is declared ouside loop is used inside loop.
520     */
521    private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) {
522        DetailAST loop2 = variable.getParent();
523        while (loop2 != null
524            && !isLoopAst(loop2.getType())) {
525            loop2 = loop2.getParent();
526        }
527        return loop2 != null;
528    }
529
530    /**
531     * Is Arithmetic operator.
532     * @param parentType token AST
533     * @return true is token type is in arithmetic operator
534     */
535    private static boolean isAssignOperator(int parentType) {
536        return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0;
537    }
538
539    /**
540     * Checks if current variable is defined in
541     *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
542     * <p>
543     * {@code
544     * for (int i = 0, j = 0; i < j; i++) { . . . }
545     * }
546     * </p>
547     * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init}
548     * @param variableDef variable definition node.
549     * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
550     */
551    private static boolean isVariableInForInit(DetailAST variableDef) {
552        return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
553    }
554
555    /**
556     * Determines whether an AST is a descendant of an abstract or native method.
557     * @param ast the AST to check.
558     * @return true if ast is a descendant of an abstract or native method.
559     */
560    private static boolean isInAbstractOrNativeMethod(DetailAST ast) {
561        boolean abstractOrNative = false;
562        DetailAST parent = ast.getParent();
563        while (parent != null && !abstractOrNative) {
564            if (parent.getType() == TokenTypes.METHOD_DEF) {
565                final DetailAST modifiers =
566                    parent.findFirstToken(TokenTypes.MODIFIERS);
567                abstractOrNative = modifiers.branchContains(TokenTypes.ABSTRACT)
568                        || modifiers.branchContains(TokenTypes.LITERAL_NATIVE);
569            }
570            parent = parent.getParent();
571        }
572        return abstractOrNative;
573    }
574
575    /**
576     * Check if current param is lambda's param.
577     * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}.
578     * @return true if current param is lambda's param.
579     */
580    private static boolean isInLambda(DetailAST paramDef) {
581        return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
582    }
583
584    /**
585     * Find the Class, Constructor, Enum, Method, or Field in which it is defined.
586     * @param ast Variable for which we want to find the scope in which it is defined
587     * @return ast The Class or Constructor or Method in which it is defined.
588     */
589    private static DetailAST findFirstUpperNamedBlock(DetailAST ast) {
590        DetailAST astTraverse = ast;
591        while (astTraverse.getType() != TokenTypes.METHOD_DEF
592                && astTraverse.getType() != TokenTypes.CLASS_DEF
593                && astTraverse.getType() != TokenTypes.ENUM_DEF
594                && astTraverse.getType() != TokenTypes.CTOR_DEF
595                && !ScopeUtils.isClassFieldDef(astTraverse)) {
596            astTraverse = astTraverse.getParent();
597        }
598        return astTraverse;
599    }
600
601    /**
602     * Check if both the Variables are same.
603     * @param ast1 Variable to compare
604     * @param ast2 Variable to compare
605     * @return true if both the variables are same, otherwise false
606     */
607    private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
608        final DetailAST classOrMethodOfAst1 =
609            findFirstUpperNamedBlock(ast1);
610        final DetailAST classOrMethodOfAst2 =
611            findFirstUpperNamedBlock(ast2);
612        return classOrMethodOfAst1 == classOrMethodOfAst2;
613    }
614
615    /**
616     * Check if both the variables are in the same loop.
617     * @param ast1 variable to compare.
618     * @param ast2 variable to compare.
619     * @return true if both the variables are in the same loop.
620     */
621    private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) {
622        DetailAST loop1 = ast1.getParent();
623        while (loop1 != null && !isLoopAst(loop1.getType())) {
624            loop1 = loop1.getParent();
625        }
626        DetailAST loop2 = ast2.getParent();
627        while (loop2 != null && !isLoopAst(loop2.getType())) {
628            loop2 = loop2.getParent();
629        }
630        return loop1 != null && loop1 == loop2;
631    }
632
633    /**
634     * Checks whether the ast is a loop.
635     * @param ast the ast to check.
636     * @return true if the ast is a loop.
637     */
638    private static boolean isLoopAst(int ast) {
639        return Arrays.binarySearch(LOOP_TYPES, ast) >= 0;
640    }
641
642    /**
643     * Holder for the scope data.
644     */
645    private static class ScopeData {
646        /** Contains variable definitions. */
647        private final Map<String, FinalVariableCandidate> scope = new HashMap<>();
648
649        /** Contains definitions of uninitialized variables. */
650        private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>();
651
652        /**
653         * Searches for final local variable candidate for ast in the scope.
654         * @param ast ast.
655         * @return Optional of {@link FinalVariableCandidate}.
656         */
657        public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) {
658            Optional<FinalVariableCandidate> result = Optional.empty();
659            DetailAST storedVariable = null;
660            final Optional<FinalVariableCandidate> candidate =
661                Optional.ofNullable(scope.get(ast.getText()));
662            if (candidate.isPresent()) {
663                storedVariable = candidate.get().variableIdent;
664            }
665            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
666                result = candidate;
667            }
668            return result;
669        }
670    }
671
672    /**Represents information about final local variable candidate. */
673    private static class FinalVariableCandidate {
674        /** Identifier token. */
675        private final DetailAST variableIdent;
676        /** Whether the variable is assigned. */
677        private boolean assigned;
678        /** Whether the variable is already assigned. */
679        private boolean alreadyAssigned;
680
681        /**
682         * Creates new instance.
683         * @param variableIdent variable identifier.
684         */
685        FinalVariableCandidate(DetailAST variableIdent) {
686            this.variableIdent = variableIdent;
687        }
688    }
689}