001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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; 023 024import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 025import com.puppycrawl.tools.checkstyle.PropertyType; 026import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <div> 034 * Checks for restricted tokens beneath other tokens. 035 * </div> 036 * 037 * <p> 038 * WARNING: This is a very powerful and flexible check, but, at the same time, 039 * it is low-level and very implementation-dependent because its results depend 040 * on the grammar we use to build abstract syntax trees. Thus, we recommend using 041 * other checks when they provide the desired functionality. Essentially, this 042 * check just works on the level of an abstract syntax tree and knows nothing 043 * about language structures. 044 * </p> 045 * 046 * @since 3.2 047 */ 048@FileStatefulCheck 049public class DescendantTokenCheck extends AbstractCheck { 050 051 /** 052 * A key is pointing to the warning message text in "messages.properties" 053 * file. 054 */ 055 public static final String MSG_KEY_MIN = "descendant.token.min"; 056 057 /** 058 * A key is pointing to the warning message text in "messages.properties" 059 * file. 060 */ 061 public static final String MSG_KEY_MAX = "descendant.token.max"; 062 063 /** 064 * A key is pointing to the warning message text in "messages.properties" 065 * file. 066 */ 067 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 068 069 /** 070 * A key is pointing to the warning message text in "messages.properties" 071 * file. 072 */ 073 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 074 075 /** Specify the minimum depth for descendant counts. */ 076 private int minimumDepth; 077 /** Specify the maximum depth for descendant counts. */ 078 private int maximumDepth = Integer.MAX_VALUE; 079 /** Specify a minimum count for descendants. */ 080 private int minimumNumber; 081 /** Specify a maximum count for descendants. */ 082 private int maximumNumber = Integer.MAX_VALUE; 083 /** 084 * Control whether the number of tokens found should be calculated from 085 * the sum of the individual token counts. 086 */ 087 private boolean sumTokenCounts; 088 /** Specify set of tokens with limited occurrences as descendants. */ 089 @XdocsPropertyType(PropertyType.TOKEN_ARRAY) 090 private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY; 091 /** Define the violation message when the minimum count is not reached. */ 092 private String minimumMessage; 093 /** Define the violation message when the maximum count is exceeded. */ 094 private String maximumMessage; 095 096 /** 097 * Counts of descendant tokens. 098 * Indexed by (token ID - 1) for performance. 099 */ 100 private int[] counts = CommonUtil.EMPTY_INT_ARRAY; 101 102 @Override 103 public int[] getAcceptableTokens() { 104 return TokenUtil.getAllTokenIds(); 105 } 106 107 @Override 108 public int[] getDefaultTokens() { 109 return getRequiredTokens(); 110 } 111 112 @Override 113 public int[] getRequiredTokens() { 114 return CommonUtil.EMPTY_INT_ARRAY; 115 } 116 117 @Override 118 public void visitToken(DetailAST ast) { 119 // reset counts 120 Arrays.fill(counts, 0); 121 countTokens(ast, 0); 122 123 if (sumTokenCounts) { 124 logAsTotal(ast); 125 } 126 else { 127 logAsSeparated(ast); 128 } 129 } 130 131 /** 132 * Log violations for each Token. 133 * 134 * @param ast token 135 */ 136 private void logAsSeparated(DetailAST ast) { 137 // name of this token 138 final String name = TokenUtil.getTokenName(ast.getType()); 139 140 for (int element : limitedTokens) { 141 final int tokenCount = counts[element - 1]; 142 if (tokenCount < minimumNumber) { 143 final String descendantName = TokenUtil.getTokenName(element); 144 145 if (minimumMessage == null) { 146 minimumMessage = MSG_KEY_MIN; 147 } 148 log(ast, 149 minimumMessage, 150 String.valueOf(tokenCount), 151 String.valueOf(minimumNumber), 152 name, 153 descendantName); 154 } 155 if (tokenCount > maximumNumber) { 156 final String descendantName = TokenUtil.getTokenName(element); 157 158 if (maximumMessage == null) { 159 maximumMessage = MSG_KEY_MAX; 160 } 161 log(ast, 162 maximumMessage, 163 String.valueOf(tokenCount), 164 String.valueOf(maximumNumber), 165 name, 166 descendantName); 167 } 168 } 169 } 170 171 /** 172 * Log validation as one violation. 173 * 174 * @param ast current token 175 */ 176 private void logAsTotal(DetailAST ast) { 177 // name of this token 178 final String name = TokenUtil.getTokenName(ast.getType()); 179 180 int total = 0; 181 for (int element : limitedTokens) { 182 total += counts[element - 1]; 183 } 184 if (total < minimumNumber) { 185 if (minimumMessage == null) { 186 minimumMessage = MSG_KEY_SUM_MIN; 187 } 188 log(ast, 189 minimumMessage, 190 String.valueOf(total), 191 String.valueOf(minimumNumber), name); 192 } 193 if (total > maximumNumber) { 194 if (maximumMessage == null) { 195 maximumMessage = MSG_KEY_SUM_MAX; 196 } 197 log(ast, 198 maximumMessage, 199 String.valueOf(total), 200 String.valueOf(maximumNumber), name); 201 } 202 } 203 204 /** 205 * Counts the number of occurrences of descendant tokens. 206 * 207 * @param ast the root token for descendants. 208 * @param depth the maximum depth of the counted descendants. 209 */ 210 private void countTokens(DetailAST ast, int depth) { 211 if (depth <= maximumDepth) { 212 // update count 213 if (depth >= minimumDepth) { 214 final int type = ast.getType(); 215 if (type <= counts.length) { 216 counts[type - 1]++; 217 } 218 } 219 DetailAST child = ast.getFirstChild(); 220 final int nextDepth = depth + 1; 221 while (child != null) { 222 countTokens(child, nextDepth); 223 child = child.getNextSibling(); 224 } 225 } 226 } 227 228 /** 229 * Setter to specify set of tokens with limited occurrences as descendants. 230 * 231 * @param limitedTokensParam tokens to ignore. 232 * @since 3.2 233 */ 234 public void setLimitedTokens(String... limitedTokensParam) { 235 limitedTokens = new int[limitedTokensParam.length]; 236 237 int maxToken = 0; 238 for (int i = 0; i < limitedTokensParam.length; i++) { 239 limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]); 240 if (limitedTokens[i] >= maxToken + 1) { 241 maxToken = limitedTokens[i]; 242 } 243 } 244 counts = new int[maxToken]; 245 } 246 247 /** 248 * Setter to specify the minimum depth for descendant counts. 249 * 250 * @param minimumDepth the minimum depth for descendant counts. 251 * @since 3.2 252 */ 253 public void setMinimumDepth(int minimumDepth) { 254 this.minimumDepth = minimumDepth; 255 } 256 257 /** 258 * Setter to specify the maximum depth for descendant counts. 259 * 260 * @param maximumDepth the maximum depth for descendant counts. 261 * @since 3.2 262 */ 263 public void setMaximumDepth(int maximumDepth) { 264 this.maximumDepth = maximumDepth; 265 } 266 267 /** 268 * Setter to specify a minimum count for descendants. 269 * 270 * @param minimumNumber the minimum count for descendants. 271 * @since 3.2 272 */ 273 public void setMinimumNumber(int minimumNumber) { 274 this.minimumNumber = minimumNumber; 275 } 276 277 /** 278 * Setter to specify a maximum count for descendants. 279 * 280 * @param maximumNumber the maximum count for descendants. 281 * @since 3.2 282 */ 283 public void setMaximumNumber(int maximumNumber) { 284 this.maximumNumber = maximumNumber; 285 } 286 287 /** 288 * Setter to define the violation message when the minimum count is not reached. 289 * 290 * @param message the violation message for minimum count not reached. 291 * Used as a {@code MessageFormat} pattern with arguments 292 * <ul> 293 * <li>{0} - token count</li> 294 * <li>{1} - minimum number</li> 295 * <li>{2} - name of token</li> 296 * <li>{3} - name of limited token</li> 297 * </ul> 298 * @since 3.2 299 */ 300 public void setMinimumMessage(String message) { 301 minimumMessage = message; 302 } 303 304 /** 305 * Setter to define the violation message when the maximum count is exceeded. 306 * 307 * @param message the violation message for maximum count exceeded. 308 * Used as a {@code MessageFormat} pattern with arguments 309 * <ul> 310 * <li>{0} - token count</li> 311 * <li>{1} - maximum number</li> 312 * <li>{2} - name of token</li> 313 * <li>{3} - name of limited token</li> 314 * </ul> 315 * @since 3.2 316 */ 317 318 public void setMaximumMessage(String message) { 319 maximumMessage = message; 320 } 321 322 /** 323 * Setter to control whether the number of tokens found should be calculated 324 * from the sum of the individual token counts. 325 * 326 * @param sum whether to use the sum. 327 * @since 5.0 328 */ 329 public void setSumTokenCounts(boolean sum) { 330 sumTokenCounts = sum; 331 } 332 333}