001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 * <p>
034 * Checks for restricted tokens beneath other tokens.
035 * </p>
036 * <p>
037 * WARNING: This is a very powerful and flexible check, but, at the same time,
038 * it is low-level and very implementation-dependent because its results depend
039 * on the grammar we use to build abstract syntax trees. Thus, we recommend using
040 * other checks when they provide the desired functionality. Essentially, this
041 * check just works on the level of an abstract syntax tree and knows nothing
042 * about language structures.
043 * </p>
044 * <ul>
045 * <li>
046 * Property {@code limitedTokens} - Specify set of tokens with limited occurrences as descendants.
047 * Type is {@code java.lang.String[]}.
048 * Validation type is {@code tokenTypesSet}.
049 * Default value is {@code ""}.
050 * </li>
051 * <li>
052 * Property {@code minimumDepth} - Specify the minimum depth for descendant counts.
053 * Type is {@code int}.
054 * Default value is {@code 0}.
055 * </li>
056 * <li>
057 * Property {@code maximumDepth} - Specify the maximum depth for descendant counts.
058 * Type is {@code int}.
059 * Default value is {@code 2147483647}.
060 * </li>
061 * <li>
062 * Property {@code minimumNumber} - Specify a minimum count for descendants.
063 * Type is {@code int}.
064 * Default value is {@code 0}.
065 * </li>
066 * <li>
067 * Property {@code maximumNumber} - Specify a maximum count for descendants.
068 * Type is {@code int}.
069 * Default value is {@code 2147483647}.
070 * </li>
071 * <li>
072 * Property {@code sumTokenCounts} - Control whether the number of tokens found
073 * should be calculated from the sum of the individual token counts.
074 * Type is {@code boolean}.
075 * Default value is {@code false}.
076 * </li>
077 * <li>
078 * Property {@code minimumMessage} - Define the violation message
079 * when the minimum count is not reached.
080 * Type is {@code java.lang.String}.
081 * Default value is {@code null}.
082 * </li>
083 * <li>
084 * Property {@code maximumMessage} - Define the violation message
085 * when the maximum count is exceeded.
086 * Type is {@code java.lang.String}.
087 * Default value is {@code null}.
088 * </li>
089 * <li>
090 * Property {@code tokens} - tokens to check
091 * Type is {@code anyTokenTypesSet}.
092 * Default value is {@code ""}.
093 * </li>
094 * </ul>
095 * <p>
096 * To configure the check to produce a violation on a switch statement with no default case:
097 * </p>
098 * <pre>
099 * &lt;module name=&quot;DescendantToken&quot;&gt;
100 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_SWITCH&quot;/&gt;
101 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;2&quot;/&gt;
102 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_DEFAULT&quot;/&gt;
103 *   &lt;property name=&quot;minimumNumber&quot; value=&quot;1&quot;/&gt;
104 * &lt;/module&gt;
105 * </pre>
106 * <p>
107 * To configure the check to produce a violation on a condition in {@code for}
108 * which performs no check:
109 * </p>
110 * <pre>
111 * &lt;module name=&quot;DescendantToken&quot;&gt;
112 *   &lt;property name=&quot;tokens&quot; value=&quot;FOR_CONDITION&quot;/&gt;
113 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;EXPR&quot;/&gt;
114 *   &lt;property name=&quot;minimumNumber&quot; value=&quot;1&quot;/&gt;
115 * &lt;/module&gt;
116 * </pre>
117 * <p>
118 * To configure the check to produce a violation on comparing {@code this} with
119 * {@code null}(i.e. {@code this == null} and {@code this != null}):
120 * </p>
121 * <pre>
122 * &lt;module name=&quot;DescendantToken&quot;&gt;
123 *   &lt;property name=&quot;tokens&quot; value=&quot;EQUAL,NOT_EQUAL&quot;/&gt;
124 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_THIS,LITERAL_NULL&quot;/&gt;
125 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;1&quot;/&gt;
126 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;1&quot;/&gt;
127 *   &lt;property name=&quot;sumTokenCounts&quot; value=&quot;true&quot;/&gt;
128 * &lt;/module&gt;
129 * </pre>
130 * <p>
131 * To configure the check to produce a violation on a {@code String} literal equality check:
132 * </p>
133 * <pre>
134 * &lt;module name=&quot;DescendantToken&quot;&gt;
135 *   &lt;property name=&quot;tokens&quot; value=&quot;EQUAL,NOT_EQUAL&quot;/&gt;
136 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;STRING_LITERAL&quot;/&gt;
137 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
138 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;1&quot;/&gt;
139 * &lt;/module&gt;
140 * </pre>
141 * <p>
142 * To configure the check to produce a violation on an assert statement that may
143 * have side effects (formatted for browser display):
144 * </p>
145 * <pre>
146 * &lt;module name=&quot;DescendantToken&quot;&gt;
147 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_ASSERT&quot;/&gt;
148 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;ASSIGN,DEC,INC,POST_DEC,
149 *     POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,
150 *     BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,
151 *     METHOD_CALL&quot;/&gt;
152 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
153 * &lt;/module&gt;
154 * </pre>
155 * <p>
156 * To configure the check to produce a violation on an initializer in {@code for}
157 * performs no setup (where a {@code while} statement could be used instead):
158 * </p>
159 * <pre>
160 * &lt;module name=&quot;DescendantToken&quot;&gt;
161 *   &lt;property name=&quot;tokens&quot; value=&quot;FOR_INIT&quot;/&gt;
162 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;EXPR&quot;/&gt;
163 *   &lt;property name=&quot;minimumNumber&quot; value=&quot;1&quot;/&gt;
164 * &lt;/module&gt;
165 * </pre>
166 * <p>
167 * To configure the check to produce a violation on a switch that is nested in another switch:
168 * </p>
169 * <pre>
170 * &lt;module name=&quot;DescendantToken&quot;&gt;
171 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_SWITCH&quot;/&gt;
172 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_SWITCH&quot;/&gt;
173 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
174 *   &lt;property name=&quot;minimumDepth&quot; value=&quot;1&quot;/&gt;
175 * &lt;/module&gt;
176 * </pre>
177 * <p>
178 * To configure the check to produce a violation on a return statement from
179 * within a catch or finally block:
180 * </p>
181 * <pre>
182 * &lt;module name=&quot;DescendantToken&quot;&gt;
183 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_FINALLY,LITERAL_CATCH&quot;/&gt;
184 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_RETURN&quot;/&gt;
185 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
186 * &lt;/module&gt;
187 * </pre>
188 * <p>
189 * To configure the check to produce a violation on a try statement within a catch or finally block:
190 * </p>
191 * <pre>
192 * &lt;module name=&quot;DescendantToken&quot;&gt;
193 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CATCH,LITERAL_FINALLY&quot;/&gt;
194 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_TRY&quot;/&gt;
195 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
196 * &lt;/module&gt;
197 * </pre>
198 * <p>
199 * To configure the check to produce a violation on a switch with too many cases:
200 * </p>
201 * <pre>
202 * &lt;module name=&quot;DescendantToken&quot;&gt;
203 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_SWITCH&quot;/&gt;
204 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_CASE&quot;/&gt;
205 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;2&quot;/&gt;
206 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;10&quot;/&gt;
207 * &lt;/module&gt;
208 * </pre>
209 * <p>
210 * To configure the check to produce a violation on a method with too many local variables:
211 * </p>
212 * <pre>
213 * &lt;module name=&quot;DescendantToken&quot;&gt;
214 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_DEF&quot;/&gt;
215 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;VARIABLE_DEF&quot;/&gt;
216 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;2&quot;/&gt;
217 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;10&quot;/&gt;
218 * &lt;/module&gt;
219 * </pre>
220 * <p>
221 * To configure the check to produce a violation on a method with too many returns:
222 * </p>
223 * <pre>
224 * &lt;module name=&quot;DescendantToken&quot;&gt;
225 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_DEF&quot;/&gt;
226 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;LITERAL_RETURN&quot;/&gt;
227 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;3&quot;/&gt;
228 * &lt;/module&gt;
229 * </pre>
230 * <p>
231 * To configure the check to produce a violation on an interface with too many fields:
232 * </p>
233 * <pre>
234 * &lt;module name=&quot;DescendantToken&quot;&gt;
235 *   &lt;property name=&quot;tokens&quot; value=&quot;INTERFACE_DEF&quot;/&gt;
236 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;VARIABLE_DEF&quot;/&gt;
237 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;2&quot;/&gt;
238 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
239 * &lt;/module&gt;
240 * </pre>
241 * <p>
242 * To configure the check to produce a violation on a method which throws too many exceptions:
243 * </p>
244 * <pre>
245 * &lt;module name=&quot;DescendantToken&quot;&gt;
246 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_THROWS&quot;/&gt;
247 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;IDENT&quot;/&gt;
248 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;1&quot;/&gt;
249 * &lt;/module&gt;
250 * </pre>
251 * <p>
252 * To configure the check to produce a violation on a method with too many expressions:
253 * </p>
254 * <pre>
255 * &lt;module name=&quot;DescendantToken&quot;&gt;
256 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_DEF&quot;/&gt;
257 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;EXPR&quot;/&gt;
258 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;200&quot;/&gt;
259 * &lt;/module&gt;
260 * </pre>
261 * <p>
262 * To configure the check to produce a violation on an empty statement:
263 * </p>
264 * <pre>
265 * &lt;module name=&quot;DescendantToken&quot;&gt;
266 *   &lt;property name=&quot;tokens&quot; value=&quot;EMPTY_STAT&quot;/&gt;
267 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;EMPTY_STAT&quot;/&gt;
268 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;0&quot;/&gt;
269 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;0&quot;/&gt;
270 *   &lt;property name=&quot;maximumMessage&quot;
271 *     value=&quot;Empty statement is not allowed.&quot;/&gt;
272 * &lt;/module&gt;
273 * </pre>
274 * <p>
275 * To configure the check to produce a violation on a class with too many fields:
276 * </p>
277 * <pre>
278 * &lt;module name=&quot;DescendantToken&quot;&gt;
279 *   &lt;property name=&quot;tokens&quot; value=&quot;CLASS_DEF&quot;/&gt;
280 *   &lt;property name=&quot;limitedTokens&quot; value=&quot;VARIABLE_DEF&quot;/&gt;
281 *   &lt;property name=&quot;maximumDepth&quot; value=&quot;2&quot;/&gt;
282 *   &lt;property name=&quot;maximumNumber&quot; value=&quot;10&quot;/&gt;
283 * &lt;/module&gt;
284 * </pre>
285 * <p>
286 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
287 * </p>
288 * <p>
289 * Violation Message Keys:
290 * </p>
291 * <ul>
292 * <li>
293 * {@code descendant.token.max}
294 * </li>
295 * <li>
296 * {@code descendant.token.min}
297 * </li>
298 * <li>
299 * {@code descendant.token.sum.max}
300 * </li>
301 * <li>
302 * {@code descendant.token.sum.min}
303 * </li>
304 * </ul>
305 *
306 * @since 3.2
307 */
308@FileStatefulCheck
309public class DescendantTokenCheck extends AbstractCheck {
310
311    /**
312     * A key is pointing to the warning message text in "messages.properties"
313     * file.
314     */
315    public static final String MSG_KEY_MIN = "descendant.token.min";
316
317    /**
318     * A key is pointing to the warning message text in "messages.properties"
319     * file.
320     */
321    public static final String MSG_KEY_MAX = "descendant.token.max";
322
323    /**
324     * A key is pointing to the warning message text in "messages.properties"
325     * file.
326     */
327    public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min";
328
329    /**
330     * A key is pointing to the warning message text in "messages.properties"
331     * file.
332     */
333    public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max";
334
335    /** Specify the minimum depth for descendant counts. */
336    private int minimumDepth;
337    /** Specify the maximum depth for descendant counts. */
338    private int maximumDepth = Integer.MAX_VALUE;
339    /** Specify a minimum count for descendants. */
340    private int minimumNumber;
341    /** Specify a maximum count for descendants. */
342    private int maximumNumber = Integer.MAX_VALUE;
343    /**
344     * Control whether the number of tokens found should be calculated from
345     * the sum of the individual token counts.
346     */
347    private boolean sumTokenCounts;
348    /** Specify set of tokens with limited occurrences as descendants. */
349    @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
350    private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY;
351    /** Define the violation message when the minimum count is not reached. */
352    private String minimumMessage;
353    /** Define the violation message when the maximum count is exceeded. */
354    private String maximumMessage;
355
356    /**
357     * Counts of descendant tokens.
358     * Indexed by (token ID - 1) for performance.
359     */
360    private int[] counts = CommonUtil.EMPTY_INT_ARRAY;
361
362    @Override
363    public int[] getAcceptableTokens() {
364        return TokenUtil.getAllTokenIds();
365    }
366
367    @Override
368    public int[] getDefaultTokens() {
369        return getRequiredTokens();
370    }
371
372    @Override
373    public int[] getRequiredTokens() {
374        return CommonUtil.EMPTY_INT_ARRAY;
375    }
376
377    @Override
378    public void visitToken(DetailAST ast) {
379        // reset counts
380        Arrays.fill(counts, 0);
381        countTokens(ast, 0);
382
383        if (sumTokenCounts) {
384            logAsTotal(ast);
385        }
386        else {
387            logAsSeparated(ast);
388        }
389    }
390
391    /**
392     * Log violations for each Token.
393     *
394     * @param ast token
395     */
396    private void logAsSeparated(DetailAST ast) {
397        // name of this token
398        final String name = TokenUtil.getTokenName(ast.getType());
399
400        for (int element : limitedTokens) {
401            final int tokenCount = counts[element - 1];
402            if (tokenCount < minimumNumber) {
403                final String descendantName = TokenUtil.getTokenName(element);
404
405                if (minimumMessage == null) {
406                    minimumMessage = MSG_KEY_MIN;
407                }
408                log(ast,
409                        minimumMessage,
410                        String.valueOf(tokenCount),
411                        String.valueOf(minimumNumber),
412                        name,
413                        descendantName);
414            }
415            if (tokenCount > maximumNumber) {
416                final String descendantName = TokenUtil.getTokenName(element);
417
418                if (maximumMessage == null) {
419                    maximumMessage = MSG_KEY_MAX;
420                }
421                log(ast,
422                        maximumMessage,
423                        String.valueOf(tokenCount),
424                        String.valueOf(maximumNumber),
425                        name,
426                        descendantName);
427            }
428        }
429    }
430
431    /**
432     * Log validation as one violation.
433     *
434     * @param ast current token
435     */
436    private void logAsTotal(DetailAST ast) {
437        // name of this token
438        final String name = TokenUtil.getTokenName(ast.getType());
439
440        int total = 0;
441        for (int element : limitedTokens) {
442            total += counts[element - 1];
443        }
444        if (total < minimumNumber) {
445            if (minimumMessage == null) {
446                minimumMessage = MSG_KEY_SUM_MIN;
447            }
448            log(ast,
449                    minimumMessage,
450                    String.valueOf(total),
451                    String.valueOf(minimumNumber), name);
452        }
453        if (total > maximumNumber) {
454            if (maximumMessage == null) {
455                maximumMessage = MSG_KEY_SUM_MAX;
456            }
457            log(ast,
458                    maximumMessage,
459                    String.valueOf(total),
460                    String.valueOf(maximumNumber), name);
461        }
462    }
463
464    /**
465     * Counts the number of occurrences of descendant tokens.
466     *
467     * @param ast the root token for descendants.
468     * @param depth the maximum depth of the counted descendants.
469     */
470    private void countTokens(DetailAST ast, int depth) {
471        if (depth <= maximumDepth) {
472            // update count
473            if (depth >= minimumDepth) {
474                final int type = ast.getType();
475                if (type <= counts.length) {
476                    counts[type - 1]++;
477                }
478            }
479            DetailAST child = ast.getFirstChild();
480            final int nextDepth = depth + 1;
481            while (child != null) {
482                countTokens(child, nextDepth);
483                child = child.getNextSibling();
484            }
485        }
486    }
487
488    /**
489     * Setter to specify set of tokens with limited occurrences as descendants.
490     *
491     * @param limitedTokensParam tokens to ignore.
492     */
493    public void setLimitedTokens(String... limitedTokensParam) {
494        limitedTokens = new int[limitedTokensParam.length];
495
496        int maxToken = 0;
497        for (int i = 0; i < limitedTokensParam.length; i++) {
498            limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]);
499            if (limitedTokens[i] >= maxToken + 1) {
500                maxToken = limitedTokens[i];
501            }
502        }
503        counts = new int[maxToken];
504    }
505
506    /**
507     * Setter to specify the minimum depth for descendant counts.
508     *
509     * @param minimumDepth the minimum depth for descendant counts.
510     */
511    public void setMinimumDepth(int minimumDepth) {
512        this.minimumDepth = minimumDepth;
513    }
514
515    /**
516     * Setter to specify the maximum depth for descendant counts.
517     *
518     * @param maximumDepth the maximum depth for descendant counts.
519     */
520    public void setMaximumDepth(int maximumDepth) {
521        this.maximumDepth = maximumDepth;
522    }
523
524    /**
525     * Setter to specify a minimum count for descendants.
526     *
527     * @param minimumNumber the minimum count for descendants.
528     */
529    public void setMinimumNumber(int minimumNumber) {
530        this.minimumNumber = minimumNumber;
531    }
532
533    /**
534      * Setter to specify a maximum count for descendants.
535      *
536      * @param maximumNumber the maximum count for descendants.
537      */
538    public void setMaximumNumber(int maximumNumber) {
539        this.maximumNumber = maximumNumber;
540    }
541
542    /**
543     * Setter to define the violation message when the minimum count is not reached.
544     *
545     * @param message the violation message for minimum count not reached.
546     *     Used as a {@code MessageFormat} pattern with arguments
547     *     <ul>
548     *     <li>{0} - token count</li>
549     *     <li>{1} - minimum number</li>
550     *     <li>{2} - name of token</li>
551     *     <li>{3} - name of limited token</li>
552     *     </ul>
553     */
554    public void setMinimumMessage(String message) {
555        minimumMessage = message;
556    }
557
558    /**
559     * Setter to define the violation message when the maximum count is exceeded.
560     *
561     * @param message the violation message for maximum count exceeded.
562     *     Used as a {@code MessageFormat} pattern with arguments
563     *     <ul>
564     *     <li>{0} - token count</li>
565     *     <li>{1} - maximum number</li>
566     *     <li>{2} - name of token</li>
567     *     <li>{3} - name of limited token</li>
568     *     </ul>
569     */
570
571    public void setMaximumMessage(String message) {
572        maximumMessage = message;
573    }
574
575    /**
576     * Setter to control whether the number of tokens found should be calculated
577     * from the sum of the individual token counts.
578     *
579     * @param sum whether to use the sum.
580     */
581    public void setSumTokenCounts(boolean sum) {
582        sumTokenCounts = sum;
583    }
584
585}