1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 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 20 package com.puppycrawl.tools.checkstyle.checks; 21 22 import java.util.Arrays; 23 24 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 25 import com.puppycrawl.tools.checkstyle.PropertyType; 26 import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 28 import com.puppycrawl.tools.checkstyle.api.DetailAST; 29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 30 import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 31 32 /** 33 * <div> 34 * Checks for restricted tokens beneath other tokens. 35 * </div> 36 * 37 * <p> 38 * WARNING: This is a very powerful and flexible check, but, at the same time, 39 * it is low-level and very implementation-dependent because its results depend 40 * on the grammar we use to build abstract syntax trees. Thus, we recommend using 41 * other checks when they provide the desired functionality. Essentially, this 42 * check just works on the level of an abstract syntax tree and knows nothing 43 * about language structures. 44 * </p> 45 * <ul> 46 * <li> 47 * Property {@code limitedTokens} - Specify set of tokens with limited occurrences as descendants. 48 * Type is {@code java.lang.String[]}. 49 * Validation type is {@code tokenTypesSet}. 50 * Default value is {@code ""}. 51 * </li> 52 * <li> 53 * Property {@code maximumDepth} - Specify the maximum depth for descendant counts. 54 * Type is {@code int}. 55 * Default value is {@code 2147483647}. 56 * </li> 57 * <li> 58 * Property {@code maximumMessage} - Define the violation message 59 * when the maximum count is exceeded. 60 * Type is {@code java.lang.String}. 61 * Default value is {@code null}. 62 * </li> 63 * <li> 64 * Property {@code maximumNumber} - Specify a maximum count for descendants. 65 * Type is {@code int}. 66 * Default value is {@code 2147483647}. 67 * </li> 68 * <li> 69 * Property {@code minimumDepth} - Specify the minimum depth for descendant counts. 70 * Type is {@code int}. 71 * Default value is {@code 0}. 72 * </li> 73 * <li> 74 * Property {@code minimumMessage} - Define the violation message 75 * when the minimum count is not reached. 76 * Type is {@code java.lang.String}. 77 * Default value is {@code null}. 78 * </li> 79 * <li> 80 * Property {@code minimumNumber} - Specify a minimum count for descendants. 81 * Type is {@code int}. 82 * Default value is {@code 0}. 83 * </li> 84 * <li> 85 * Property {@code sumTokenCounts} - Control whether the number of tokens found 86 * should be calculated from the sum of the individual token counts. 87 * Type is {@code boolean}. 88 * Default value is {@code false}. 89 * </li> 90 * <li> 91 * Property {@code tokens} - tokens to check 92 * Type is {@code anyTokenTypesSet}. 93 * Default value is {@code ""}. 94 * </li> 95 * </ul> 96 * 97 * <p> 98 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 99 * </p> 100 * 101 * <p> 102 * Violation Message Keys: 103 * </p> 104 * <ul> 105 * <li> 106 * {@code descendant.token.max} 107 * </li> 108 * <li> 109 * {@code descendant.token.min} 110 * </li> 111 * <li> 112 * {@code descendant.token.sum.max} 113 * </li> 114 * <li> 115 * {@code descendant.token.sum.min} 116 * </li> 117 * </ul> 118 * 119 * @since 3.2 120 */ 121 @FileStatefulCheck 122 public class DescendantTokenCheck extends AbstractCheck { 123 124 /** 125 * A key is pointing to the warning message text in "messages.properties" 126 * file. 127 */ 128 public static final String MSG_KEY_MIN = "descendant.token.min"; 129 130 /** 131 * A key is pointing to the warning message text in "messages.properties" 132 * file. 133 */ 134 public static final String MSG_KEY_MAX = "descendant.token.max"; 135 136 /** 137 * A key is pointing to the warning message text in "messages.properties" 138 * file. 139 */ 140 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 141 142 /** 143 * A key is pointing to the warning message text in "messages.properties" 144 * file. 145 */ 146 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 147 148 /** Specify the minimum depth for descendant counts. */ 149 private int minimumDepth; 150 /** Specify the maximum depth for descendant counts. */ 151 private int maximumDepth = Integer.MAX_VALUE; 152 /** Specify a minimum count for descendants. */ 153 private int minimumNumber; 154 /** Specify a maximum count for descendants. */ 155 private int maximumNumber = Integer.MAX_VALUE; 156 /** 157 * Control whether the number of tokens found should be calculated from 158 * the sum of the individual token counts. 159 */ 160 private boolean sumTokenCounts; 161 /** Specify set of tokens with limited occurrences as descendants. */ 162 @XdocsPropertyType(PropertyType.TOKEN_ARRAY) 163 private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY; 164 /** Define the violation message when the minimum count is not reached. */ 165 private String minimumMessage; 166 /** Define the violation message when the maximum count is exceeded. */ 167 private String maximumMessage; 168 169 /** 170 * Counts of descendant tokens. 171 * Indexed by (token ID - 1) for performance. 172 */ 173 private int[] counts = CommonUtil.EMPTY_INT_ARRAY; 174 175 @Override 176 public int[] getAcceptableTokens() { 177 return TokenUtil.getAllTokenIds(); 178 } 179 180 @Override 181 public int[] getDefaultTokens() { 182 return getRequiredTokens(); 183 } 184 185 @Override 186 public int[] getRequiredTokens() { 187 return CommonUtil.EMPTY_INT_ARRAY; 188 } 189 190 @Override 191 public void visitToken(DetailAST ast) { 192 // reset counts 193 Arrays.fill(counts, 0); 194 countTokens(ast, 0); 195 196 if (sumTokenCounts) { 197 logAsTotal(ast); 198 } 199 else { 200 logAsSeparated(ast); 201 } 202 } 203 204 /** 205 * Log violations for each Token. 206 * 207 * @param ast token 208 */ 209 private void logAsSeparated(DetailAST ast) { 210 // name of this token 211 final String name = TokenUtil.getTokenName(ast.getType()); 212 213 for (int element : limitedTokens) { 214 final int tokenCount = counts[element - 1]; 215 if (tokenCount < minimumNumber) { 216 final String descendantName = TokenUtil.getTokenName(element); 217 218 if (minimumMessage == null) { 219 minimumMessage = MSG_KEY_MIN; 220 } 221 log(ast, 222 minimumMessage, 223 String.valueOf(tokenCount), 224 String.valueOf(minimumNumber), 225 name, 226 descendantName); 227 } 228 if (tokenCount > maximumNumber) { 229 final String descendantName = TokenUtil.getTokenName(element); 230 231 if (maximumMessage == null) { 232 maximumMessage = MSG_KEY_MAX; 233 } 234 log(ast, 235 maximumMessage, 236 String.valueOf(tokenCount), 237 String.valueOf(maximumNumber), 238 name, 239 descendantName); 240 } 241 } 242 } 243 244 /** 245 * Log validation as one violation. 246 * 247 * @param ast current token 248 */ 249 private void logAsTotal(DetailAST ast) { 250 // name of this token 251 final String name = TokenUtil.getTokenName(ast.getType()); 252 253 int total = 0; 254 for (int element : limitedTokens) { 255 total += counts[element - 1]; 256 } 257 if (total < minimumNumber) { 258 if (minimumMessage == null) { 259 minimumMessage = MSG_KEY_SUM_MIN; 260 } 261 log(ast, 262 minimumMessage, 263 String.valueOf(total), 264 String.valueOf(minimumNumber), name); 265 } 266 if (total > maximumNumber) { 267 if (maximumMessage == null) { 268 maximumMessage = MSG_KEY_SUM_MAX; 269 } 270 log(ast, 271 maximumMessage, 272 String.valueOf(total), 273 String.valueOf(maximumNumber), name); 274 } 275 } 276 277 /** 278 * Counts the number of occurrences of descendant tokens. 279 * 280 * @param ast the root token for descendants. 281 * @param depth the maximum depth of the counted descendants. 282 */ 283 private void countTokens(DetailAST ast, int depth) { 284 if (depth <= maximumDepth) { 285 // update count 286 if (depth >= minimumDepth) { 287 final int type = ast.getType(); 288 if (type <= counts.length) { 289 counts[type - 1]++; 290 } 291 } 292 DetailAST child = ast.getFirstChild(); 293 final int nextDepth = depth + 1; 294 while (child != null) { 295 countTokens(child, nextDepth); 296 child = child.getNextSibling(); 297 } 298 } 299 } 300 301 /** 302 * Setter to specify set of tokens with limited occurrences as descendants. 303 * 304 * @param limitedTokensParam tokens to ignore. 305 * @since 3.2 306 */ 307 public void setLimitedTokens(String... limitedTokensParam) { 308 limitedTokens = new int[limitedTokensParam.length]; 309 310 int maxToken = 0; 311 for (int i = 0; i < limitedTokensParam.length; i++) { 312 limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]); 313 if (limitedTokens[i] >= maxToken + 1) { 314 maxToken = limitedTokens[i]; 315 } 316 } 317 counts = new int[maxToken]; 318 } 319 320 /** 321 * Setter to specify the minimum depth for descendant counts. 322 * 323 * @param minimumDepth the minimum depth for descendant counts. 324 * @since 3.2 325 */ 326 public void setMinimumDepth(int minimumDepth) { 327 this.minimumDepth = minimumDepth; 328 } 329 330 /** 331 * Setter to specify the maximum depth for descendant counts. 332 * 333 * @param maximumDepth the maximum depth for descendant counts. 334 * @since 3.2 335 */ 336 public void setMaximumDepth(int maximumDepth) { 337 this.maximumDepth = maximumDepth; 338 } 339 340 /** 341 * Setter to specify a minimum count for descendants. 342 * 343 * @param minimumNumber the minimum count for descendants. 344 * @since 3.2 345 */ 346 public void setMinimumNumber(int minimumNumber) { 347 this.minimumNumber = minimumNumber; 348 } 349 350 /** 351 * Setter to specify a maximum count for descendants. 352 * 353 * @param maximumNumber the maximum count for descendants. 354 * @since 3.2 355 */ 356 public void setMaximumNumber(int maximumNumber) { 357 this.maximumNumber = maximumNumber; 358 } 359 360 /** 361 * Setter to define the violation message when the minimum count is not reached. 362 * 363 * @param message the violation message for minimum count not reached. 364 * Used as a {@code MessageFormat} pattern with arguments 365 * <ul> 366 * <li>{0} - token count</li> 367 * <li>{1} - minimum number</li> 368 * <li>{2} - name of token</li> 369 * <li>{3} - name of limited token</li> 370 * </ul> 371 * @since 3.2 372 */ 373 public void setMinimumMessage(String message) { 374 minimumMessage = message; 375 } 376 377 /** 378 * Setter to define the violation message when the maximum count is exceeded. 379 * 380 * @param message the violation message for maximum count exceeded. 381 * Used as a {@code MessageFormat} pattern with arguments 382 * <ul> 383 * <li>{0} - token count</li> 384 * <li>{1} - maximum number</li> 385 * <li>{2} - name of token</li> 386 * <li>{3} - name of limited token</li> 387 * </ul> 388 * @since 3.2 389 */ 390 391 public void setMaximumMessage(String message) { 392 maximumMessage = message; 393 } 394 395 /** 396 * Setter to control whether the number of tokens found should be calculated 397 * from the sum of the individual token counts. 398 * 399 * @param sum whether to use the sum. 400 * @since 5.0 401 */ 402 public void setSumTokenCounts(boolean sum) { 403 sumTokenCounts = sum; 404 } 405 406 }