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