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   * <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 }