001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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;
023import java.util.Set;
024
025import antlr.collections.AST;
026
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
030import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
031
032/**
033 * <p>
034 * Checks for restricted tokens beneath other tokens.
035 * </p>
036 * <p>
037 * Examples of how to configure the check:
038 * </p>
039 * <pre>
040 * &lt;!-- String literal equality check --&gt;
041 * &lt;module name="DescendantToken"&gt;
042 *     &lt;property name="tokens" value="EQUAL,NOT_EQUAL"/&gt;
043 *     &lt;property name="limitedTokens" value="STRING_LITERAL"/&gt;
044 *     &lt;property name="maximumNumber" value="0"/&gt;
045 *     &lt;property name="maximumDepth" value="1"/&gt;
046 * &lt;/module&gt;
047 *
048 * &lt;!-- Switch with no default --&gt;
049 * &lt;module name="DescendantToken"&gt;
050 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
051 *     &lt;property name="maximumDepth" value="2"/&gt;
052 *     &lt;property name="limitedTokens" value="LITERAL_DEFAULT"/&gt;
053 *     &lt;property name="minimumNumber" value="1"/&gt;
054 * &lt;/module&gt;
055 *
056 * &lt;!-- Assert statement may have side effects --&gt;
057 * &lt;module name="DescendantToken"&gt;
058 *     &lt;property name="tokens" value="LITERAL_ASSERT"/&gt;
059 *     &lt;property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC,
060 *     POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,
061 *     BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,
062 *     METHOD_CALL"/&gt;
063 *     &lt;property name="maximumNumber" value="0"/&gt;
064 * &lt;/module&gt;
065 *
066 * &lt;!-- Initializer in for performs no setup - use while instead? --&gt;
067 * &lt;module name="DescendantToken"&gt;
068 *     &lt;property name="tokens" value="FOR_INIT"/&gt;
069 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
070 *     &lt;property name="minimumNumber" value="1"/&gt;
071 * &lt;/module&gt;
072 *
073 * &lt;!-- Condition in for performs no check --&gt;
074 * &lt;module name="DescendantToken"&gt;
075 *     &lt;property name="tokens" value="FOR_CONDITION"/&gt;
076 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
077 *     &lt;property name="minimumNumber" value="1"/&gt;
078 * &lt;/module&gt;
079 *
080 * &lt;!-- Switch within switch --&gt;
081 * &lt;module name="DescendantToken"&gt;
082 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
083 *     &lt;property name="limitedTokens" value="LITERAL_SWITCH"/&gt;
084 *     &lt;property name="maximumNumber" value="0"/&gt;
085 *     &lt;property name="minimumDepth" value="1"/&gt;
086 * &lt;/module&gt;
087 *
088 * &lt;!-- Return from within a catch or finally block --&gt;
089 * &lt;module name="DescendantToken"&gt;
090 *     &lt;property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/&gt;
091 *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
092 *     &lt;property name="maximumNumber" value="0"/&gt;
093 * &lt;/module&gt;
094 *
095 * &lt;!-- Try within catch or finally block --&gt;
096 * &lt;module name="DescendantToken"&gt;
097 *     &lt;property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/&gt;
098 *     &lt;property name="limitedTokens" value="LITERAL_TRY"/&gt;
099 *     &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 */
169public class DescendantTokenCheck extends AbstractCheck {
170
171    /**
172     * A key is pointing to the warning message text in "messages.properties"
173     * file.
174     */
175    public static final String MSG_KEY_MIN = "descendant.token.min";
176
177    /**
178     * A key is pointing to the warning message text in "messages.properties"
179     * file.
180     */
181    public static final String MSG_KEY_MAX = "descendant.token.max";
182
183    /**
184     * A key is pointing to the warning message text in "messages.properties"
185     * file.
186     */
187    public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min";
188
189    /**
190     * A key is pointing to the warning message text in "messages.properties"
191     * file.
192     */
193    public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max";
194
195    /** Minimum depth. */
196    private int minimumDepth;
197    /** Maximum depth. */
198    private int maximumDepth = Integer.MAX_VALUE;
199    /** Minimum number. */
200    private int minimumNumber;
201    /** Maximum number. */
202    private int maximumNumber = Integer.MAX_VALUE;
203    /** Whether to sum the number of tokens found. */
204    private boolean sumTokenCounts;
205    /** Limited tokens. */
206    private int[] limitedTokens = CommonUtils.EMPTY_INT_ARRAY;
207    /** Error message when minimum count not reached. */
208    private String minimumMessage;
209    /** Error message when maximum count exceeded. */
210    private String maximumMessage;
211
212    /**
213     * Counts of descendant tokens.
214     * Indexed by (token ID - 1) for performance.
215     */
216    private int[] counts = CommonUtils.EMPTY_INT_ARRAY;
217
218    @Override
219    public int[] getDefaultTokens() {
220        return CommonUtils.EMPTY_INT_ARRAY;
221    }
222
223    @Override
224    public int[] getRequiredTokens() {
225        return CommonUtils.EMPTY_INT_ARRAY;
226    }
227
228    @Override
229    public void visitToken(DetailAST ast) {
230        //reset counts
231        Arrays.fill(counts, 0);
232        countTokens(ast, 0);
233
234        if (sumTokenCounts) {
235            logAsTotal(ast);
236        }
237        else {
238            logAsSeparated(ast);
239        }
240    }
241
242    /**
243     * Log violations for each Token.
244     * @param ast token
245     */
246    private void logAsSeparated(DetailAST ast) {
247        // name of this token
248        final String name = TokenUtils.getTokenName(ast.getType());
249
250        for (int element : limitedTokens) {
251            final int tokenCount = counts[element - 1];
252            if (tokenCount < minimumNumber) {
253                final String descendantName = TokenUtils.getTokenName(element);
254
255                if (minimumMessage == null) {
256                    minimumMessage = MSG_KEY_MIN;
257                }
258                log(ast.getLineNo(), ast.getColumnNo(),
259                        minimumMessage,
260                        String.valueOf(tokenCount),
261                        String.valueOf(minimumNumber),
262                        name,
263                        descendantName);
264            }
265            if (tokenCount > maximumNumber) {
266                final String descendantName = TokenUtils.getTokenName(element);
267
268                if (maximumMessage == null) {
269                    maximumMessage = MSG_KEY_MAX;
270                }
271                log(ast.getLineNo(), ast.getColumnNo(),
272                        maximumMessage,
273                        String.valueOf(tokenCount),
274                        String.valueOf(maximumNumber),
275                        name,
276                        descendantName);
277            }
278        }
279    }
280
281    /**
282     * Log validation as one violation.
283     * @param ast current token
284     */
285    private void logAsTotal(DetailAST ast) {
286        // name of this token
287        final String name = TokenUtils.getTokenName(ast.getType());
288
289        int total = 0;
290        for (int element : limitedTokens) {
291            total += counts[element - 1];
292        }
293        if (total < minimumNumber) {
294            if (minimumMessage == null) {
295                minimumMessage = MSG_KEY_SUM_MIN;
296            }
297            log(ast.getLineNo(), ast.getColumnNo(),
298                    minimumMessage,
299                    String.valueOf(total),
300                    String.valueOf(minimumNumber), name);
301        }
302        if (total > maximumNumber) {
303            if (maximumMessage == null) {
304                maximumMessage = MSG_KEY_SUM_MAX;
305            }
306            log(ast.getLineNo(), ast.getColumnNo(),
307                    maximumMessage,
308                    String.valueOf(total),
309                    String.valueOf(maximumNumber), name);
310        }
311    }
312
313    /**
314     * Counts the number of occurrences of descendant tokens.
315     * @param ast the root token for descendants.
316     * @param depth the maximum depth of the counted descendants.
317     */
318    private void countTokens(AST ast, int depth) {
319        if (depth <= maximumDepth) {
320            //update count
321            if (depth >= minimumDepth) {
322                final int type = ast.getType();
323                if (type <= counts.length) {
324                    counts[type - 1]++;
325                }
326            }
327            AST child = ast.getFirstChild();
328            final int nextDepth = depth + 1;
329            while (child != null) {
330                countTokens(child, nextDepth);
331                child = child.getNextSibling();
332            }
333        }
334    }
335
336    @Override
337    public int[] getAcceptableTokens() {
338        // Any tokens set by property 'tokens' are acceptable
339        final Set<String> tokenNames = getTokenNames();
340        final int[] result = new int[tokenNames.size()];
341        int index = 0;
342        for (String name : tokenNames) {
343            result[index] = TokenUtils.getTokenId(name);
344            index++;
345        }
346        return result;
347    }
348
349    /**
350     * Sets the tokens which occurrence as descendant is limited.
351     * @param limitedTokensParam - list of tokens to ignore.
352     */
353    public void setLimitedTokens(String... limitedTokensParam) {
354        limitedTokens = new int[limitedTokensParam.length];
355
356        int maxToken = 0;
357        for (int i = 0; i < limitedTokensParam.length; i++) {
358            limitedTokens[i] = TokenUtils.getTokenId(limitedTokensParam[i]);
359            if (limitedTokens[i] > maxToken) {
360                maxToken = limitedTokens[i];
361            }
362        }
363        counts = new int[maxToken];
364    }
365
366    /**
367     * Sets the minimum depth for descendant counts.
368     * @param minimumDepth the minimum depth for descendant counts.
369     */
370    public void setMinimumDepth(int minimumDepth) {
371        this.minimumDepth = minimumDepth;
372    }
373
374    /**
375     * Sets the maximum depth for descendant counts.
376     * @param maximumDepth the maximum depth for descendant counts.
377     */
378    public void setMaximumDepth(int maximumDepth) {
379        this.maximumDepth = maximumDepth;
380    }
381
382    /**
383     * Sets a minimum count for descendants.
384     * @param minimumNumber the minimum count for descendants.
385     */
386    public void setMinimumNumber(int minimumNumber) {
387        this.minimumNumber = minimumNumber;
388    }
389
390    /**
391      * Sets a maximum count for descendants.
392      * @param maximumNumber the maximum count for descendants.
393      */
394    public void setMaximumNumber(int maximumNumber) {
395        this.maximumNumber = maximumNumber;
396    }
397
398    /**
399     * Sets the error message for minimum count not reached.
400     * @param message the error message for minimum count not reached.
401     *     Used as a {@code MessageFormat} pattern with arguments
402     *     <ul>
403     *     <li>{0} - token count</li>
404     *     <li>{1} - minimum number</li>
405     *     <li>{2} - name of token</li>
406     *     <li>{3} - name of limited token</li>
407     *     </ul>
408     */
409    public void setMinimumMessage(String message) {
410        minimumMessage = message;
411    }
412
413    /**
414     * Sets the error message for maximum count exceeded.
415     * @param message the error message for maximum count exceeded.
416     *     Used as a {@code MessageFormat} pattern with arguments
417     * <ul>
418     * <li>{0} - token count</li>
419     * <li>{1} - maximum number</li>
420     * <li>{2} - name of token</li>
421     * <li>{3} - name of limited token</li>
422     * </ul>
423     */
424
425    public void setMaximumMessage(String message) {
426        maximumMessage = message;
427    }
428
429    /**
430     * Sets whether to use the sum of the tokens found, rather than the
431     * individual counts.
432     * @param sum whether to use the sum.
433     */
434    public void setSumTokenCounts(boolean sum) {
435        sumTokenCounts = sum;
436    }
437}