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