1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2015 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19 package com.puppycrawl.tools.checkstyle.checks.coding;
20
21 import com.puppycrawl.tools.checkstyle.api.Check;
22 import com.puppycrawl.tools.checkstyle.api.DetailAST;
23 import com.puppycrawl.tools.checkstyle.api.FastStack;
24 import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 /**
30 * <p>
31 * Ensures that local variables that never get their values changed,
32 * must be declared final.
33 * </p>
34 * <p>
35 * An example of how to configure the check is:
36 * </p>
37 * <pre>
38 * <module name="FinalLocalVariable">
39 * <property name="token" value="VARIABLE_DEF"/>
40 * </module>
41 * </pre>
42 * <p>
43 * By default, this Check skip final validation on
44 * <a href = "http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2">
45 * Enhanced For-Loop</a>
46 * </p>
47 * <p>
48 * Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable
49 * from Enhanced For Loop.
50 * </p>
51 * <p>
52 * An example of how to configure the check so that it also validates enhanced For Loop Variable is:
53 * </p>
54 * <pre>
55 * <module name="FinalLocalVariable">
56 * <property name="token" value="VARIABLE_DEF"/>
57 * <property name="validateEnhancedForLoopVariable" value="true"/>
58 * </module>
59 * </pre>
60 * <p>Example:</p>
61 * <p>
62 * <code>
63 * for (int number : myNumbers) { // violation
64 * System.out.println(number);
65 * }
66 * </code>
67 * </p>
68 * @author k_gibbs, r_auckenthaler
69 */
70 public class FinalLocalVariableCheck extends Check
71 {
72
73 /**
74 * A key is pointing to the warning message text in "messages.properties"
75 * file.
76 */
77 public static final String MSG_KEY = "final.variable";
78
79 /** Scope Stack */
80 private final FastStack<Map<String, DetailAST>> scopeStack =
81 FastStack.newInstance();
82
83 /** Controls whether to check enhanced for-loop variable. */
84 private boolean validateEnhancedForLoopVariable;
85
86 /**
87 * Whether to check enhanced for-loop variable or not.
88 * @param validateEnhancedForLoopVariable whether to check for-loop variable
89 */
90 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable)
91 {
92 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
93 }
94
95 @Override
96 public int[] getDefaultTokens()
97 {
98 return new int[] {
99 TokenTypes.IDENT,
100 TokenTypes.CTOR_DEF,
101 TokenTypes.METHOD_DEF,
102 TokenTypes.VARIABLE_DEF,
103 TokenTypes.INSTANCE_INIT,
104 TokenTypes.STATIC_INIT,
105 TokenTypes.LITERAL_FOR,
106 TokenTypes.SLIST,
107 TokenTypes.OBJBLOCK,
108 };
109 }
110
111 @Override
112 public int[] getAcceptableTokens()
113 {
114 return new int[] {
115 TokenTypes.IDENT,
116 TokenTypes.CTOR_DEF,
117 TokenTypes.METHOD_DEF,
118 TokenTypes.VARIABLE_DEF,
119 TokenTypes.INSTANCE_INIT,
120 TokenTypes.STATIC_INIT,
121 TokenTypes.LITERAL_FOR,
122 TokenTypes.SLIST,
123 TokenTypes.OBJBLOCK,
124 TokenTypes.PARAMETER_DEF,
125 };
126 }
127
128 @Override
129 public int[] getRequiredTokens()
130 {
131 return new int[] {
132 TokenTypes.IDENT,
133 TokenTypes.CTOR_DEF,
134 TokenTypes.METHOD_DEF,
135 TokenTypes.INSTANCE_INIT,
136 TokenTypes.STATIC_INIT,
137 TokenTypes.LITERAL_FOR,
138 TokenTypes.SLIST,
139 TokenTypes.OBJBLOCK,
140 };
141 }
142
143 @Override
144 public void visitToken(DetailAST ast)
145 {
146 switch (ast.getType()) {
147 case TokenTypes.OBJBLOCK:
148 case TokenTypes.SLIST:
149 case TokenTypes.LITERAL_FOR:
150 case TokenTypes.METHOD_DEF:
151 case TokenTypes.CTOR_DEF:
152 case TokenTypes.STATIC_INIT:
153 case TokenTypes.INSTANCE_INIT:
154 scopeStack.push(new HashMap<String, DetailAST>());
155 break;
156
157 case TokenTypes.PARAMETER_DEF:
158 if (ScopeUtils.inInterfaceBlock(ast)
159 || inAbstractOrNativeMethod(ast))
160 {
161 break;
162 }
163 case TokenTypes.VARIABLE_DEF:
164 if (ast.getParent().getType() != TokenTypes.OBJBLOCK
165 && shouldCheckEnhancedForLoopVariable(ast)
166 && isVariableInForInit(ast))
167 {
168 insertVariable(ast);
169 }
170 break;
171
172 case TokenTypes.IDENT:
173 final int parentType = ast.getParent().getType();
174 if (TokenTypes.POST_DEC == parentType
175 || TokenTypes.DEC == parentType
176 || TokenTypes.POST_INC == parentType
177 || TokenTypes.INC == parentType
178 || TokenTypes.ASSIGN == parentType
179 || TokenTypes.PLUS_ASSIGN == parentType
180 || TokenTypes.MINUS_ASSIGN == parentType
181 || TokenTypes.DIV_ASSIGN == parentType
182 || TokenTypes.STAR_ASSIGN == parentType
183 || TokenTypes.MOD_ASSIGN == parentType
184 || TokenTypes.SR_ASSIGN == parentType
185 || TokenTypes.BSR_ASSIGN == parentType
186 || TokenTypes.SL_ASSIGN == parentType
187 || TokenTypes.BXOR_ASSIGN == parentType
188 || TokenTypes.BOR_ASSIGN == parentType
189 || TokenTypes.BAND_ASSIGN == parentType)
190 {
191 // TODO: is there better way to check is ast
192 // in left part of assignment?
193 if (ast.getParent().getFirstChild() == ast) {
194 removeVariable(ast);
195 }
196 }
197 break;
198
199 default:
200 }
201 }
202
203 /**
204 * Determines whether enhanced for-loop variable should be checked or not.
205 * @param ast The ast to compare.
206 * @return true if enhanced for-loop variable should be checked.
207 */
208 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast)
209 {
210 return validateEnhancedForLoopVariable
211 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
212 }
213
214 /**
215 * Checks if current variable is defined in
216 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
217 * <p>
218 * <code>
219 * for (int i = 0, j = 0; i < j; i++) { . . . }
220 * </code>
221 * </p>
222 * <code>i, j</code> are defined in {@link TokenTypes#FOR_INIT for-loop init}
223 * @param variableDef variable definition node.
224 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
225 */
226 private static boolean isVariableInForInit(DetailAST variableDef)
227 {
228 return variableDef.getParent().getType() != TokenTypes.FOR_INIT;
229 }
230
231 /**
232 * Determines whether an AST is a descendant of an abstract or native method.
233 * @param ast the AST to check.
234 * @return true if ast is a descendant of an abstract or native method.
235 */
236 private static boolean inAbstractOrNativeMethod(DetailAST ast)
237 {
238 DetailAST parent = ast.getParent();
239 while (parent != null) {
240 if (parent.getType() == TokenTypes.METHOD_DEF) {
241 final DetailAST modifiers =
242 parent.findFirstToken(TokenTypes.MODIFIERS);
243 return modifiers.branchContains(TokenTypes.ABSTRACT)
244 || modifiers.branchContains(TokenTypes.LITERAL_NATIVE);
245 }
246 parent = parent.getParent();
247 }
248 return false;
249 }
250
251 /**
252 * Inserts a variable at the topmost scope stack
253 * @param ast the variable to insert
254 */
255 private void insertVariable(DetailAST ast)
256 {
257 if (!ast.branchContains(TokenTypes.FINAL)) {
258 final Map<String, DetailAST> state = scopeStack.peek();
259 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
260 state.put(astNode.getText(), astNode);
261 }
262 }
263
264 /**
265 * Removes the variable from the Stacks
266 * @param ast Variable to remove
267 */
268 private void removeVariable(DetailAST ast)
269 {
270 for (int i = scopeStack.size() - 1; i >= 0; i--) {
271 final Map<String, DetailAST> state = scopeStack.peek(i);
272 final Object obj = state.remove(ast.getText());
273 if (obj != null) {
274 break;
275 }
276 }
277 }
278
279 @Override
280 public void leaveToken(DetailAST ast)
281 {
282 super.leaveToken(ast);
283
284 switch (ast.getType()) {
285 case TokenTypes.OBJBLOCK:
286 case TokenTypes.SLIST:
287 case TokenTypes.LITERAL_FOR:
288 case TokenTypes.CTOR_DEF:
289 case TokenTypes.STATIC_INIT:
290 case TokenTypes.INSTANCE_INIT:
291 case TokenTypes.METHOD_DEF:
292 final Map<String, DetailAST> state = scopeStack.pop();
293 for (DetailAST var : state.values()) {
294 log(var.getLineNo(), var.getColumnNo(), MSG_KEY, var
295 .getText());
296 }
297 break;
298
299 default:
300 }
301 }
302 }