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