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