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}