1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.metrics;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29  
30  /**
31   * This check calculates the Non Commenting Source Statements (NCSS) metric for
32   * java source files and methods. The check adheres to the <a
33   * href="http://www.kclee.com/clemens/java/javancss">JavaNCSS specification
34   * </a> and gives the same results as the JavaNCSS tool.
35   *
36   * <p>The NCSS-metric tries to determine complexity of methods, classes and files
37   * by counting the non commenting lines. Roughly said this is (nearly)
38   * equivalent to counting the semicolons and opening curly braces.
39   *
40   */
41  // -@cs[AbbreviationAsWordInName] We can not change it as,
42  // check's name is a part of API (used in configurations).
43  @FileStatefulCheck
44  public class JavaNCSSCheck extends AbstractCheck {
45  
46      /**
47       * A key is pointing to the warning message text in "messages.properties"
48       * file.
49       */
50      public static final String MSG_METHOD = "ncss.method";
51  
52      /**
53       * A key is pointing to the warning message text in "messages.properties"
54       * file.
55       */
56      public static final String MSG_CLASS = "ncss.class";
57  
58      /**
59       * A key is pointing to the warning message text in "messages.properties"
60       * file.
61       */
62      public static final String MSG_FILE = "ncss.file";
63  
64      /** Default constant for max file ncss. */
65      private static final int FILE_MAX_NCSS = 2000;
66  
67      /** Default constant for max file ncss. */
68      private static final int CLASS_MAX_NCSS = 1500;
69  
70      /** Default constant for max method ncss. */
71      private static final int METHOD_MAX_NCSS = 50;
72  
73      /** Maximum ncss for a complete source file. */
74      private int fileMaximum = FILE_MAX_NCSS;
75  
76      /** Maximum ncss for a class. */
77      private int classMaximum = CLASS_MAX_NCSS;
78  
79      /** Maximum ncss for a method. */
80      private int methodMaximum = METHOD_MAX_NCSS;
81  
82      /** List containing the stacked counters. */
83      private Deque<Counter> counters;
84  
85      @Override
86      public int[] getDefaultTokens() {
87          return getRequiredTokens();
88      }
89  
90      @Override
91      public int[] getRequiredTokens() {
92          return new int[] {
93              TokenTypes.CLASS_DEF,
94              TokenTypes.INTERFACE_DEF,
95              TokenTypes.METHOD_DEF,
96              TokenTypes.CTOR_DEF,
97              TokenTypes.INSTANCE_INIT,
98              TokenTypes.STATIC_INIT,
99              TokenTypes.PACKAGE_DEF,
100             TokenTypes.IMPORT,
101             TokenTypes.VARIABLE_DEF,
102             TokenTypes.CTOR_CALL,
103             TokenTypes.SUPER_CTOR_CALL,
104             TokenTypes.LITERAL_IF,
105             TokenTypes.LITERAL_ELSE,
106             TokenTypes.LITERAL_WHILE,
107             TokenTypes.LITERAL_DO,
108             TokenTypes.LITERAL_FOR,
109             TokenTypes.LITERAL_SWITCH,
110             TokenTypes.LITERAL_BREAK,
111             TokenTypes.LITERAL_CONTINUE,
112             TokenTypes.LITERAL_RETURN,
113             TokenTypes.LITERAL_THROW,
114             TokenTypes.LITERAL_SYNCHRONIZED,
115             TokenTypes.LITERAL_CATCH,
116             TokenTypes.LITERAL_FINALLY,
117             TokenTypes.EXPR,
118             TokenTypes.LABELED_STAT,
119             TokenTypes.LITERAL_CASE,
120             TokenTypes.LITERAL_DEFAULT,
121         };
122     }
123 
124     @Override
125     public int[] getAcceptableTokens() {
126         return getRequiredTokens();
127     }
128 
129     @Override
130     public void beginTree(DetailAST rootAST) {
131         counters = new ArrayDeque<>();
132 
133         //add a counter for the file
134         counters.push(new Counter());
135     }
136 
137     @Override
138     public void visitToken(DetailAST ast) {
139         final int tokenType = ast.getType();
140 
141         if (tokenType == TokenTypes.CLASS_DEF
142             || tokenType == TokenTypes.METHOD_DEF
143             || tokenType == TokenTypes.CTOR_DEF
144             || tokenType == TokenTypes.STATIC_INIT
145             || tokenType == TokenTypes.INSTANCE_INIT) {
146             //add a counter for this class/method
147             counters.push(new Counter());
148         }
149 
150         //check if token is countable
151         if (isCountable(ast)) {
152             //increment the stacked counters
153             counters.forEach(Counter::increment);
154         }
155     }
156 
157     @Override
158     public void leaveToken(DetailAST ast) {
159         final int tokenType = ast.getType();
160         if (tokenType == TokenTypes.METHOD_DEF
161             || tokenType == TokenTypes.CTOR_DEF
162             || tokenType == TokenTypes.STATIC_INIT
163             || tokenType == TokenTypes.INSTANCE_INIT) {
164             //pop counter from the stack
165             final Counter counter = counters.pop();
166 
167             final int count = counter.getCount();
168             if (count > methodMaximum) {
169                 log(ast, MSG_METHOD, count, methodMaximum);
170             }
171         }
172         else if (tokenType == TokenTypes.CLASS_DEF) {
173             //pop counter from the stack
174             final Counter counter = counters.pop();
175 
176             final int count = counter.getCount();
177             if (count > classMaximum) {
178                 log(ast, MSG_CLASS, count, classMaximum);
179             }
180         }
181     }
182 
183     @Override
184     public void finishTree(DetailAST rootAST) {
185         //pop counter from the stack
186         final Counter counter = counters.pop();
187 
188         final int count = counter.getCount();
189         if (count > fileMaximum) {
190             log(rootAST, MSG_FILE, count, fileMaximum);
191         }
192     }
193 
194     /**
195      * Sets the maximum ncss for a file.
196      *
197      * @param fileMaximum
198      *            the maximum ncss
199      */
200     public void setFileMaximum(int fileMaximum) {
201         this.fileMaximum = fileMaximum;
202     }
203 
204     /**
205      * Sets the maximum ncss for a class.
206      *
207      * @param classMaximum
208      *            the maximum ncss
209      */
210     public void setClassMaximum(int classMaximum) {
211         this.classMaximum = classMaximum;
212     }
213 
214     /**
215      * Sets the maximum ncss for a method.
216      *
217      * @param methodMaximum
218      *            the maximum ncss
219      */
220     public void setMethodMaximum(int methodMaximum) {
221         this.methodMaximum = methodMaximum;
222     }
223 
224     /**
225      * Checks if a token is countable for the ncss metric.
226      *
227      * @param ast
228      *            the AST
229      * @return true if the token is countable
230      */
231     private static boolean isCountable(DetailAST ast) {
232         boolean countable = true;
233 
234         final int tokenType = ast.getType();
235 
236         //check if an expression is countable
237         if (tokenType == TokenTypes.EXPR) {
238             countable = isExpressionCountable(ast);
239         }
240         //check if an variable definition is countable
241         else if (tokenType == TokenTypes.VARIABLE_DEF) {
242             countable = isVariableDefCountable(ast);
243         }
244         return countable;
245     }
246 
247     /**
248      * Checks if a variable definition is countable.
249      *
250      * @param ast the AST
251      * @return true if the variable definition is countable, false otherwise
252      */
253     private static boolean isVariableDefCountable(DetailAST ast) {
254         boolean countable = false;
255 
256         //count variable definitions only if they are direct child to a slist or
257         // object block
258         final int parentType = ast.getParent().getType();
259 
260         if (parentType == TokenTypes.SLIST
261             || parentType == TokenTypes.OBJBLOCK) {
262             final DetailAST prevSibling = ast.getPreviousSibling();
263 
264             //is countable if no previous sibling is found or
265             //the sibling is no COMMA.
266             //This is done because multiple assignment on one line are counted
267             // as 1
268             countable = prevSibling == null
269                     || prevSibling.getType() != TokenTypes.COMMA;
270         }
271 
272         return countable;
273     }
274 
275     /**
276      * Checks if an expression is countable for the ncss metric.
277      *
278      * @param ast the AST
279      * @return true if the expression is countable, false otherwise
280      */
281     private static boolean isExpressionCountable(DetailAST ast) {
282         final boolean countable;
283 
284         //count expressions only if they are direct child to a slist (method
285         // body, for loop...)
286         //or direct child of label,if,else,do,while,for
287         final int parentType = ast.getParent().getType();
288         switch (parentType) {
289             case TokenTypes.SLIST :
290             case TokenTypes.LABELED_STAT :
291             case TokenTypes.LITERAL_FOR :
292             case TokenTypes.LITERAL_DO :
293             case TokenTypes.LITERAL_WHILE :
294             case TokenTypes.LITERAL_IF :
295             case TokenTypes.LITERAL_ELSE :
296                 //don't count if or loop conditions
297                 final DetailAST prevSibling = ast.getPreviousSibling();
298                 countable = prevSibling == null
299                     || prevSibling.getType() != TokenTypes.LPAREN;
300                 break;
301             default :
302                 countable = false;
303                 break;
304         }
305         return countable;
306     }
307 
308     /**
309      * Class representing a counter.
310      *
311      */
312     private static class Counter {
313 
314         /** The counters internal integer. */
315         private int count;
316 
317         /**
318          * Increments the counter.
319          */
320         public void increment() {
321             count++;
322         }
323 
324         /**
325          * Gets the counters value.
326          *
327          * @return the counter
328          */
329         public int getCount() {
330             return count;
331         }
332 
333     }
334 
335 }