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     * @param validateEnhancedForLoopVariable whether to check for-loop variable
196     */
197    public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
198        this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
199    }
200
201    @Override
202    public int[] getRequiredTokens() {
203        return new int[] {
204            TokenTypes.IDENT,
205            TokenTypes.CTOR_DEF,
206            TokenTypes.METHOD_DEF,
207            TokenTypes.SLIST,
208            TokenTypes.OBJBLOCK,
209            TokenTypes.LITERAL_BREAK,
210            TokenTypes.LITERAL_FOR,
211        };
212    }
213
214    @Override
215    public int[] getDefaultTokens() {
216        return new int[] {
217            TokenTypes.IDENT,
218            TokenTypes.CTOR_DEF,
219            TokenTypes.METHOD_DEF,
220            TokenTypes.SLIST,
221            TokenTypes.OBJBLOCK,
222            TokenTypes.LITERAL_BREAK,
223            TokenTypes.LITERAL_FOR,
224            TokenTypes.VARIABLE_DEF,
225        };
226    }
227
228    @Override
229    public int[] getAcceptableTokens() {
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            TokenTypes.PARAMETER_DEF,
240        };
241    }
242
243    // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block
244    // expressions to separate methods, but that will not increase readability.
245    @Override
246    public void visitToken(DetailAST ast) {
247        switch (ast.getType()) {
248            case TokenTypes.OBJBLOCK:
249            case TokenTypes.METHOD_DEF:
250            case TokenTypes.CTOR_DEF:
251            case TokenTypes.LITERAL_FOR:
252                scopeStack.push(new ScopeData());
253                break;
254            case TokenTypes.SLIST:
255                currentScopeAssignedVariables.push(new ArrayDeque<>());
256                if (ast.getParent().getType() != TokenTypes.CASE_GROUP
257                    || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP)
258                    == ast.getParent()) {
259                    storePrevScopeUninitializedVariableData();
260                    scopeStack.push(new ScopeData());
261                }
262                break;
263            case TokenTypes.PARAMETER_DEF:
264                if (!isInLambda(ast)
265                        && ast.findFirstToken(TokenTypes.MODIFIERS)
266                            .findFirstToken(TokenTypes.FINAL) == null
267                        && !isInAbstractOrNativeMethod(ast)
268                        && !ScopeUtil.isInInterfaceBlock(ast)
269                        && !isMultipleTypeCatch(ast)
270                        && !CheckUtil.isReceiverParameter(ast)) {
271                    insertParameter(ast);
272                }
273                break;
274            case TokenTypes.VARIABLE_DEF:
275                if (ast.getParent().getType() != TokenTypes.OBJBLOCK
276                        && ast.findFirstToken(TokenTypes.MODIFIERS)
277                            .findFirstToken(TokenTypes.FINAL) == null
278                        && !isVariableInForInit(ast)
279                        && shouldCheckEnhancedForLoopVariable(ast)) {
280                    insertVariable(ast);
281                }
282                break;
283            case TokenTypes.IDENT:
284                final int parentType = ast.getParent().getType();
285                if (isAssignOperator(parentType) && isFirstChild(ast)) {
286                    final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast);
287                    if (candidate.isPresent()) {
288                        determineAssignmentConditions(ast, candidate.get());
289                        currentScopeAssignedVariables.peek().add(ast);
290                    }
291                    removeFinalVariableCandidateFromStack(ast);
292                }
293                break;
294            case TokenTypes.LITERAL_BREAK:
295                scopeStack.peek().containsBreak = true;
296                break;
297            default:
298                throw new IllegalStateException("Incorrect token type");
299        }
300    }
301
302    @Override
303    public void leaveToken(DetailAST ast) {
304        Map<String, FinalVariableCandidate> scope = null;
305        switch (ast.getType()) {
306            case TokenTypes.OBJBLOCK:
307            case TokenTypes.CTOR_DEF:
308            case TokenTypes.METHOD_DEF:
309            case TokenTypes.LITERAL_FOR:
310                scope = scopeStack.pop().scope;
311                break;
312            case TokenTypes.SLIST:
313                // -@cs[MoveVariableInsideIf] assignment value is modified later so it can't be
314                // moved
315                final Deque<DetailAST> prevScopeUninitializedVariableData =
316                    prevScopeUninitializedVariables.peek();
317                boolean containsBreak = false;
318                if (ast.getParent().getType() != TokenTypes.CASE_GROUP
319                    || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(),
320                            TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) {
321                    containsBreak = scopeStack.peek().containsBreak;
322                    scope = scopeStack.pop().scope;
323                    prevScopeUninitializedVariables.pop();
324                }
325                final DetailAST parent = ast.getParent();
326                if (containsBreak || shouldUpdateUninitializedVariables(parent)) {
327                    updateAllUninitializedVariables(prevScopeUninitializedVariableData);
328                }
329                updateCurrentScopeAssignedVariables();
330                break;
331            default:
332                // do nothing
333        }
334        if (scope != null) {
335            for (FinalVariableCandidate candidate : scope.values()) {
336                final DetailAST ident = candidate.variableIdent;
337                log(ident, MSG_KEY, ident.getText());
338            }
339        }
340    }
341
342    /**
343     * Update assigned variables in a temporary stack.
344     */
345    private void updateCurrentScopeAssignedVariables() {
346        // -@cs[MoveVariableInsideIf] assignment value is a modification call so it can't be moved
347        final Deque<DetailAST> poppedScopeAssignedVariableData =
348                currentScopeAssignedVariables.pop();
349        final Deque<DetailAST> currentScopeAssignedVariableData =
350                currentScopeAssignedVariables.peek();
351        if (currentScopeAssignedVariableData != null) {
352            currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData);
353        }
354    }
355
356    /**
357     * Determines identifier assignment conditions (assigned or already assigned).
358     * @param ident identifier.
359     * @param candidate final local variable candidate.
360     */
361    private static void determineAssignmentConditions(DetailAST ident,
362                                                      FinalVariableCandidate candidate) {
363        if (candidate.assigned) {
364            if (!isInSpecificCodeBlock(ident, TokenTypes.LITERAL_ELSE)
365                    && !isInSpecificCodeBlock(ident, TokenTypes.CASE_GROUP)) {
366                candidate.alreadyAssigned = true;
367            }
368        }
369        else {
370            candidate.assigned = true;
371        }
372    }
373
374    /**
375     * Checks whether the scope of a node is restricted to a specific code block.
376     * @param node node.
377     * @param blockType block type.
378     * @return true if the scope of a node is restricted to a specific code block.
379     */
380    private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
381        boolean returnValue = false;
382        for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
383            final int type = token.getType();
384            if (type == blockType) {
385                returnValue = true;
386                break;
387            }
388        }
389        return returnValue;
390    }
391
392    /**
393     * Gets final variable candidate for ast.
394     * @param ast ast.
395     * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack.
396     */
397    private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) {
398        Optional<FinalVariableCandidate> result = Optional.empty();
399        final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
400        while (iterator.hasNext() && !result.isPresent()) {
401            final ScopeData scopeData = iterator.next();
402            result = scopeData.findFinalVariableCandidateForAst(ast);
403        }
404        return result;
405    }
406
407    /**
408     * Store un-initialized variables in a temporary stack for future use.
409     */
410    private void storePrevScopeUninitializedVariableData() {
411        final ScopeData scopeData = scopeStack.peek();
412        final Deque<DetailAST> prevScopeUninitializedVariableData =
413                new ArrayDeque<>();
414        scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push);
415        prevScopeUninitializedVariables.push(prevScopeUninitializedVariableData);
416    }
417
418    /**
419     * Update current scope data uninitialized variable according to the whole scope data.
420     * @param prevScopeUninitializedVariableData variable for previous stack of uninitialized
421     *     variables
422     * @noinspection MethodParameterNamingConvention
423     */
424    private void updateAllUninitializedVariables(
425            Deque<DetailAST> prevScopeUninitializedVariableData) {
426        // Check for only previous scope
427        updateUninitializedVariables(prevScopeUninitializedVariableData);
428        // Check for rest of the scope
429        prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables);
430    }
431
432    /**
433     * Update current scope data uninitialized variable according to the specific scope data.
434     * @param scopeUninitializedVariableData variable for specific stack of uninitialized variables
435     */
436    private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) {
437        final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator();
438        while (iterator.hasNext()) {
439            final DetailAST assignedVariable = iterator.next();
440            boolean shouldRemove = false;
441            for (DetailAST variable : scopeUninitializedVariableData) {
442                for (ScopeData scopeData : scopeStack) {
443                    final FinalVariableCandidate candidate =
444                        scopeData.scope.get(variable.getText());
445                    DetailAST storedVariable = null;
446                    if (candidate != null) {
447                        storedVariable = candidate.variableIdent;
448                    }
449                    if (storedVariable != null
450                            && isSameVariables(storedVariable, variable)
451                            && isSameVariables(assignedVariable, variable)) {
452                        scopeData.uninitializedVariables.push(variable);
453                        shouldRemove = true;
454                    }
455                }
456            }
457            if (shouldRemove) {
458                iterator.remove();
459            }
460        }
461    }
462
463    /**
464     * If token is LITERAL_IF and there is an {@code else} following or token is CASE_GROUP and
465     * there is another {@code case} following, then update the uninitialized variables.
466     * @param ast token to be checked
467     * @return true if should be updated, else false
468     */
469    private static boolean shouldUpdateUninitializedVariables(DetailAST ast) {
470        return isIfTokenWithAnElseFollowing(ast) || isCaseTokenWithAnotherCaseFollowing(ast);
471    }
472
473    /**
474     * If token is LITERAL_IF and there is an {@code else} following.
475     * @param ast token to be checked
476     * @return true if token is LITERAL_IF and there is an {@code else} following, else false
477     */
478    private static boolean isIfTokenWithAnElseFollowing(DetailAST ast) {
479        return ast.getType() == TokenTypes.LITERAL_IF
480                && ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE;
481    }
482
483    /**
484     * If token is CASE_GROUP and there is another {@code case} following.
485     * @param ast token to be checked
486     * @return true if token is CASE_GROUP and there is another {@code case} following, else false
487     */
488    private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) {
489        return ast.getType() == TokenTypes.CASE_GROUP
490                && findLastChildWhichContainsSpecifiedToken(
491                        ast.getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) != ast;
492    }
493
494    /**
495     * Returns the last child token that makes a specified type and contains containType in
496     * its branch.
497     * @param ast token to be tested
498     * @param childType the token type to match
499     * @param containType the token type which has to be present in the branch
500     * @return the matching token, or null if no match
501     */
502    private static DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType,
503                                                              int containType) {
504        DetailAST returnValue = null;
505        for (DetailAST astIterator = ast.getFirstChild(); astIterator != null;
506                astIterator = astIterator.getNextSibling()) {
507            if (astIterator.getType() == childType
508                    && astIterator.findFirstToken(containType) != null) {
509                returnValue = astIterator;
510            }
511        }
512        return returnValue;
513    }
514
515    /**
516     * Determines whether enhanced for-loop variable should be checked or not.
517     * @param ast The ast to compare.
518     * @return true if enhanced for-loop variable should be checked.
519     */
520    private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
521        return validateEnhancedForLoopVariable
522                || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
523    }
524
525    /**
526     * Insert a parameter at the topmost scope stack.
527     * @param ast the variable to insert.
528     */
529    private void insertParameter(DetailAST ast) {
530        final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
531        final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
532        scope.put(astNode.getText(), new FinalVariableCandidate(astNode));
533    }
534
535    /**
536     * Insert a variable at the topmost scope stack.
537     * @param ast the variable to insert.
538     */
539    private void insertVariable(DetailAST ast) {
540        final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
541        final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
542        final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode);
543        // for-each variables are implicitly assigned
544        candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE;
545        scope.put(astNode.getText(), candidate);
546        if (!isInitialized(astNode)) {
547            scopeStack.peek().uninitializedVariables.add(astNode);
548        }
549    }
550
551    /**
552     * Check if VARIABLE_DEF is initialized or not.
553     * @param ast VARIABLE_DEF to be checked
554     * @return true if initialized
555     */
556    private static boolean isInitialized(DetailAST ast) {
557        return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN;
558    }
559
560    /**
561     * Whether the ast is the first child of its parent.
562     * @param ast the ast to check.
563     * @return true if the ast is the first child of its parent.
564     */
565    private static boolean isFirstChild(DetailAST ast) {
566        return ast.getPreviousSibling() == null;
567    }
568
569    /**
570     * Removes the final variable candidate from the Stack.
571     * @param ast variable to remove.
572     */
573    private void removeFinalVariableCandidateFromStack(DetailAST ast) {
574        final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
575        while (iterator.hasNext()) {
576            final ScopeData scopeData = iterator.next();
577            final Map<String, FinalVariableCandidate> scope = scopeData.scope;
578            final FinalVariableCandidate candidate = scope.get(ast.getText());
579            DetailAST storedVariable = null;
580            if (candidate != null) {
581                storedVariable = candidate.variableIdent;
582            }
583            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
584                if (shouldRemoveFinalVariableCandidate(scopeData, ast)) {
585                    scope.remove(ast.getText());
586                }
587                break;
588            }
589        }
590    }
591
592    /**
593     * Check if given parameter definition is a multiple type catch.
594     * @param parameterDefAst parameter definition
595     * @return true if it is a multiple type catch, false otherwise
596     */
597    private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) {
598        final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE);
599        return typeAst.getFirstChild().getType() == TokenTypes.BOR;
600    }
601
602    /**
603     * Whether the final variable candidate should be removed from the list of final local variable
604     * candidates.
605     * @param scopeData the scope data of the variable.
606     * @param ast the variable ast.
607     * @return true, if the variable should be removed.
608     */
609    private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) {
610        boolean shouldRemove = true;
611        for (DetailAST variable : scopeData.uninitializedVariables) {
612            if (variable.getText().equals(ast.getText())) {
613                // if the variable is declared outside the loop and initialized inside
614                // the loop, then it cannot be declared final, as it can be initialized
615                // more than once in this case
616                if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) {
617                    final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText());
618                    shouldRemove = candidate.alreadyAssigned;
619                }
620                scopeData.uninitializedVariables.remove(variable);
621                break;
622            }
623        }
624        return shouldRemove;
625    }
626
627    /**
628     * Checks whether a variable which is declared outside loop is used inside loop.
629     * For example:
630     * <p>
631     * {@code
632     * int x;
633     * for (int i = 0, j = 0; i < j; i++) {
634     *     x = 5;
635     * }
636     * }
637     * </p>
638     * @param variable variable.
639     * @return true if a variable which is declared outside loop is used inside loop.
640     */
641    private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) {
642        DetailAST loop2 = variable.getParent();
643        while (loop2 != null
644            && !isLoopAst(loop2.getType())) {
645            loop2 = loop2.getParent();
646        }
647        return loop2 != null;
648    }
649
650    /**
651     * Is Arithmetic operator.
652     * @param parentType token AST
653     * @return true is token type is in arithmetic operator
654     */
655    private static boolean isAssignOperator(int parentType) {
656        return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0;
657    }
658
659    /**
660     * Checks if current variable is defined in
661     *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
662     * <p>
663     * {@code
664     * for (int i = 0, j = 0; i < j; i++) { . . . }
665     * }
666     * </p>
667     * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init}
668     * @param variableDef variable definition node.
669     * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
670     */
671    private static boolean isVariableInForInit(DetailAST variableDef) {
672        return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
673    }
674
675    /**
676     * Determines whether an AST is a descendant of an abstract or native method.
677     * @param ast the AST to check.
678     * @return true if ast is a descendant of an abstract or native method.
679     */
680    private static boolean isInAbstractOrNativeMethod(DetailAST ast) {
681        boolean abstractOrNative = false;
682        DetailAST parent = ast.getParent();
683        while (parent != null && !abstractOrNative) {
684            if (parent.getType() == TokenTypes.METHOD_DEF) {
685                final DetailAST modifiers =
686                    parent.findFirstToken(TokenTypes.MODIFIERS);
687                abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null
688                        || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null;
689            }
690            parent = parent.getParent();
691        }
692        return abstractOrNative;
693    }
694
695    /**
696     * Check if current param is lambda's param.
697     * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}.
698     * @return true if current param is lambda's param.
699     */
700    private static boolean isInLambda(DetailAST paramDef) {
701        return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
702    }
703
704    /**
705     * Find the Class, Constructor, Enum, Method, or Field in which it is defined.
706     * @param ast Variable for which we want to find the scope in which it is defined
707     * @return ast The Class or Constructor or Method in which it is defined.
708     */
709    private static DetailAST findFirstUpperNamedBlock(DetailAST ast) {
710        DetailAST astTraverse = ast;
711        while (astTraverse.getType() != TokenTypes.METHOD_DEF
712                && astTraverse.getType() != TokenTypes.CLASS_DEF
713                && astTraverse.getType() != TokenTypes.ENUM_DEF
714                && astTraverse.getType() != TokenTypes.CTOR_DEF
715                && !ScopeUtil.isClassFieldDef(astTraverse)) {
716            astTraverse = astTraverse.getParent();
717        }
718        return astTraverse;
719    }
720
721    /**
722     * Check if both the Variables are same.
723     * @param ast1 Variable to compare
724     * @param ast2 Variable to compare
725     * @return true if both the variables are same, otherwise false
726     */
727    private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
728        final DetailAST classOrMethodOfAst1 =
729            findFirstUpperNamedBlock(ast1);
730        final DetailAST classOrMethodOfAst2 =
731            findFirstUpperNamedBlock(ast2);
732        return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText());
733    }
734
735    /**
736     * Check if both the variables are in the same loop.
737     * @param ast1 variable to compare.
738     * @param ast2 variable to compare.
739     * @return true if both the variables are in the same loop.
740     */
741    private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) {
742        DetailAST loop1 = ast1.getParent();
743        while (loop1 != null && !isLoopAst(loop1.getType())) {
744            loop1 = loop1.getParent();
745        }
746        DetailAST loop2 = ast2.getParent();
747        while (loop2 != null && !isLoopAst(loop2.getType())) {
748            loop2 = loop2.getParent();
749        }
750        return loop1 != null && loop1 == loop2;
751    }
752
753    /**
754     * Checks whether the ast is a loop.
755     * @param ast the ast to check.
756     * @return true if the ast is a loop.
757     */
758    private static boolean isLoopAst(int ast) {
759        return Arrays.binarySearch(LOOP_TYPES, ast) >= 0;
760    }
761
762    /**
763     * Holder for the scope data.
764     */
765    private static class ScopeData {
766
767        /** Contains variable definitions. */
768        private final Map<String, FinalVariableCandidate> scope = new HashMap<>();
769
770        /** Contains definitions of uninitialized variables. */
771        private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>();
772
773        /** Whether there is a {@code break} in the scope. */
774        private boolean containsBreak;
775
776        /**
777         * Searches for final local variable candidate for ast in the scope.
778         * @param ast ast.
779         * @return Optional of {@link FinalVariableCandidate}.
780         */
781        public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) {
782            Optional<FinalVariableCandidate> result = Optional.empty();
783            DetailAST storedVariable = null;
784            final Optional<FinalVariableCandidate> candidate =
785                Optional.ofNullable(scope.get(ast.getText()));
786            if (candidate.isPresent()) {
787                storedVariable = candidate.get().variableIdent;
788            }
789            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
790                result = candidate;
791            }
792            return result;
793        }
794
795    }
796
797    /**Represents information about final local variable candidate. */
798    private static class FinalVariableCandidate {
799
800        /** Identifier token. */
801        private final DetailAST variableIdent;
802        /** Whether the variable is assigned. */
803        private boolean assigned;
804        /** Whether the variable is already assigned. */
805        private boolean alreadyAssigned;
806
807        /**
808         * Creates new instance.
809         * @param variableIdent variable identifier.
810         */
811        /* package */ FinalVariableCandidate(DetailAST variableIdent) {
812            this.variableIdent = variableIdent;
813        }
814
815    }
816
817}