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 * <module name="FinalLocalVariable"> 044 * <property name="token" value="VARIABLE_DEF"/> 045 * </module> 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 * <module name="FinalLocalVariable"> 061 * <property name="token" value="VARIABLE_DEF"/> 062 * <property name="validateEnhancedForLoopVariable" value="true"/> 063 * </module> 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}