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}