View Javadoc
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 }