001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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 org.apache.commons.lang3.ArrayUtils;
026
027import antlr.collections.AST;
028
029import com.puppycrawl.tools.checkstyle.api.Check;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
032
033/**
034 * <p>
035 * Checks for restricted tokens beneath other tokens.
036 * </p>
037 * <p>
038 * Examples of how to configure the check:
039 * </p>
040 * <pre>
041 * &lt;!-- String literal equality check --&gt;
042 * &lt;module name="DescendantToken"&gt;
043 *     &lt;property name="tokens" value="EQUAL,NOT_EQUAL"/&gt;
044 *     &lt;property name="limitedTokens" value="STRING_LITERAL"/&gt;
045 *     &lt;property name="maximumNumber" value="0"/&gt;
046 *     &lt;property name="maximumDepth" value="1"/&gt;
047 * &lt;/module&gt;
048 *
049 * &lt;!-- Switch with no default --&gt;
050 * &lt;module name="DescendantToken"&gt;
051 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
052 *     &lt;property name="maximumDepth" value="2"/&gt;
053 *     &lt;property name="limitedTokens" value="LITERAL_DEFAULT"/&gt;
054 *     &lt;property name="minimumNumber" value="1"/&gt;
055 * &lt;/module&gt;
056 *
057 * &lt;!-- Assert statement may have side effects --&gt;
058 * &lt;module name="DescendantToken"&gt;
059 *     &lt;property name="tokens" value="LITERAL_ASSERT"/&gt;
060 *     &lt;property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC,
061 *     POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,
062 *     BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,
063 *     METHOD_CALL"/&gt;
064 *     &lt;property name="maximumNumber" value="0"/&gt;
065 * &lt;/module&gt;
066 *
067 * &lt;!-- Initializer in for performs no setup - use while instead? --&gt;
068 * &lt;module name="DescendantToken"&gt;
069 *     &lt;property name="tokens" value="FOR_INIT"/&gt;
070 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
071 *     &lt;property name="minimumNumber" value="1"/&gt;
072 * &lt;/module&gt;
073 *
074 * &lt;!-- Condition in for performs no check --&gt;
075 * &lt;module name="DescendantToken"&gt;
076 *     &lt;property name="tokens" value="FOR_CONDITION"/&gt;
077 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
078 *     &lt;property name="minimumNumber" value="1"/&gt;
079 * &lt;/module&gt;
080 *
081 * &lt;!-- Switch within switch --&gt;
082 * &lt;module name="DescendantToken"&gt;
083 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
084 *     &lt;property name="limitedTokens" value="LITERAL_SWITCH"/&gt;
085 *     &lt;property name="maximumNumber" value="0"/&gt;
086 *     &lt;property name="minimumDepth" value="1"/&gt;
087 * &lt;/module&gt;
088 *
089 * &lt;!-- Return from within a catch or finally block --&gt;
090 * &lt;module name="DescendantToken"&gt;
091 *     &lt;property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/&gt;
092 *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
093 *     &lt;property name="maximumNumber" value="0"/&gt;
094 * &lt;/module&gt;
095 *
096 * &lt;!-- Try within catch or finally block --&gt;
097 * &lt;module name="DescendantToken"&gt;
098 *     &lt;property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/&gt;
099 *     &lt;property name="limitedTokens" value="LITERAL_TRY"/&gt;
100 *     &lt;property name="maximumNumber" value="0"/&gt;
101 * &lt;/module&gt;
102 *
103 * &lt;!-- Too many cases within a switch --&gt;
104 * &lt;module name="DescendantToken"&gt;
105 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
106 *     &lt;property name="limitedTokens" value="LITERAL_CASE"/&gt;
107 *     &lt;property name="maximumDepth" value="2"/&gt;
108 *     &lt;property name="maximumNumber" value="10"/&gt;
109 * &lt;/module&gt;
110 *
111 * &lt;!-- Too many local variables within a method --&gt;
112 * &lt;module name="DescendantToken"&gt;
113 *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
114 *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
115 *     &lt;property name="maximumDepth" value="2"/&gt;
116 *     &lt;property name="maximumNumber" value="10"/&gt;
117 * &lt;/module&gt;
118 *
119 * &lt;!-- Too many returns from within a method --&gt;
120 * &lt;module name="DescendantToken"&gt;
121 *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
122 *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
123 *     &lt;property name="maximumNumber" value="3"/&gt;
124 * &lt;/module&gt;
125 *
126 * &lt;!-- Too many fields within an interface --&gt;
127 * &lt;module name="DescendantToken"&gt;
128 *     &lt;property name="tokens" value="INTERFACE_DEF"/&gt;
129 *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
130 *     &lt;property name="maximumDepth" value="2"/&gt;
131 *     &lt;property name="maximumNumber" value="0"/&gt;
132 * &lt;/module&gt;
133 *
134 * &lt;!-- Limit the number of exceptions a method can throw --&gt;
135 * &lt;module name="DescendantToken"&gt;
136 *     &lt;property name="tokens" value="LITERAL_THROWS"/&gt;
137 *     &lt;property name="limitedTokens" value="IDENT"/&gt;
138 *     &lt;property name="maximumNumber" value="1"/&gt;
139 * &lt;/module&gt;
140 *
141 * &lt;!-- Limit the number of expressions in a method --&gt;
142 * &lt;module name="DescendantToken"&gt;
143 *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
144 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
145 *     &lt;property name="maximumNumber" value="200"/&gt;
146 * &lt;/module&gt;
147 *
148 * &lt;!-- Disallow empty statements --&gt;
149 * &lt;module name="DescendantToken"&gt;
150 *     &lt;property name="tokens" value="EMPTY_STAT"/&gt;
151 *     &lt;property name="limitedTokens" value="EMPTY_STAT"/&gt;
152 *     &lt;property name="maximumNumber" value="0"/&gt;
153 *     &lt;property name="maximumDepth" value="0"/&gt;
154 *     &lt;property name="maximumMessage"
155 *         value="Empty statement is not allowed."/&gt;
156 * &lt;/module&gt;
157 *
158 * &lt;!-- Too many fields within a class --&gt;
159 * &lt;module name="DescendantToken"&gt;
160 *     &lt;property name="tokens" value="CLASS_DEF"/&gt;
161 *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
162 *     &lt;property name="maximumDepth" value="2"/&gt;
163 *     &lt;property name="maximumNumber" value="10"/&gt;
164 * &lt;/module&gt;
165 * </pre>
166 *
167 * @author Tim Tyler &lt;tim@tt1.org&gt;
168 * @author Rick Giles
169 */
170public class DescendantTokenCheck extends Check {
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 = ArrayUtils.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 = ArrayUtils.EMPTY_INT_ARRAY;
218
219    @Override
220    public int[] getDefaultTokens() {
221        return ArrayUtils.EMPTY_INT_ARRAY;
222    }
223
224    @Override
225    public int[] getRequiredTokens() {
226        return ArrayUtils.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) {
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}