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.metrics;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FastStack;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 * This check calculates the Non Commenting Source Statements (NCSS) metric for
028 * java source files and methods. The check adheres to the <a
029 * href="http://www.kclee.com/clemens/java/javancss/">JavaNCSS specification
030 * </a> and gives the same results as the JavaNCSS tool.
031 *
032 * The NCSS-metric tries to determine complexity of methods, classes and files
033 * by counting the non commenting lines. Roughly said this is (nearly)
034 * equivalent to counting the semicolons and opening curly braces.
035 *
036 * @author Lars Ködderitzsch
037 */
038public class JavaNCSSCheck extends Check
039{
040    /** default constant for max file ncss */
041    private static final int FILE_MAX_NCSS = 2000;
042
043    /** default constant for max file ncss */
044    private static final int CLASS_MAX_NCSS = 1500;
045
046    /** default constant for max method ncss */
047    private static final int METHOD_MAX_NCSS = 50;
048
049    /** maximum ncss for a complete source file */
050    private int fileMax = FILE_MAX_NCSS;
051
052    /** maximum ncss for a class */
053    private int classMax = CLASS_MAX_NCSS;
054
055    /** maximum ncss for a method */
056    private int methodMax = METHOD_MAX_NCSS;
057
058    /** list containing the stacked counters */
059    private FastStack<Counter> counters;
060
061    @Override
062    public int[] getDefaultTokens()
063    {
064        return new int[]{
065            TokenTypes.CLASS_DEF,
066            TokenTypes.INTERFACE_DEF,
067            TokenTypes.METHOD_DEF,
068            TokenTypes.CTOR_DEF,
069            TokenTypes.INSTANCE_INIT,
070            TokenTypes.STATIC_INIT,
071            TokenTypes.PACKAGE_DEF,
072            TokenTypes.IMPORT,
073            TokenTypes.VARIABLE_DEF,
074            TokenTypes.CTOR_CALL,
075            TokenTypes.SUPER_CTOR_CALL,
076            TokenTypes.LITERAL_IF,
077            TokenTypes.LITERAL_ELSE,
078            TokenTypes.LITERAL_WHILE,
079            TokenTypes.LITERAL_DO,
080            TokenTypes.LITERAL_FOR,
081            TokenTypes.LITERAL_SWITCH,
082            TokenTypes.LITERAL_BREAK,
083            TokenTypes.LITERAL_CONTINUE,
084            TokenTypes.LITERAL_RETURN,
085            TokenTypes.LITERAL_THROW,
086            TokenTypes.LITERAL_SYNCHRONIZED,
087            TokenTypes.LITERAL_CATCH,
088            TokenTypes.LITERAL_FINALLY,
089            TokenTypes.EXPR,
090            TokenTypes.LABELED_STAT,
091            TokenTypes.LITERAL_CASE,
092            TokenTypes.LITERAL_DEFAULT,
093        };
094    }
095
096    @Override
097    public int[] getRequiredTokens()
098    {
099        return new int[]{
100            TokenTypes.CLASS_DEF,
101            TokenTypes.INTERFACE_DEF,
102            TokenTypes.METHOD_DEF,
103            TokenTypes.CTOR_DEF,
104            TokenTypes.INSTANCE_INIT,
105            TokenTypes.STATIC_INIT,
106            TokenTypes.PACKAGE_DEF,
107            TokenTypes.IMPORT,
108            TokenTypes.VARIABLE_DEF,
109            TokenTypes.CTOR_CALL,
110            TokenTypes.SUPER_CTOR_CALL,
111            TokenTypes.LITERAL_IF,
112            TokenTypes.LITERAL_ELSE,
113            TokenTypes.LITERAL_WHILE,
114            TokenTypes.LITERAL_DO,
115            TokenTypes.LITERAL_FOR,
116            TokenTypes.LITERAL_SWITCH,
117            TokenTypes.LITERAL_BREAK,
118            TokenTypes.LITERAL_CONTINUE,
119            TokenTypes.LITERAL_RETURN,
120            TokenTypes.LITERAL_THROW,
121            TokenTypes.LITERAL_SYNCHRONIZED,
122            TokenTypes.LITERAL_CATCH,
123            TokenTypes.LITERAL_FINALLY,
124            TokenTypes.EXPR,
125            TokenTypes.LABELED_STAT,
126            TokenTypes.LITERAL_CASE,
127            TokenTypes.LITERAL_DEFAULT,
128        };
129    }
130
131    @Override
132    public void beginTree(DetailAST rootAST)
133    {
134        counters = new FastStack<Counter>();
135
136        //add a counter for the file
137        counters.push(new Counter());
138    }
139
140    @Override
141    public void visitToken(DetailAST ast)
142    {
143        final int tokenType = ast.getType();
144
145        if ((TokenTypes.CLASS_DEF == tokenType)
146            || (TokenTypes.METHOD_DEF == tokenType)
147            || (TokenTypes.CTOR_DEF == tokenType)
148            || (TokenTypes.STATIC_INIT == tokenType)
149            || (TokenTypes.INSTANCE_INIT == tokenType))
150        {
151            //add a counter for this class/method
152            counters.push(new Counter());
153        }
154
155        //check if token is countable
156        if (isCountable(ast)) {
157            //increment the stacked counters
158            for (final Counter c : counters) {
159                c.increment();
160            }
161        }
162    }
163
164    @Override
165    public void leaveToken(DetailAST ast)
166    {
167        final int tokenType = ast.getType();
168        if ((TokenTypes.METHOD_DEF == tokenType)
169            || (TokenTypes.CTOR_DEF == tokenType)
170            || (TokenTypes.STATIC_INIT == tokenType)
171            || (TokenTypes.INSTANCE_INIT == tokenType))
172        {
173            //pop counter from the stack
174            final Counter counter = counters.pop();
175
176            final int count = counter.getCount();
177            if (count > methodMax) {
178                log(ast.getLineNo(), ast.getColumnNo(), "ncss.method",
179                        count, methodMax);
180            }
181        }
182        else if (TokenTypes.CLASS_DEF == tokenType) {
183            //pop counter from the stack
184            final Counter counter = counters.pop();
185
186            final int count = counter.getCount();
187            if (count > classMax) {
188                log(ast.getLineNo(), ast.getColumnNo(), "ncss.class",
189                        count, classMax);
190            }
191        }
192    }
193
194    @Override
195    public void finishTree(DetailAST rootAST)
196    {
197        //pop counter from the stack
198        final Counter counter = counters.pop();
199
200        final int count = counter.getCount();
201        if (count > fileMax) {
202            log(rootAST.getLineNo(), rootAST.getColumnNo(), "ncss.file",
203                    count, fileMax);
204        }
205    }
206
207    /**
208     * Sets the maximum ncss for a file.
209     *
210     * @param fileMax
211     *            the maximum ncss
212     */
213    public void setFileMaximum(int fileMax)
214    {
215        this.fileMax = fileMax;
216    }
217
218    /**
219     * Sets the maximum ncss for a class.
220     *
221     * @param classMax
222     *            the maximum ncss
223     */
224    public void setClassMaximum(int classMax)
225    {
226        this.classMax = classMax;
227    }
228
229    /**
230     * Sets the maximum ncss for a method.
231     *
232     * @param methodMax
233     *            the maximum ncss
234     */
235    public void setMethodMaximum(int methodMax)
236    {
237        this.methodMax = methodMax;
238    }
239
240    /**
241     * Checks if a token is countable for the ncss metric
242     *
243     * @param ast
244     *            the AST
245     * @return true if the token is countable
246     */
247    private boolean isCountable(DetailAST ast)
248    {
249        boolean countable = true;
250
251        final int tokenType = ast.getType();
252
253        //check if an expression is countable
254        if (TokenTypes.EXPR == tokenType) {
255            countable = isExpressionCountable(ast);
256        }
257        //check if an variable definition is countable
258        else if (TokenTypes.VARIABLE_DEF == tokenType) {
259            countable = isVariableDefCountable(ast);
260        }
261        return countable;
262    }
263
264    /**
265     * Checks if a variable definition is countable.
266     *
267     * @param ast the AST
268     * @return true if the variable definition is countable, false otherwise
269     */
270    private boolean isVariableDefCountable(DetailAST ast)
271    {
272        boolean countable = false;
273
274        //count variable defs only if they are direct child to a slist or
275        // object block
276        final int parentType = ast.getParent().getType();
277
278        if ((TokenTypes.SLIST == parentType)
279            || (TokenTypes.OBJBLOCK == parentType))
280        {
281            final DetailAST prevSibling = ast.getPreviousSibling();
282
283            //is countable if no previous sibling is found or
284            //the sibling is no COMMA.
285            //This is done because multiple assignment on one line are countes
286            // as 1
287            countable = (prevSibling == null)
288                    || (TokenTypes.COMMA != prevSibling.getType());
289        }
290
291        return countable;
292    }
293
294    /**
295     * Checks if an expression is countable for the ncss metric.
296     *
297     * @param ast the AST
298     * @return true if the expression is countable, false otherwise
299     */
300    private boolean isExpressionCountable(DetailAST ast)
301    {
302        boolean countable = true;
303
304        //count expressions only if they are direct child to a slist (method
305        // body, for loop...)
306        //or direct child of label,if,else,do,while,for
307        final int parentType = ast.getParent().getType();
308        switch (parentType) {
309            case TokenTypes.SLIST :
310            case TokenTypes.LABELED_STAT :
311            case TokenTypes.LITERAL_FOR :
312            case TokenTypes.LITERAL_DO :
313            case TokenTypes.LITERAL_WHILE :
314            case TokenTypes.LITERAL_IF :
315            case TokenTypes.LITERAL_ELSE :
316                //don't count if or loop conditions
317                final DetailAST prevSibling = ast.getPreviousSibling();
318                countable = (prevSibling == null)
319                    || (TokenTypes.LPAREN != prevSibling.getType());
320                break;
321            default :
322                countable = false;
323                break;
324        }
325        return countable;
326    }
327
328    /**
329     * @author Lars Ködderitzsch
330     *
331     * Class representing a counter,
332     */
333    private static class Counter
334    {
335        /** the counters internal integer */
336        private int ivCount;
337
338        /**
339         * Increments the counter.
340         */
341        public void increment()
342        {
343            ivCount++;
344        }
345
346        /**
347         * Gets the counters value
348         *
349         * @return the counter
350         */
351        public int getCount()
352        {
353            return ivCount;
354        }
355    }
356}