001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 /** 103 * Creates a new {@code DescendantTokenCheck} instance. 104 */ 105 public DescendantTokenCheck() { 106 // no code by default 107 } 108 109 @Override 110 public int[] getAcceptableTokens() { 111 return TokenUtil.getAllTokenIds(); 112 } 113 114 @Override 115 public int[] getDefaultTokens() { 116 return getRequiredTokens(); 117 } 118 119 @Override 120 public int[] getRequiredTokens() { 121 return CommonUtil.EMPTY_INT_ARRAY; 122 } 123 124 @Override 125 public void visitToken(DetailAST ast) { 126 // reset counts 127 Arrays.fill(counts, 0); 128 countTokens(ast, 0); 129 130 if (sumTokenCounts) { 131 logAsTotal(ast); 132 } 133 else { 134 logAsSeparated(ast); 135 } 136 } 137 138 /** 139 * Log violations for each Token. 140 * 141 * @param ast token 142 */ 143 private void logAsSeparated(DetailAST ast) { 144 // name of this token 145 final String name = TokenUtil.getTokenName(ast.getType()); 146 147 for (int element : limitedTokens) { 148 final int tokenCount = counts[element - 1]; 149 if (tokenCount < minimumNumber) { 150 final String descendantName = TokenUtil.getTokenName(element); 151 152 if (minimumMessage == null) { 153 minimumMessage = MSG_KEY_MIN; 154 } 155 log(ast, 156 minimumMessage, 157 String.valueOf(tokenCount), 158 String.valueOf(minimumNumber), 159 name, 160 descendantName); 161 } 162 if (tokenCount > maximumNumber) { 163 final String descendantName = TokenUtil.getTokenName(element); 164 165 if (maximumMessage == null) { 166 maximumMessage = MSG_KEY_MAX; 167 } 168 log(ast, 169 maximumMessage, 170 String.valueOf(tokenCount), 171 String.valueOf(maximumNumber), 172 name, 173 descendantName); 174 } 175 } 176 } 177 178 /** 179 * Log validation as one violation. 180 * 181 * @param ast current token 182 */ 183 private void logAsTotal(DetailAST ast) { 184 // name of this token 185 final String name = TokenUtil.getTokenName(ast.getType()); 186 187 int total = 0; 188 for (int element : limitedTokens) { 189 total += counts[element - 1]; 190 } 191 if (total < minimumNumber) { 192 if (minimumMessage == null) { 193 minimumMessage = MSG_KEY_SUM_MIN; 194 } 195 log(ast, 196 minimumMessage, 197 String.valueOf(total), 198 String.valueOf(minimumNumber), name); 199 } 200 if (total > maximumNumber) { 201 if (maximumMessage == null) { 202 maximumMessage = MSG_KEY_SUM_MAX; 203 } 204 log(ast, 205 maximumMessage, 206 String.valueOf(total), 207 String.valueOf(maximumNumber), name); 208 } 209 } 210 211 /** 212 * Counts the number of occurrences of descendant tokens. 213 * 214 * @param ast the root token for descendants. 215 * @param depth the maximum depth of the counted descendants. 216 */ 217 private void countTokens(DetailAST ast, int depth) { 218 if (depth <= maximumDepth) { 219 // update count 220 if (depth >= minimumDepth) { 221 final int type = ast.getType(); 222 if (type <= counts.length) { 223 counts[type - 1]++; 224 } 225 } 226 DetailAST child = ast.getFirstChild(); 227 final int nextDepth = depth + 1; 228 while (child != null) { 229 countTokens(child, nextDepth); 230 child = child.getNextSibling(); 231 } 232 } 233 } 234 235 /** 236 * Setter to specify set of tokens with limited occurrences as descendants. 237 * 238 * @param limitedTokensParam tokens to ignore. 239 * @since 3.2 240 */ 241 public void setLimitedTokens(String... limitedTokensParam) { 242 limitedTokens = new int[limitedTokensParam.length]; 243 244 int maxToken = 0; 245 for (int i = 0; i < limitedTokensParam.length; i++) { 246 limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]); 247 if (limitedTokens[i] >= maxToken + 1) { 248 maxToken = limitedTokens[i]; 249 } 250 } 251 counts = new int[maxToken]; 252 } 253 254 /** 255 * Setter to specify the minimum depth for descendant counts. 256 * 257 * @param minimumDepth the minimum depth for descendant counts. 258 * @since 3.2 259 */ 260 public void setMinimumDepth(int minimumDepth) { 261 this.minimumDepth = minimumDepth; 262 } 263 264 /** 265 * Setter to specify the maximum depth for descendant counts. 266 * 267 * @param maximumDepth the maximum depth for descendant counts. 268 * @since 3.2 269 */ 270 public void setMaximumDepth(int maximumDepth) { 271 this.maximumDepth = maximumDepth; 272 } 273 274 /** 275 * Setter to specify a minimum count for descendants. 276 * 277 * @param minimumNumber the minimum count for descendants. 278 * @since 3.2 279 */ 280 public void setMinimumNumber(int minimumNumber) { 281 this.minimumNumber = minimumNumber; 282 } 283 284 /** 285 * Setter to specify a maximum count for descendants. 286 * 287 * @param maximumNumber the maximum count for descendants. 288 * @since 3.2 289 */ 290 public void setMaximumNumber(int maximumNumber) { 291 this.maximumNumber = maximumNumber; 292 } 293 294 /** 295 * Setter to define the violation message when the minimum count is not reached. 296 * 297 * @param message the violation message for minimum count not reached. 298 * Used as a {@code MessageFormat} pattern with arguments 299 * <ul> 300 * <li>{0} - token count</li> 301 * <li>{1} - minimum number</li> 302 * <li>{2} - name of token</li> 303 * <li>{3} - name of limited token</li> 304 * </ul> 305 * @since 3.2 306 */ 307 public void setMinimumMessage(String message) { 308 minimumMessage = message; 309 } 310 311 /** 312 * Setter to define the violation message when the maximum count is exceeded. 313 * 314 * @param message the violation message for maximum count exceeded. 315 * Used as a {@code MessageFormat} pattern with arguments 316 * <ul> 317 * <li>{0} - token count</li> 318 * <li>{1} - maximum number</li> 319 * <li>{2} - name of token</li> 320 * <li>{3} - name of limited token</li> 321 * </ul> 322 * @since 3.2 323 */ 324 325 public void setMaximumMessage(String message) { 326 maximumMessage = message; 327 } 328 329 /** 330 * Setter to control whether the number of tokens found should be calculated 331 * from the sum of the individual token counts. 332 * 333 * @param sum whether to use the sum. 334 * @since 5.0 335 */ 336 public void setSumTokenCounts(boolean sum) { 337 sumTokenCounts = sum; 338 } 339 340}