001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks;
020
021import antlr.collections.AST;
022import com.puppycrawl.tools.checkstyle.api.Check;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import java.util.Arrays;
026import java.util.Set;
027
028/**
029 * <p>
030 * Checks for restricted tokens beneath other tokens.
031 * </p>
032 * <p>
033 * Examples of how to configure the check:
034 * </p>
035 * <pre>
036 * &lt;!-- String literal equality check --&gt;
037 * &lt;module name="DescendantToken"&gt;
038 *     &lt;property name="tokens" value="EQUAL,NOT_EQUAL"/&gt;
039 *     &lt;property name="limitedTokens" value="STRING_LITERAL"/&gt;
040 *     &lt;property name="maximumNumber" value="0"/&gt;
041 *     &lt;property name="maximumDepth" value="1"/&gt;
042 * &lt;/module&gt;
043 *
044 * &lt;!-- Switch with no default --&gt;
045 * &lt;module name="DescendantToken"&gt;
046 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
047 *     &lt;property name="maximumDepth" value="2"/&gt;
048 *     &lt;property name="limitedTokens" value="LITERAL_DEFAULT"/&gt;
049 *     &lt;property name="minimumNumber" value="1"/&gt;
050 * &lt;/module&gt;
051 *
052 * &lt;!-- Assert statement may have side effects --&gt;
053 * &lt;module name="DescendantToken"&gt;
054 *     &lt;property name="tokens" value="LITERAL_ASSERT"/&gt;
055 *     &lt;property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC,
056 *     POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,
057 *     BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,
058 *     METHOD_CALL"/&gt;
059 *     &lt;property name="maximumNumber" value="0"/&gt;
060 * &lt;/module&gt;
061 *
062 * &lt;!-- Initialiser in for performs no setup - use while instead? --&gt;
063 * &lt;module name="DescendantToken"&gt;
064 *     &lt;property name="tokens" value="FOR_INIT"/&gt;
065 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
066 *     &lt;property name="minimumNumber" value="1"/&gt;
067 * &lt;/module&gt;
068 *
069 * &lt;!-- Condition in for performs no check --&gt;
070 * &lt;module name="DescendantToken"&gt;
071 *     &lt;property name="tokens" value="FOR_CONDITION"/&gt;
072 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
073 *     &lt;property name="minimumNumber" value="1"/&gt;
074 * &lt;/module&gt;
075 *
076 * &lt;!-- Switch within switch --&gt;
077 * &lt;module name="DescendantToken"&gt;
078 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
079 *     &lt;property name="limitedTokens" value="LITERAL_SWITCH"/&gt;
080 *     &lt;property name="maximumNumber" value="0"/&gt;
081 *     &lt;property name="minimumDepth" value="1"/&gt;
082 * &lt;/module&gt;
083 *
084 * &lt;!-- Return from within a catch or finally block --&gt;
085 * &lt;module name="DescendantToken"&gt;
086 *     &lt;property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/&gt;
087 *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
088 *     &lt;property name="maximumNumber" value="0"/&gt;
089 * &lt;/module&gt;
090 *
091 * &lt;!-- Try within catch or finally block --&gt;
092 * &lt;module name="DescendantToken"&gt;
093 *     &lt;property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/&gt;
094 *     &lt;property name="limitedTokens" value="LITERAL_TRY"/&gt;
095 *     &lt;property name="maximumNumber" value="0"/&gt;
096 * &lt;/module&gt;
097 *
098 * &lt;!-- Too many cases within a switch --&gt;
099 * &lt;module name="DescendantToken"&gt;
100 *     &lt;property name="tokens" value="LITERAL_SWITCH"/&gt;
101 *     &lt;property name="limitedTokens" value="LITERAL_CASE"/&gt;
102 *     &lt;property name="maximumDepth" value="2"/&gt;
103 *     &lt;property name="maximumNumber" value="10"/&gt;
104 * &lt;/module&gt;
105 *
106 * &lt;!-- Too many local variables within a method --&gt;
107 * &lt;module name="DescendantToken"&gt;
108 *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
109 *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
110 *     &lt;property name="maximumDepth" value="2"/&gt;
111 *     &lt;property name="maximumNumber" value="10"/&gt;
112 * &lt;/module&gt;
113 *
114 * &lt;!-- Too many returns from within a method --&gt;
115 * &lt;module name="DescendantToken"&gt;
116 *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
117 *     &lt;property name="limitedTokens" value="LITERAL_RETURN"/&gt;
118 *     &lt;property name="maximumNumber" value="3"/&gt;
119 * &lt;/module&gt;
120 *
121 * &lt;!-- Too many fields within an interface --&gt;
122 * &lt;module name="DescendantToken"&gt;
123 *     &lt;property name="tokens" value="INTERFACE_DEF"/&gt;
124 *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
125 *     &lt;property name="maximumDepth" value="2"/&gt;
126 *     &lt;property name="maximumNumber" value="0"/&gt;
127 * &lt;/module&gt;
128 *
129 * &lt;!-- Limit the number of exceptions a method can throw --&gt;
130 * &lt;module name="DescendantToken"&gt;
131 *     &lt;property name="tokens" value="LITERAL_THROWS"/&gt;
132 *     &lt;property name="limitedTokens" value="IDENT"/&gt;
133 *     &lt;property name="maximumNumber" value="1"/&gt;
134 * &lt;/module&gt;
135 *
136 * &lt;!-- Limit the number of expressions in a method --&gt;
137 * &lt;module name="DescendantToken"&gt;
138 *     &lt;property name="tokens" value="METHOD_DEF"/&gt;
139 *     &lt;property name="limitedTokens" value="EXPR"/&gt;
140 *     &lt;property name="maximumNumber" value="200"/&gt;
141 * &lt;/module&gt;
142 *
143 * &lt;!-- Disallow empty statements --&gt;
144 * &lt;module name="DescendantToken"&gt;
145 *     &lt;property name="tokens" value="EMPTY_STAT"/&gt;
146 *     &lt;property name="limitedTokens" value="EMPTY_STAT"/&gt;
147 *     &lt;property name="maximumNumber" value="0"/&gt;
148 *     &lt;property name="maximumDepth" value="0"/&gt;
149 *     &lt;property name="maximumMessage"
150 *         value="Empty statement is not allowed."/&gt;
151 * &lt;/module&gt;
152 *
153 * &lt;!-- Too many fields within a class --&gt;
154 * &lt;module name="DescendantToken"&gt;
155 *     &lt;property name="tokens" value="CLASS_DEF"/&gt;
156 *     &lt;property name="limitedTokens" value="VARIABLE_DEF"/&gt;
157 *     &lt;property name="maximumDepth" value="2"/&gt;
158 *     &lt;property name="maximumNumber" value="10"/&gt;
159 * &lt;/module&gt;
160 * </pre>
161 *
162 * @author Tim Tyler &lt;tim@tt1.org&gt;
163 * @author Rick Giles
164 */
165public class DescendantTokenCheck extends Check
166{
167     /** minimum  depth */
168    private int minimumDepth;
169    /** maximum depth */
170    private int maximumDepth = Integer.MAX_VALUE;
171    /** minimum number */
172    private int minimumNumber;
173    /** maximum number */
174    private int maximumNumber = Integer.MAX_VALUE;
175    /** Whether to sum the number of tokens found. */
176    private boolean sumTokenCounts;
177    /** limited tokens */
178    private int[] limitedTokens = new int[0];
179    /** error message when minimum count not reached */
180    private String minimumMessage;
181    /** error message when maximum count exceeded */
182    private String maximumMessage;
183
184    /**
185     * Counts of descendant tokens.
186     * Indexed by (token ID - 1) for performance.
187     */
188    private int[] counts = new int[0];
189
190    @Override
191    public int[] getDefaultTokens()
192    {
193        return new int[0];
194    }
195
196    @Override
197    public void visitToken(DetailAST ast)
198    {
199        //reset counts
200        Arrays.fill(counts, 0);
201        countTokens(ast, 0);
202
203        // name of this token
204        final String name = TokenTypes.getTokenName(ast.getType());
205
206        if (sumTokenCounts) {
207            int total = 0;
208            for (int element : limitedTokens) {
209                total += counts[element - 1];
210            }
211            if (total < minimumNumber) {
212                log(ast.getLineNo(), ast.getColumnNo(),
213                        (null == minimumMessage) ? "descendant.token.sum.min"
214                                : minimumMessage,
215                        String.valueOf(total),
216                        String.valueOf(minimumNumber), name);
217            }
218            if (total > maximumNumber) {
219                log(ast.getLineNo(), ast.getColumnNo(),
220                        (null == maximumMessage) ? "descendant.token.sum.max"
221                                : maximumMessage,
222                        String.valueOf(total),
223                        String.valueOf(maximumNumber),
224                        name);
225            }
226        }
227        else {
228            for (int element : limitedTokens) {
229                final int tokenCount = counts[element - 1];
230                if (tokenCount < minimumNumber) {
231                    final String descendantName = TokenTypes
232                            .getTokenName(element);
233                    log(ast.getLineNo(), ast.getColumnNo(),
234                            (null == minimumMessage) ? "descendant.token.min"
235                                    : minimumMessage,
236                            String.valueOf(tokenCount),
237                            String.valueOf(minimumNumber),
238                            name,
239                            descendantName);
240                }
241                if (tokenCount > maximumNumber) {
242                    final String descendantName = TokenTypes
243                            .getTokenName(element);
244                    log(ast.getLineNo(), ast.getColumnNo(),
245                            (null == maximumMessage) ? "descendant.token.max"
246                                    : maximumMessage,
247                            String.valueOf(tokenCount),
248                            String.valueOf(maximumNumber),
249                            name,
250                            descendantName);
251                }
252            }
253        }
254    }
255
256    /**
257     * Counts the number of occurrences of descendant tokens.
258     * @param ast the root token for descendants.
259     * @param depth the maximum depth of the counted descendants.
260     */
261    private void countTokens(AST ast, int depth)
262    {
263        if (depth <= maximumDepth) {
264            //update count
265            if (depth >= minimumDepth) {
266                final int type = ast.getType();
267                if (type <= counts.length) {
268                    counts[type - 1]++;
269                }
270            }
271            AST child = ast.getFirstChild();
272            final int nextDepth = depth + 1;
273            while (child != null) {
274                countTokens(child, nextDepth);
275                child = child.getNextSibling();
276            }
277        }
278    }
279
280    @Override
281    public int[] getAcceptableTokens()
282    {
283        // Any tokens set by property 'tokens' are acceptable
284        final Set<String> tokenNames = getTokenNames();
285        final int[] result = new int[tokenNames.size()];
286        int i = 0;
287        for (String name : tokenNames) {
288            result[i++] = TokenTypes.getTokenId(name);
289        }
290        return result;
291    }
292
293    /**
294     * Sets the tokens which occurance as descendant is limited.
295     * @param limitedTokensParam - list of tokens to ignore.
296     */
297    public void setLimitedTokens(String[] limitedTokensParam)
298    {
299        limitedTokens = new int[limitedTokensParam.length];
300
301        int maxToken = 0;
302        for (int i = 0; i < limitedTokensParam.length; i++) {
303            limitedTokens[i] = TokenTypes.getTokenId(limitedTokensParam[i]);
304            if (limitedTokens[i] > maxToken) {
305                maxToken = limitedTokens[i];
306            }
307        }
308        counts = new int[maxToken];
309    }
310
311    /**
312     * Sets the minimum depth for descendant counts.
313     * @param minimumDepth the minimum depth for descendant counts.
314     */
315    public void setMinimumDepth(int minimumDepth)
316    {
317        this.minimumDepth = minimumDepth;
318    }
319
320    /**
321     * Sets the maximum depth for descendant counts.
322     * @param maximumDepth the maximum depth for descendant counts.
323     */
324    public void setMaximumDepth(int maximumDepth)
325    {
326        this.maximumDepth = maximumDepth;
327    }
328
329   /**
330    * Sets a minimum count for descendants.
331    * @param minimumNumber the minimum count for descendants.
332    */
333    public void setMinimumNumber(int minimumNumber)
334    {
335        this.minimumNumber = minimumNumber;
336    }
337
338    /**
339      * Sets a maximum count for descendants.
340      * @param maximumNumber the maximum count for descendants.
341      */
342    public void setMaximumNumber(int maximumNumber)
343    {
344        this.maximumNumber = maximumNumber;
345    }
346
347    /**
348     * Sets the error message for minimum count not reached.
349     * @param message the error message for minimum count not reached.
350     * Used as a <code>MessageFormat</code> pattern with arguments
351     * <ul>
352     * <li>{0} - token count</li>
353     * <li>{1} - minimum number</li>
354     * <li>{2} - name of token</li>
355     * <li>{3} - name of limited token</li>
356     * </ul>
357     */
358    public void setMinimumMessage(String message)
359    {
360        minimumMessage = message;
361    }
362
363    /**
364     * Sets the error message for maximum count exceeded.
365     * @param message the error message for maximum count exceeded.
366     * Used as a <code>MessageFormat</code> pattern with arguments
367     * <ul>
368     * <li>{0} - token count</li>
369     * <li>{1} - maximum number</li>
370     * <li>{2} - name of token</li>
371     * <li>{3} - name of limited token</li>
372     * </ul>
373     */
374
375    public void setMaximumMessage(String message)
376    {
377        maximumMessage = message;
378    }
379
380    /**
381     * Sets whether to use the sum of the tokens found, rather than the
382     * individual counts.
383     * @param sum whether to use the sum.
384     */
385    public void setSumTokenCounts(boolean sum)
386    {
387        sumTokenCounts = sum;
388    }
389}