001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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;
028
029import com.puppycrawl.tools.checkstyle.api.Check;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
033
034/**
035 * <p>
036 * Ensures that local variables that never get their values changed,
037 * must be declared final.
038 * </p>
039 * <p>
040 * An example of how to configure the check is:
041 * </p>
042 * <pre>
043 * &lt;module name="FinalLocalVariable"&gt;
044 *     &lt;property name="token" value="VARIABLE_DEF"/&gt;
045 * &lt;/module&gt;
046 * </pre>
047 * <p>
048 * By default, this Check skip final validation on
049 *  <a href = "http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2">
050 * Enhanced For-Loop</a>
051 * </p>
052 * <p>
053 * Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable
054 *  from Enhanced For Loop.
055 * </p>
056 * <p>
057 * An example of how to configure the check so that it also validates enhanced For Loop Variable is:
058 * </p>
059 * <pre>
060 * &lt;module name="FinalLocalVariable"&gt;
061 *     &lt;property name="token" value="VARIABLE_DEF"/&gt;
062 *     &lt;property name="validateEnhancedForLoopVariable" value="true"/&gt;
063 * &lt;/module&gt;
064 * </pre>
065 * <p>Example:</p>
066 * <p>
067 * {@code
068 * for (int number : myNumbers) { // violation
069 *    System.out.println(number);
070 * }
071 * }
072 * </p>
073 * @author k_gibbs, r_auckenthaler
074 */
075public class FinalLocalVariableCheck extends Check {
076
077    /**
078     * A key is pointing to the warning message text in "messages.properties"
079     * file.
080     */
081    public static final String MSG_KEY = "final.variable";
082
083    /**
084     * Assign operators types.
085     */
086    private static final int[] ASSIGN_OPERATOR_TYPES = {
087        TokenTypes.POST_INC,
088        TokenTypes.POST_DEC,
089        TokenTypes.ASSIGN,
090        TokenTypes.PLUS_ASSIGN,
091        TokenTypes.MINUS_ASSIGN,
092        TokenTypes.STAR_ASSIGN,
093        TokenTypes.DIV_ASSIGN,
094        TokenTypes.MOD_ASSIGN,
095        TokenTypes.SR_ASSIGN,
096        TokenTypes.BSR_ASSIGN,
097        TokenTypes.SL_ASSIGN,
098        TokenTypes.BAND_ASSIGN,
099        TokenTypes.BXOR_ASSIGN,
100        TokenTypes.BOR_ASSIGN,
101        TokenTypes.INC,
102        TokenTypes.DEC,
103    };
104
105    /** Scope Stack. */
106    private final Deque<Map<String, DetailAST>> scopeStack = new ArrayDeque<>();
107
108    /** Controls whether to check enhanced for-loop variable. */
109    private boolean validateEnhancedForLoopVariable;
110
111    static {
112        // Array sorting for binary search
113        Arrays.sort(ASSIGN_OPERATOR_TYPES);
114    }
115
116    /**
117     * Whether to check enhanced for-loop variable or not.
118     * @param validateEnhancedForLoopVariable whether to check for-loop variable
119     */
120    public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
121        this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
122    }
123
124    @Override
125    public int[] getDefaultTokens() {
126        return new int[] {
127            TokenTypes.IDENT,
128            TokenTypes.CTOR_DEF,
129            TokenTypes.METHOD_DEF,
130            TokenTypes.VARIABLE_DEF,
131            TokenTypes.INSTANCE_INIT,
132            TokenTypes.STATIC_INIT,
133            TokenTypes.LITERAL_FOR,
134            TokenTypes.SLIST,
135            TokenTypes.OBJBLOCK,
136        };
137    }
138
139    @Override
140    public int[] getAcceptableTokens() {
141        return new int[] {
142            TokenTypes.IDENT,
143            TokenTypes.CTOR_DEF,
144            TokenTypes.METHOD_DEF,
145            TokenTypes.VARIABLE_DEF,
146            TokenTypes.INSTANCE_INIT,
147            TokenTypes.STATIC_INIT,
148            TokenTypes.LITERAL_FOR,
149            TokenTypes.SLIST,
150            TokenTypes.OBJBLOCK,
151            TokenTypes.PARAMETER_DEF,
152        };
153    }
154
155    @Override
156    public int[] getRequiredTokens() {
157        return new int[] {
158            TokenTypes.IDENT,
159            TokenTypes.CTOR_DEF,
160            TokenTypes.METHOD_DEF,
161            TokenTypes.INSTANCE_INIT,
162            TokenTypes.STATIC_INIT,
163            TokenTypes.LITERAL_FOR,
164            TokenTypes.SLIST,
165            TokenTypes.OBJBLOCK,
166        };
167    }
168
169    @Override
170    public void visitToken(DetailAST ast) {
171        switch (ast.getType()) {
172            case TokenTypes.OBJBLOCK:
173            case TokenTypes.SLIST:
174            case TokenTypes.LITERAL_FOR:
175            case TokenTypes.METHOD_DEF:
176            case TokenTypes.CTOR_DEF:
177            case TokenTypes.STATIC_INIT:
178            case TokenTypes.INSTANCE_INIT:
179                scopeStack.push(new HashMap<String, DetailAST>());
180                break;
181
182            case TokenTypes.PARAMETER_DEF:
183                if (!isInLambda(ast)
184                        && !ast.branchContains(TokenTypes.FINAL)
185                        && !isInAbstractOrNativeMethod(ast)
186                        && !ScopeUtils.isInInterfaceBlock(ast)) {
187                    insertVariable(ast);
188                }
189                break;
190            case TokenTypes.VARIABLE_DEF:
191                if (ast.getParent().getType() != TokenTypes.OBJBLOCK
192                        && !isVariableInForInit(ast)
193                        && shouldCheckEnhancedForLoopVariable(ast)
194                        && !ast.branchContains(TokenTypes.FINAL)) {
195                    insertVariable(ast);
196                }
197                break;
198
199            case TokenTypes.IDENT:
200                final int parentType = ast.getParent().getType();
201                if (isAssignOperator(parentType)
202                        && ast.getParent().getFirstChild() == ast) {
203                    removeVariable(ast);
204                }
205                break;
206
207            default:
208                throw new IllegalStateException("Incorrect token type");
209        }
210    }
211
212    /**
213     * Is Arithmetic operator.
214     * @param parentType token AST
215     * @return true is token type is in arithmetic operator
216     */
217    private static boolean isAssignOperator(int parentType) {
218        return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0;
219    }
220
221    /**
222     * Determines whether enhanced for-loop variable should be checked or not.
223     * @param ast The ast to compare.
224     * @return true if enhanced for-loop variable should be checked.
225     */
226    private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
227        return validateEnhancedForLoopVariable
228                || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
229    }
230
231    /**
232     * Checks if current variable is defined in
233     *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
234     * <p>
235     * {@code
236     * for (int i = 0, j = 0; i < j; i++) { . . . }
237     * }
238     * </p>
239     * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init}
240     * @param variableDef variable definition node.
241     * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
242     */
243    private static boolean isVariableInForInit(DetailAST variableDef) {
244        return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
245    }
246
247    /**
248     * Determines whether an AST is a descendant of an abstract or native method.
249     * @param ast the AST to check.
250     * @return true if ast is a descendant of an abstract or native method.
251     */
252    private static boolean isInAbstractOrNativeMethod(DetailAST ast) {
253        boolean abstractOrNative = false;
254        DetailAST parent = ast.getParent();
255        while (parent != null && !abstractOrNative) {
256            if (parent.getType() == TokenTypes.METHOD_DEF) {
257                final DetailAST modifiers =
258                    parent.findFirstToken(TokenTypes.MODIFIERS);
259                abstractOrNative = modifiers.branchContains(TokenTypes.ABSTRACT)
260                        || modifiers.branchContains(TokenTypes.LITERAL_NATIVE);
261            }
262            parent = parent.getParent();
263        }
264        return abstractOrNative;
265    }
266
267    /**
268     * Check if current param is lambda's param.
269     * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}.
270     * @return true if current param is lambda's param.
271     */
272    private static boolean isInLambda(DetailAST paramDef) {
273        return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
274    }
275
276    /**
277     * Find the Class or Constructor or Method in which it is defined.
278     * @param ast Variable for which we want to find the scope in which it is defined
279     * @return ast The Class or Constructor or Method in which it is defined.
280     */
281    private static DetailAST findClassOrConstructorOrMethodInWhichItIsDefined(DetailAST ast) {
282        DetailAST astTraverse = ast;
283        while (!(astTraverse.getType() == TokenTypes.METHOD_DEF
284                || astTraverse.getType() == TokenTypes.CLASS_DEF
285                || astTraverse.getType() == TokenTypes.ENUM_DEF
286                || astTraverse.getType() == TokenTypes.CTOR_DEF)) {
287            astTraverse = astTraverse.getParent();
288        }
289        return astTraverse;
290    }
291
292    /**
293     * Check if both the Variable are same.
294     * @param ast1 Variable to compare
295     * @param ast2 Variable to compare
296     * @return true if both the variable are same, otherwise false
297     */
298    private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
299        final DetailAST classOrMethodOfAst1 =
300            findClassOrConstructorOrMethodInWhichItIsDefined(ast1);
301        final DetailAST classOrMethodOfAst2 =
302            findClassOrConstructorOrMethodInWhichItIsDefined(ast2);
303
304        final String identifierOfAst1 =
305            classOrMethodOfAst1.findFirstToken(TokenTypes.IDENT).getText();
306        final String identifierOfAst2 =
307            classOrMethodOfAst2.findFirstToken(TokenTypes.IDENT).getText();
308
309        return identifierOfAst1.equals(identifierOfAst2);
310    }
311
312    /**
313     * Inserts a variable at the topmost scope stack.
314     * @param ast the variable to insert
315     */
316    private void insertVariable(DetailAST ast) {
317        final Map<String, DetailAST> state = scopeStack.peek();
318        final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
319        state.put(astNode.getText(), astNode);
320    }
321
322    /**
323     * Removes the variable from the Stacks.
324     * @param ast Variable to remove
325     */
326    private void removeVariable(DetailAST ast) {
327        final Iterator<Map<String, DetailAST>> iterator = scopeStack.descendingIterator();
328        while (iterator.hasNext()) {
329            final Map<String, DetailAST> state = iterator.next();
330            final DetailAST storedVariable = state.get(ast.getText());
331            if (storedVariable != null && isSameVariables(storedVariable, ast)) {
332                state.remove(ast.getText());
333                break;
334            }
335        }
336    }
337
338    @Override
339    public void leaveToken(DetailAST ast) {
340        super.leaveToken(ast);
341
342        switch (ast.getType()) {
343            case TokenTypes.OBJBLOCK:
344            case TokenTypes.SLIST:
345            case TokenTypes.LITERAL_FOR:
346            case TokenTypes.CTOR_DEF:
347            case TokenTypes.STATIC_INIT:
348            case TokenTypes.INSTANCE_INIT:
349            case TokenTypes.METHOD_DEF:
350                final Map<String, DetailAST> state = scopeStack.pop();
351                for (DetailAST node : state.values()) {
352                    log(node.getLineNo(), node.getColumnNo(), MSG_KEY, node
353                        .getText());
354                }
355                break;
356
357            default:
358        }
359    }
360}