View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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   * @author Lars Ködderitzsch
41   */
42  // -@cs[AbbreviationAsWordInName] We can not change it as,
43  // check's name is a part of API (used in configurations).
44  @FileStatefulCheck
45  public class JavaNCSSCheck extends AbstractCheck {
46  
47      /**
48       * A key is pointing to the warning message text in "messages.properties"
49       * file.
50       */
51      public static final String MSG_METHOD = "ncss.method";
52  
53      /**
54       * A key is pointing to the warning message text in "messages.properties"
55       * file.
56       */
57      public static final String MSG_CLASS = "ncss.class";
58  
59      /**
60       * A key is pointing to the warning message text in "messages.properties"
61       * file.
62       */
63      public static final String MSG_FILE = "ncss.file";
64  
65      /** Default constant for max file ncss. */
66      private static final int FILE_MAX_NCSS = 2000;
67  
68      /** Default constant for max file ncss. */
69      private static final int CLASS_MAX_NCSS = 1500;
70  
71      /** Default constant for max method ncss. */
72      private static final int METHOD_MAX_NCSS = 50;
73  
74      /** Maximum ncss for a complete source file. */
75      private int fileMaximum = FILE_MAX_NCSS;
76  
77      /** Maximum ncss for a class. */
78      private int classMaximum = CLASS_MAX_NCSS;
79  
80      /** Maximum ncss for a method. */
81      private int methodMaximum = METHOD_MAX_NCSS;
82  
83      /** List containing the stacked counters. */
84      private Deque<Counter> counters;
85  
86      @Override
87      public int[] getDefaultTokens() {
88          return getRequiredTokens();
89      }
90  
91      @Override
92      public int[] getRequiredTokens() {
93          return new int[] {
94              TokenTypes.CLASS_DEF,
95              TokenTypes.INTERFACE_DEF,
96              TokenTypes.METHOD_DEF,
97              TokenTypes.CTOR_DEF,
98              TokenTypes.INSTANCE_INIT,
99              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 
319         /** The counters internal integer. */
320         private int count;
321 
322         /**
323          * Increments the counter.
324          */
325         public void increment() {
326             count++;
327         }
328 
329         /**
330          * Gets the counters value.
331          *
332          * @return the counter
333          */
334         public int getCount() {
335             return count;
336         }
337 
338     }
339 
340 }