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