001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.FileStatefulCheck;
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 */
169@FileStatefulCheck
170public 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
439}