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