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" file. 053 */ 054 public static final String MSG_KEY_MIN = "descendant.token.min"; 055 056 /** 057 * A key is pointing to the warning message text in "messages.properties" file. 058 */ 059 public static final String MSG_KEY_MAX = "descendant.token.max"; 060 061 /** 062 * A key is pointing to the warning message text in "messages.properties" file. 063 */ 064 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 065 066 /** 067 * A key is pointing to the warning message text in "messages.properties" file. 068 */ 069 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 070 071 /** Specify the minimum depth for descendant counts. */ 072 private int minimumDepth; 073 /** Specify the maximum depth for descendant counts. */ 074 private int maximumDepth = Integer.MAX_VALUE; 075 /** Specify a minimum count for descendants. */ 076 private int minimumNumber; 077 /** Specify a maximum count for descendants. */ 078 private int maximumNumber = Integer.MAX_VALUE; 079 /** 080 * Control whether the number of tokens found should be calculated from 081 * the sum of the individual token counts. 082 */ 083 private boolean sumTokenCounts; 084 /** Specify set of tokens with limited occurrences as descendants. */ 085 @XdocsPropertyType(PropertyType.TOKEN_ARRAY) 086 private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY; 087 /** Define the violation message when the minimum count is not reached. */ 088 private String minimumMessage; 089 /** Define the violation message when the maximum count is exceeded. */ 090 private String maximumMessage; 091 092 /** 093 * Counts of descendant tokens. 094 * Indexed by (token ID - 1) for performance. 095 */ 096 private int[] counts = CommonUtil.EMPTY_INT_ARRAY; 097 098 /** 099 * Creates a new {@code DescendantTokenCheck} instance. 100 */ 101 public DescendantTokenCheck() { 102 // no code by default 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return TokenUtil.getAllTokenIds(); 108 } 109 110 @Override 111 public int[] getDefaultTokens() { 112 return getRequiredTokens(); 113 } 114 115 @Override 116 public int[] getRequiredTokens() { 117 return CommonUtil.EMPTY_INT_ARRAY; 118 } 119 120 @Override 121 public void visitToken(DetailAST ast) { 122 // reset counts 123 Arrays.fill(counts, 0); 124 countTokens(ast); 125 126 if (sumTokenCounts) { 127 logAsTotal(ast); 128 } 129 else { 130 logAsSeparated(ast); 131 } 132 } 133 134 /** 135 * Log violations for each Token. 136 * 137 * @param ast token 138 */ 139 private void logAsSeparated(DetailAST ast) { 140 // name of this token 141 final String name = TokenUtil.getTokenName(ast.getType()); 142 143 for (int element : limitedTokens) { 144 final int tokenCount = counts[element - 1]; 145 if (tokenCount < minimumNumber) { 146 final String descendantName = TokenUtil.getTokenName(element); 147 148 if (minimumMessage == null) { 149 minimumMessage = MSG_KEY_MIN; 150 } 151 log(ast, 152 minimumMessage, 153 String.valueOf(tokenCount), 154 String.valueOf(minimumNumber), 155 name, 156 descendantName); 157 } 158 if (tokenCount > maximumNumber) { 159 final String descendantName = TokenUtil.getTokenName(element); 160 161 if (maximumMessage == null) { 162 maximumMessage = MSG_KEY_MAX; 163 } 164 log(ast, 165 maximumMessage, 166 String.valueOf(tokenCount), 167 String.valueOf(maximumNumber), 168 name, 169 descendantName); 170 } 171 } 172 } 173 174 /** 175 * Log validation as one violation. 176 * 177 * @param ast current token 178 */ 179 private void logAsTotal(DetailAST ast) { 180 // name of this token 181 final String name = TokenUtil.getTokenName(ast.getType()); 182 183 int total = 0; 184 for (int element : limitedTokens) { 185 total += counts[element - 1]; 186 } 187 if (total < minimumNumber) { 188 if (minimumMessage == null) { 189 minimumMessage = MSG_KEY_SUM_MIN; 190 } 191 log(ast, 192 minimumMessage, 193 String.valueOf(total), 194 String.valueOf(minimumNumber), name); 195 } 196 if (total > maximumNumber) { 197 if (maximumMessage == null) { 198 maximumMessage = MSG_KEY_SUM_MAX; 199 } 200 log(ast, 201 maximumMessage, 202 String.valueOf(total), 203 String.valueOf(maximumNumber), name); 204 } 205 } 206 207 /** 208 * Counts the number of occurrences of descendant tokens. 209 * The tree is traversed iteratively, as deeply nested trees 210 * (for example long string concatenations) would overflow the stack. 211 * 212 * @param root the root token for descendants. 213 */ 214 private void countTokens(DetailAST root) { 215 DetailAST node = null; 216 if (maximumDepth >= 0) { 217 node = root; 218 } 219 int depth = 0; 220 while (node != null) { 221 // update count 222 if (depth >= minimumDepth) { 223 final int type = node.getType(); 224 if (type <= counts.length) { 225 counts[type - 1]++; 226 } 227 } 228 DetailAST next = null; 229 if (depth < maximumDepth) { 230 next = node.getFirstChild(); 231 } 232 if (next == null) { 233 while (next == null && depth > 0) { 234 next = node.getNextSibling(); 235 if (next == null) { 236 node = node.getParent(); 237 depth--; 238 } 239 } 240 } 241 else { 242 depth++; 243 } 244 node = next; 245 } 246 } 247 248 /** 249 * Setter to specify set of tokens with limited occurrences as descendants. 250 * 251 * @param limitedTokensParam tokens to ignore. 252 * @since 3.2 253 */ 254 public void setLimitedTokens(String... limitedTokensParam) { 255 limitedTokens = new int[limitedTokensParam.length]; 256 257 int maxToken = 0; 258 for (int index = 0; index < limitedTokensParam.length; index++) { 259 limitedTokens[index] = TokenUtil.getTokenId(limitedTokensParam[index]); 260 if (limitedTokens[index] >= maxToken + 1) { 261 maxToken = limitedTokens[index]; 262 } 263 } 264 counts = new int[maxToken]; 265 } 266 267 /** 268 * Setter to specify the minimum depth for descendant counts. 269 * 270 * @param minimumDepth the minimum depth for descendant counts. 271 * @since 3.2 272 */ 273 public void setMinimumDepth(int minimumDepth) { 274 this.minimumDepth = minimumDepth; 275 } 276 277 /** 278 * Setter to specify the maximum depth for descendant counts. 279 * 280 * @param maximumDepth the maximum depth for descendant counts. 281 * @since 3.2 282 */ 283 public void setMaximumDepth(int maximumDepth) { 284 this.maximumDepth = maximumDepth; 285 } 286 287 /** 288 * Setter to specify a minimum count for descendants. 289 * 290 * @param minimumNumber the minimum count for descendants. 291 * @since 3.2 292 */ 293 public void setMinimumNumber(int minimumNumber) { 294 this.minimumNumber = minimumNumber; 295 } 296 297 /** 298 * Setter to specify a maximum count for descendants. 299 * 300 * @param maximumNumber the maximum count for descendants. 301 * @since 3.2 302 */ 303 public void setMaximumNumber(int maximumNumber) { 304 this.maximumNumber = maximumNumber; 305 } 306 307 /** 308 * Setter to define the violation message when the minimum count is not reached. 309 * 310 * @param message the violation message for minimum count not reached. 311 * Used as a {@code MessageFormat} pattern with arguments 312 * <ul> 313 * <li>{0} - token count</li> 314 * <li>{1} - minimum number</li> 315 * <li>{2} - name of token</li> 316 * <li>{3} - name of limited token</li> 317 * </ul> 318 * @since 3.2 319 */ 320 public void setMinimumMessage(String message) { 321 minimumMessage = message; 322 } 323 324 /** 325 * Setter to define the violation message when the maximum count is exceeded. 326 * 327 * @param message the violation message for maximum count exceeded. 328 * Used as a {@code MessageFormat} pattern with arguments 329 * <ul> 330 * <li>{0} - token count</li> 331 * <li>{1} - maximum number</li> 332 * <li>{2} - name of token</li> 333 * <li>{3} - name of limited token</li> 334 * </ul> 335 * @since 3.2 336 */ 337 338 public void setMaximumMessage(String message) { 339 maximumMessage = message; 340 } 341 342 /** 343 * Setter to control whether the number of tokens found should be calculated 344 * from the sum of the individual token counts. 345 * 346 * @param sum whether to use the sum. 347 * @since 5.0 348 */ 349 public void setSumTokenCounts(boolean sum) { 350 sumTokenCounts = sum; 351 } 352 353}