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