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