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