1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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  import java.util.Set;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
29  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30  
31  /**
32   * <p>
33   * Checks for restricted tokens beneath other tokens.
34   * </p>
35   * <p>
36   * Examples of how to configure the check:
37   * </p>
38   * <pre>
39   * &lt;!-- String literal equality check --&gt;
40   * &lt;module name="DescendantToken"&gt;
41   *     &lt;property name="tokens" value="EQUAL,NOT_EQUAL"/&gt;
42   *     &lt;property name="limitedTokens" value="STRING_LITERAL"/&gt;
43   *     &lt;property name="maximumNumber" value="0"/&gt;
44   *     &lt;property name="maximumDepth" value="1"/&gt;
45   * &lt;/module&gt;
46   *
47   * &lt;!-- Switch with no default --&gt;
48   * &lt;module name="DescendantToken"&gt;
49   *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
50   *     &lt;property name="maximumDepth" value="2"/&gt;
51   *     &lt;property name="limitedTokens" value="LITERAL_DEFAULT"/&gt;
52   *     &lt;property name="minimumNumber" value="1"/&gt;
53   * &lt;/module&gt;
54   *
55   * &lt;!-- Assert statement may have side effects --&gt;
56   * &lt;module name="DescendantToken"&gt;
57   *     &lt;property name="tokens" value="LITERAL_ASSERT"/&gt;
58   *     &lt;property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC,
59   *     POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,
60   *     BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,
61   *     METHOD_CALL"/&gt;
62   *     &lt;property name="maximumNumber" value="0"/&gt;
63   * &lt;/module&gt;
64   *
65   * &lt;!-- Initializer in for performs no setup - use while instead? --&gt;
66   * &lt;module name="DescendantToken"&gt;
67   *     &lt;property name="tokens" value="FOR_INIT"/&gt;
68   *     &lt;property name="limitedTokens" value="EXPR"/&gt;
69   *     &lt;property name="minimumNumber" value="1"/&gt;
70   * &lt;/module&gt;
71   *
72   * &lt;!-- Condition in for performs no check --&gt;
73   * &lt;module name="DescendantToken"&gt;
74   *     &lt;property name="tokens" value="FOR_CONDITION"/&gt;
75   *     &lt;property name="limitedTokens" value="EXPR"/&gt;
76   *     &lt;property name="minimumNumber" value="1"/&gt;
77   * &lt;/module&gt;
78   *
79   * &lt;!-- Switch within switch --&gt;
80   * &lt;module name="DescendantToken"&gt;
81   *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
82   *     &lt;property name="limitedTokens" value="LITERAL_SWITCH"/&gt;
83   *     &lt;property name="maximumNumber" value="0"/&gt;
84   *     &lt;property name="minimumDepth" value="1"/&gt;
85   * &lt;/module&gt;
86   *
87   * &lt;!-- Return from within a catch or finally block --&gt;
88   * &lt;module name="DescendantToken"&gt;
89   *     &lt;property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/&gt;
90   *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
91   *     &lt;property name="maximumNumber" value="0"/&gt;
92   * &lt;/module&gt;
93   *
94   * &lt;!-- Try within catch or finally block --&gt;
95   * &lt;module name="DescendantToken"&gt;
96   *     &lt;property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/&gt;
97   *     &lt;property name="limitedTokens" value="LITERAL_TRY"/&gt;
98   *     &lt;property name="maximumNumber" value="0"/&gt;
99   * &lt;/module&gt;
100  *
101  * &lt;!-- Too many cases within a switch --&gt;
102  * &lt;module name="DescendantToken"&gt;
103  *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
104  *     &lt;property name="limitedTokens" value="LITERAL_CASE"/&gt;
105  *     &lt;property name="maximumDepth" value="2"/&gt;
106  *     &lt;property name="maximumNumber" value="10"/&gt;
107  * &lt;/module&gt;
108  *
109  * &lt;!-- Too many local variables within a method --&gt;
110  * &lt;module name="DescendantToken"&gt;
111  *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
112  *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
113  *     &lt;property name="maximumDepth" value="2"/&gt;
114  *     &lt;property name="maximumNumber" value="10"/&gt;
115  * &lt;/module&gt;
116  *
117  * &lt;!-- Too many returns from within a method --&gt;
118  * &lt;module name="DescendantToken"&gt;
119  *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
120  *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
121  *     &lt;property name="maximumNumber" value="3"/&gt;
122  * &lt;/module&gt;
123  *
124  * &lt;!-- Too many fields within an interface --&gt;
125  * &lt;module name="DescendantToken"&gt;
126  *     &lt;property name="tokens" value="INTERFACE_DEF"/&gt;
127  *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
128  *     &lt;property name="maximumDepth" value="2"/&gt;
129  *     &lt;property name="maximumNumber" value="0"/&gt;
130  * &lt;/module&gt;
131  *
132  * &lt;!-- Limit the number of exceptions a method can throw --&gt;
133  * &lt;module name="DescendantToken"&gt;
134  *     &lt;property name="tokens" value="LITERAL_THROWS"/&gt;
135  *     &lt;property name="limitedTokens" value="IDENT"/&gt;
136  *     &lt;property name="maximumNumber" value="1"/&gt;
137  * &lt;/module&gt;
138  *
139  * &lt;!-- Limit the number of expressions in a method --&gt;
140  * &lt;module name="DescendantToken"&gt;
141  *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
142  *     &lt;property name="limitedTokens" value="EXPR"/&gt;
143  *     &lt;property name="maximumNumber" value="200"/&gt;
144  * &lt;/module&gt;
145  *
146  * &lt;!-- Disallow empty statements --&gt;
147  * &lt;module name="DescendantToken"&gt;
148  *     &lt;property name="tokens" value="EMPTY_STAT"/&gt;
149  *     &lt;property name="limitedTokens" value="EMPTY_STAT"/&gt;
150  *     &lt;property name="maximumNumber" value="0"/&gt;
151  *     &lt;property name="maximumDepth" value="0"/&gt;
152  *     &lt;property name="maximumMessage"
153  *         value="Empty statement is not allowed."/&gt;
154  * &lt;/module&gt;
155  *
156  * &lt;!-- Too many fields within a class --&gt;
157  * &lt;module name="DescendantToken"&gt;
158  *     &lt;property name="tokens" value="CLASS_DEF"/&gt;
159  *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
160  *     &lt;property name="maximumDepth" value="2"/&gt;
161  *     &lt;property name="maximumNumber" value="10"/&gt;
162  * &lt;/module&gt;
163  * </pre>
164  *
165  */
166 @FileStatefulCheck
167 public class DescendantTokenCheck extends AbstractCheck {
168 
169     /**
170      * A key is pointing to the warning message text in "messages.properties"
171      * file.
172      */
173     public static final String MSG_KEY_MIN = "descendant.token.min";
174 
175     /**
176      * A key is pointing to the warning message text in "messages.properties"
177      * file.
178      */
179     public static final String MSG_KEY_MAX = "descendant.token.max";
180 
181     /**
182      * A key is pointing to the warning message text in "messages.properties"
183      * file.
184      */
185     public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min";
186 
187     /**
188      * A key is pointing to the warning message text in "messages.properties"
189      * file.
190      */
191     public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max";
192 
193     /** Minimum depth. */
194     private int minimumDepth;
195     /** Maximum depth. */
196     private int maximumDepth = Integer.MAX_VALUE;
197     /** Minimum number. */
198     private int minimumNumber;
199     /** Maximum number. */
200     private int maximumNumber = Integer.MAX_VALUE;
201     /** Whether to sum the number of tokens found. */
202     private boolean sumTokenCounts;
203     /** Limited tokens. */
204     private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY;
205     /** Error message when minimum count not reached. */
206     private String minimumMessage;
207     /** Error message when maximum count exceeded. */
208     private String maximumMessage;
209 
210     /**
211      * Counts of descendant tokens.
212      * Indexed by (token ID - 1) for performance.
213      */
214     private int[] counts = CommonUtil.EMPTY_INT_ARRAY;
215 
216     @Override
217     public int[] getDefaultTokens() {
218         return getRequiredTokens();
219     }
220 
221     @Override
222     public int[] getRequiredTokens() {
223         return CommonUtil.EMPTY_INT_ARRAY;
224     }
225 
226     @Override
227     public void visitToken(DetailAST ast) {
228         //reset counts
229         Arrays.fill(counts, 0);
230         countTokens(ast, 0);
231 
232         if (sumTokenCounts) {
233             logAsTotal(ast);
234         }
235         else {
236             logAsSeparated(ast);
237         }
238     }
239 
240     /**
241      * Log violations for each Token.
242      * @param ast token
243      */
244     private void logAsSeparated(DetailAST ast) {
245         // name of this token
246         final String name = TokenUtil.getTokenName(ast.getType());
247 
248         for (int element : limitedTokens) {
249             final int tokenCount = counts[element - 1];
250             if (tokenCount < minimumNumber) {
251                 final String descendantName = TokenUtil.getTokenName(element);
252 
253                 if (minimumMessage == null) {
254                     minimumMessage = MSG_KEY_MIN;
255                 }
256                 log(ast,
257                         minimumMessage,
258                         String.valueOf(tokenCount),
259                         String.valueOf(minimumNumber),
260                         name,
261                         descendantName);
262             }
263             if (tokenCount > maximumNumber) {
264                 final String descendantName = TokenUtil.getTokenName(element);
265 
266                 if (maximumMessage == null) {
267                     maximumMessage = MSG_KEY_MAX;
268                 }
269                 log(ast,
270                         maximumMessage,
271                         String.valueOf(tokenCount),
272                         String.valueOf(maximumNumber),
273                         name,
274                         descendantName);
275             }
276         }
277     }
278 
279     /**
280      * Log validation as one violation.
281      * @param ast current token
282      */
283     private void logAsTotal(DetailAST ast) {
284         // name of this token
285         final String name = TokenUtil.getTokenName(ast.getType());
286 
287         int total = 0;
288         for (int element : limitedTokens) {
289             total += counts[element - 1];
290         }
291         if (total < minimumNumber) {
292             if (minimumMessage == null) {
293                 minimumMessage = MSG_KEY_SUM_MIN;
294             }
295             log(ast,
296                     minimumMessage,
297                     String.valueOf(total),
298                     String.valueOf(minimumNumber), name);
299         }
300         if (total > maximumNumber) {
301             if (maximumMessage == null) {
302                 maximumMessage = MSG_KEY_SUM_MAX;
303             }
304             log(ast,
305                     maximumMessage,
306                     String.valueOf(total),
307                     String.valueOf(maximumNumber), name);
308         }
309     }
310 
311     /**
312      * Counts the number of occurrences of descendant tokens.
313      * @param ast the root token for descendants.
314      * @param depth the maximum depth of the counted descendants.
315      */
316     private void countTokens(DetailAST ast, int depth) {
317         if (depth <= maximumDepth) {
318             //update count
319             if (depth >= minimumDepth) {
320                 final int type = ast.getType();
321                 if (type <= counts.length) {
322                     counts[type - 1]++;
323                 }
324             }
325             DetailAST child = ast.getFirstChild();
326             final int nextDepth = depth + 1;
327             while (child != null) {
328                 countTokens(child, nextDepth);
329                 child = child.getNextSibling();
330             }
331         }
332     }
333 
334     @Override
335     public int[] getAcceptableTokens() {
336         // Any tokens set by property 'tokens' are acceptable
337         final Set<String> tokenNames = getTokenNames();
338         final int[] result = new int[tokenNames.size()];
339         int index = 0;
340         for (String name : tokenNames) {
341             result[index] = TokenUtil.getTokenId(name);
342             index++;
343         }
344         return result;
345     }
346 
347     /**
348      * Sets the tokens which occurrence as descendant is limited.
349      * @param limitedTokensParam - list of tokens to ignore.
350      */
351     public void setLimitedTokens(String... limitedTokensParam) {
352         limitedTokens = new int[limitedTokensParam.length];
353 
354         int maxToken = 0;
355         for (int i = 0; i < limitedTokensParam.length; i++) {
356             limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]);
357             if (limitedTokens[i] >= maxToken + 1) {
358                 maxToken = limitedTokens[i];
359             }
360         }
361         counts = new int[maxToken];
362     }
363 
364     /**
365      * Sets the minimum depth for descendant counts.
366      * @param minimumDepth the minimum depth for descendant counts.
367      */
368     public void setMinimumDepth(int minimumDepth) {
369         this.minimumDepth = minimumDepth;
370     }
371 
372     /**
373      * Sets the maximum depth for descendant counts.
374      * @param maximumDepth the maximum depth for descendant counts.
375      */
376     public void setMaximumDepth(int maximumDepth) {
377         this.maximumDepth = maximumDepth;
378     }
379 
380     /**
381      * Sets a minimum count for descendants.
382      * @param minimumNumber the minimum count for descendants.
383      */
384     public void setMinimumNumber(int minimumNumber) {
385         this.minimumNumber = minimumNumber;
386     }
387 
388     /**
389       * Sets a maximum count for descendants.
390       * @param maximumNumber the maximum count for descendants.
391       */
392     public void setMaximumNumber(int maximumNumber) {
393         this.maximumNumber = maximumNumber;
394     }
395 
396     /**
397      * Sets the error message for minimum count not reached.
398      * @param message the error message for minimum count not reached.
399      *     Used as a {@code MessageFormat} pattern with arguments
400      *     <ul>
401      *     <li>{0} - token count</li>
402      *     <li>{1} - minimum number</li>
403      *     <li>{2} - name of token</li>
404      *     <li>{3} - name of limited token</li>
405      *     </ul>
406      */
407     public void setMinimumMessage(String message) {
408         minimumMessage = message;
409     }
410 
411     /**
412      * Sets the error message for maximum count exceeded.
413      * @param message the error message for maximum count exceeded.
414      *     Used as a {@code MessageFormat} pattern with arguments
415      * <ul>
416      * <li>{0} - token count</li>
417      * <li>{1} - maximum number</li>
418      * <li>{2} - name of token</li>
419      * <li>{3} - name of limited token</li>
420      * </ul>
421      */
422 
423     public void setMaximumMessage(String message) {
424         maximumMessage = message;
425     }
426 
427     /**
428      * Sets whether to use the sum of the tokens found, rather than the
429      * individual counts.
430      * @param sum whether to use the sum.
431      */
432     public void setSumTokenCounts(boolean sum) {
433         sumTokenCounts = sum;
434     }
435 
436 }