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