001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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; 021 022import java.util.Arrays; 023import java.util.Set; 024 025import antlr.collections.AST; 026 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 031 032/** 033 * <p> 034 * Checks for restricted tokens beneath other tokens. 035 * </p> 036 * <p> 037 * Examples of how to configure the check: 038 * </p> 039 * <pre> 040 * <!-- String literal equality check --> 041 * <module name="DescendantToken"> 042 * <property name="tokens" value="EQUAL,NOT_EQUAL"/> 043 * <property name="limitedTokens" value="STRING_LITERAL"/> 044 * <property name="maximumNumber" value="0"/> 045 * <property name="maximumDepth" value="1"/> 046 * </module> 047 * 048 * <!-- Switch with no default --> 049 * <module name="DescendantToken"> 050 * <property name="tokens" value="LITERAL_SWITCH"/> 051 * <property name="maximumDepth" value="2"/> 052 * <property name="limitedTokens" value="LITERAL_DEFAULT"/> 053 * <property name="minimumNumber" value="1"/> 054 * </module> 055 * 056 * <!-- Assert statement may have side effects --> 057 * <module name="DescendantToken"> 058 * <property name="tokens" value="LITERAL_ASSERT"/> 059 * <property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC, 060 * POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN, 061 * BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN, 062 * METHOD_CALL"/> 063 * <property name="maximumNumber" value="0"/> 064 * </module> 065 * 066 * <!-- Initializer in for performs no setup - use while instead? --> 067 * <module name="DescendantToken"> 068 * <property name="tokens" value="FOR_INIT"/> 069 * <property name="limitedTokens" value="EXPR"/> 070 * <property name="minimumNumber" value="1"/> 071 * </module> 072 * 073 * <!-- Condition in for performs no check --> 074 * <module name="DescendantToken"> 075 * <property name="tokens" value="FOR_CONDITION"/> 076 * <property name="limitedTokens" value="EXPR"/> 077 * <property name="minimumNumber" value="1"/> 078 * </module> 079 * 080 * <!-- Switch within switch --> 081 * <module name="DescendantToken"> 082 * <property name="tokens" value="LITERAL_SWITCH"/> 083 * <property name="limitedTokens" value="LITERAL_SWITCH"/> 084 * <property name="maximumNumber" value="0"/> 085 * <property name="minimumDepth" value="1"/> 086 * </module> 087 * 088 * <!-- Return from within a catch or finally block --> 089 * <module name="DescendantToken"> 090 * <property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/> 091 * <property name="limitedTokens" value="LITERAL_RETURN"/> 092 * <property name="maximumNumber" value="0"/> 093 * </module> 094 * 095 * <!-- Try within catch or finally block --> 096 * <module name="DescendantToken"> 097 * <property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/> 098 * <property name="limitedTokens" value="LITERAL_TRY"/> 099 * <property name="maximumNumber" value="0"/> 100 * </module> 101 * 102 * <!-- Too many cases within a switch --> 103 * <module name="DescendantToken"> 104 * <property name="tokens" value="LITERAL_SWITCH"/> 105 * <property name="limitedTokens" value="LITERAL_CASE"/> 106 * <property name="maximumDepth" value="2"/> 107 * <property name="maximumNumber" value="10"/> 108 * </module> 109 * 110 * <!-- Too many local variables within a method --> 111 * <module name="DescendantToken"> 112 * <property name="tokens" value="METHOD_DEF"/> 113 * <property name="limitedTokens" value="VARIABLE_DEF"/> 114 * <property name="maximumDepth" value="2"/> 115 * <property name="maximumNumber" value="10"/> 116 * </module> 117 * 118 * <!-- Too many returns from within a method --> 119 * <module name="DescendantToken"> 120 * <property name="tokens" value="METHOD_DEF"/> 121 * <property name="limitedTokens" value="LITERAL_RETURN"/> 122 * <property name="maximumNumber" value="3"/> 123 * </module> 124 * 125 * <!-- Too many fields within an interface --> 126 * <module name="DescendantToken"> 127 * <property name="tokens" value="INTERFACE_DEF"/> 128 * <property name="limitedTokens" value="VARIABLE_DEF"/> 129 * <property name="maximumDepth" value="2"/> 130 * <property name="maximumNumber" value="0"/> 131 * </module> 132 * 133 * <!-- Limit the number of exceptions a method can throw --> 134 * <module name="DescendantToken"> 135 * <property name="tokens" value="LITERAL_THROWS"/> 136 * <property name="limitedTokens" value="IDENT"/> 137 * <property name="maximumNumber" value="1"/> 138 * </module> 139 * 140 * <!-- Limit the number of expressions in a method --> 141 * <module name="DescendantToken"> 142 * <property name="tokens" value="METHOD_DEF"/> 143 * <property name="limitedTokens" value="EXPR"/> 144 * <property name="maximumNumber" value="200"/> 145 * </module> 146 * 147 * <!-- Disallow empty statements --> 148 * <module name="DescendantToken"> 149 * <property name="tokens" value="EMPTY_STAT"/> 150 * <property name="limitedTokens" value="EMPTY_STAT"/> 151 * <property name="maximumNumber" value="0"/> 152 * <property name="maximumDepth" value="0"/> 153 * <property name="maximumMessage" 154 * value="Empty statement is not allowed."/> 155 * </module> 156 * 157 * <!-- Too many fields within a class --> 158 * <module name="DescendantToken"> 159 * <property name="tokens" value="CLASS_DEF"/> 160 * <property name="limitedTokens" value="VARIABLE_DEF"/> 161 * <property name="maximumDepth" value="2"/> 162 * <property name="maximumNumber" value="10"/> 163 * </module> 164 * </pre> 165 * 166 * @author Tim Tyler <tim@tt1.org> 167 * @author Rick Giles 168 */ 169public class DescendantTokenCheck extends AbstractCheck { 170 171 /** 172 * A key is pointing to the warning message text in "messages.properties" 173 * file. 174 */ 175 public static final String MSG_KEY_MIN = "descendant.token.min"; 176 177 /** 178 * A key is pointing to the warning message text in "messages.properties" 179 * file. 180 */ 181 public static final String MSG_KEY_MAX = "descendant.token.max"; 182 183 /** 184 * A key is pointing to the warning message text in "messages.properties" 185 * file. 186 */ 187 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 188 189 /** 190 * A key is pointing to the warning message text in "messages.properties" 191 * file. 192 */ 193 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 194 195 /** Minimum depth. */ 196 private int minimumDepth; 197 /** Maximum depth. */ 198 private int maximumDepth = Integer.MAX_VALUE; 199 /** Minimum number. */ 200 private int minimumNumber; 201 /** Maximum number. */ 202 private int maximumNumber = Integer.MAX_VALUE; 203 /** Whether to sum the number of tokens found. */ 204 private boolean sumTokenCounts; 205 /** Limited tokens. */ 206 private int[] limitedTokens = CommonUtils.EMPTY_INT_ARRAY; 207 /** Error message when minimum count not reached. */ 208 private String minimumMessage; 209 /** Error message when maximum count exceeded. */ 210 private String maximumMessage; 211 212 /** 213 * Counts of descendant tokens. 214 * Indexed by (token ID - 1) for performance. 215 */ 216 private int[] counts = CommonUtils.EMPTY_INT_ARRAY; 217 218 @Override 219 public int[] getDefaultTokens() { 220 return CommonUtils.EMPTY_INT_ARRAY; 221 } 222 223 @Override 224 public int[] getRequiredTokens() { 225 return CommonUtils.EMPTY_INT_ARRAY; 226 } 227 228 @Override 229 public void visitToken(DetailAST ast) { 230 //reset counts 231 Arrays.fill(counts, 0); 232 countTokens(ast, 0); 233 234 if (sumTokenCounts) { 235 logAsTotal(ast); 236 } 237 else { 238 logAsSeparated(ast); 239 } 240 } 241 242 /** 243 * Log violations for each Token. 244 * @param ast token 245 */ 246 private void logAsSeparated(DetailAST ast) { 247 // name of this token 248 final String name = TokenUtils.getTokenName(ast.getType()); 249 250 for (int element : limitedTokens) { 251 final int tokenCount = counts[element - 1]; 252 if (tokenCount < minimumNumber) { 253 final String descendantName = TokenUtils.getTokenName(element); 254 255 if (minimumMessage == null) { 256 minimumMessage = MSG_KEY_MIN; 257 } 258 log(ast.getLineNo(), ast.getColumnNo(), 259 minimumMessage, 260 String.valueOf(tokenCount), 261 String.valueOf(minimumNumber), 262 name, 263 descendantName); 264 } 265 if (tokenCount > maximumNumber) { 266 final String descendantName = TokenUtils.getTokenName(element); 267 268 if (maximumMessage == null) { 269 maximumMessage = MSG_KEY_MAX; 270 } 271 log(ast.getLineNo(), ast.getColumnNo(), 272 maximumMessage, 273 String.valueOf(tokenCount), 274 String.valueOf(maximumNumber), 275 name, 276 descendantName); 277 } 278 } 279 } 280 281 /** 282 * Log validation as one violation. 283 * @param ast current token 284 */ 285 private void logAsTotal(DetailAST ast) { 286 // name of this token 287 final String name = TokenUtils.getTokenName(ast.getType()); 288 289 int total = 0; 290 for (int element : limitedTokens) { 291 total += counts[element - 1]; 292 } 293 if (total < minimumNumber) { 294 if (minimumMessage == null) { 295 minimumMessage = MSG_KEY_SUM_MIN; 296 } 297 log(ast.getLineNo(), ast.getColumnNo(), 298 minimumMessage, 299 String.valueOf(total), 300 String.valueOf(minimumNumber), name); 301 } 302 if (total > maximumNumber) { 303 if (maximumMessage == null) { 304 maximumMessage = MSG_KEY_SUM_MAX; 305 } 306 log(ast.getLineNo(), ast.getColumnNo(), 307 maximumMessage, 308 String.valueOf(total), 309 String.valueOf(maximumNumber), name); 310 } 311 } 312 313 /** 314 * Counts the number of occurrences of descendant tokens. 315 * @param ast the root token for descendants. 316 * @param depth the maximum depth of the counted descendants. 317 */ 318 private void countTokens(AST ast, int depth) { 319 if (depth <= maximumDepth) { 320 //update count 321 if (depth >= minimumDepth) { 322 final int type = ast.getType(); 323 if (type <= counts.length) { 324 counts[type - 1]++; 325 } 326 } 327 AST child = ast.getFirstChild(); 328 final int nextDepth = depth + 1; 329 while (child != null) { 330 countTokens(child, nextDepth); 331 child = child.getNextSibling(); 332 } 333 } 334 } 335 336 @Override 337 public int[] getAcceptableTokens() { 338 // Any tokens set by property 'tokens' are acceptable 339 final Set<String> tokenNames = getTokenNames(); 340 final int[] result = new int[tokenNames.size()]; 341 int index = 0; 342 for (String name : tokenNames) { 343 result[index] = TokenUtils.getTokenId(name); 344 index++; 345 } 346 return result; 347 } 348 349 /** 350 * Sets the tokens which occurrence as descendant is limited. 351 * @param limitedTokensParam - list of tokens to ignore. 352 */ 353 public void setLimitedTokens(String... limitedTokensParam) { 354 limitedTokens = new int[limitedTokensParam.length]; 355 356 int maxToken = 0; 357 for (int i = 0; i < limitedTokensParam.length; i++) { 358 limitedTokens[i] = TokenUtils.getTokenId(limitedTokensParam[i]); 359 if (limitedTokens[i] > maxToken) { 360 maxToken = limitedTokens[i]; 361 } 362 } 363 counts = new int[maxToken]; 364 } 365 366 /** 367 * Sets the minimum depth for descendant counts. 368 * @param minimumDepth the minimum depth for descendant counts. 369 */ 370 public void setMinimumDepth(int minimumDepth) { 371 this.minimumDepth = minimumDepth; 372 } 373 374 /** 375 * Sets the maximum depth for descendant counts. 376 * @param maximumDepth the maximum depth for descendant counts. 377 */ 378 public void setMaximumDepth(int maximumDepth) { 379 this.maximumDepth = maximumDepth; 380 } 381 382 /** 383 * Sets a minimum count for descendants. 384 * @param minimumNumber the minimum count for descendants. 385 */ 386 public void setMinimumNumber(int minimumNumber) { 387 this.minimumNumber = minimumNumber; 388 } 389 390 /** 391 * Sets a maximum count for descendants. 392 * @param maximumNumber the maximum count for descendants. 393 */ 394 public void setMaximumNumber(int maximumNumber) { 395 this.maximumNumber = maximumNumber; 396 } 397 398 /** 399 * Sets the error message for minimum count not reached. 400 * @param message the error message for minimum count not reached. 401 * Used as a {@code MessageFormat} pattern with arguments 402 * <ul> 403 * <li>{0} - token count</li> 404 * <li>{1} - minimum number</li> 405 * <li>{2} - name of token</li> 406 * <li>{3} - name of limited token</li> 407 * </ul> 408 */ 409 public void setMinimumMessage(String message) { 410 minimumMessage = message; 411 } 412 413 /** 414 * Sets the error message for maximum count exceeded. 415 * @param message the error message for maximum count exceeded. 416 * Used as a {@code MessageFormat} pattern with arguments 417 * <ul> 418 * <li>{0} - token count</li> 419 * <li>{1} - maximum number</li> 420 * <li>{2} - name of token</li> 421 * <li>{3} - name of limited token</li> 422 * </ul> 423 */ 424 425 public void setMaximumMessage(String message) { 426 maximumMessage = message; 427 } 428 429 /** 430 * Sets whether to use the sum of the tokens found, rather than the 431 * individual counts. 432 * @param sum whether to use the sum. 433 */ 434 public void setSumTokenCounts(boolean sum) { 435 sumTokenCounts = sum; 436 } 437}