001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.math.BigInteger; 023import java.util.ArrayDeque; 024import java.util.Deque; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030 031/** 032 * <p> 033 * Checks cyclomatic complexity against a specified limit. It is a measure of 034 * the minimum number of possible paths through the source and therefore the 035 * number of required tests, it is not a about quality of code! It is only 036 * applied to methods, c-tors, 037 * <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html"> 038 * static initializers and instance initializers</a>. 039 * </p> 040 * <p> 041 * The complexity is equal to the number of decision points {@code + 1}. 042 * Decision points: {@code if}, {@code while}, {@code do}, {@code for}, 043 * {@code ?:}, {@code catch}, {@code switch}, {@code case} statements and 044 * operators {@code &&} and {@code ||} in the body of target. 045 * </p> 046 * <p> 047 * By pure theory level 1-4 is considered easy to test, 5-7 OK, 8-10 consider 048 * re-factoring to ease testing, and 11+ re-factor now as testing will be painful. 049 * </p> 050 * <p> 051 * When it comes to code quality measurement by this metric level 10 is very 052 * good level as a ultimate target (that is hard to archive). Do not be ashamed 053 * to have complexity level 15 or even higher, but keep it below 20 to catch 054 * really bad designed code automatically. 055 * </p> 056 * <p> 057 * Please use Suppression to avoid violations on cases that could not be split 058 * in few methods without damaging readability of code or encapsulation. 059 * </p> 060 * <ul> 061 * <li> 062 * Property {@code max} - Specify the maximum threshold allowed. 063 * Type is {@code int}. 064 * Default value is {@code 10}. 065 * </li> 066 * <li> 067 * Property {@code switchBlockAsSingleDecisionPoint} - Control whether to treat 068 * the whole switch block as a single decision point. 069 * Type is {@code boolean}. 070 * Default value is {@code false}. 071 * </li> 072 * <li> 073 * Property {@code tokens} - tokens to check 074 * Type is {@code int[]}. 075 * Default value is: 076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 077 * LITERAL_WHILE</a>, 078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 079 * LITERAL_DO</a>, 080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 081 * LITERAL_FOR</a>, 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 083 * LITERAL_IF</a>, 084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 085 * LITERAL_SWITCH</a>, 086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE"> 087 * LITERAL_CASE</a>, 088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 089 * LITERAL_CATCH</a>, 090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 091 * QUESTION</a>, 092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 093 * LAND</a>, 094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 095 * LOR</a>. 096 * </li> 097 * </ul> 098 * <p> 099 * To configure the check: 100 * </p> 101 * <pre> 102 * <module name="CyclomaticComplexity"/> 103 * </pre> 104 * <p> 105 * Example: 106 * </p> 107 * <pre> 108 * class CyclomaticComplexity { 109 * // Cyclomatic Complexity = 11 110 * int a, b, c, d, n; 111 * public void foo() { // 1, function declaration 112 * if (a == 1) { // 2, if 113 * fun1(); 114 * } else if (a == b // 3, if 115 * && a == c) { // 4, && operator 116 * if (c == 2) { // 5, if 117 * fun2(); 118 * } 119 * } else if (a == d) { // 6, if 120 * try { 121 * fun4(); 122 * } catch (Exception e) { // 7, catch 123 * } 124 * } else { 125 * switch(n) { 126 * case 1: // 8, case 127 * fun1(); 128 * break; 129 * case 2: // 9, case 130 * fun2(); 131 * break; 132 * case 3: // 10, case 133 * fun3(); 134 * break; 135 * default: 136 * break; 137 * } 138 * } 139 * d = a < 0 ? -1 : 1; // 11, ternary operator 140 * } 141 * } 142 * </pre> 143 * <p> 144 * To configure the check with a threshold of 4 and check only for while and do-while loops: 145 * </p> 146 * <pre> 147 * <module name="CyclomaticComplexity"> 148 * <property name="max" value="4"/> 149 * <property name="tokens" value="LITERAL_WHILE, LITERAL_DO"/> 150 * </module> 151 * </pre> 152 * <p> 153 * Example: 154 * </p> 155 * <pre> 156 * class CyclomaticComplexity { 157 * // Cyclomatic Complexity = 5 158 * int a, b, c, d; 159 * public void foo() { // 1, function declaration 160 * while (a < b // 2, while 161 * && a > c) { 162 * fun(); 163 * } 164 * if (a == b) { 165 * do { // 3, do 166 * fun(); 167 * } while (d); 168 * } else if (c == d) { 169 * while (c > 0) { // 4, while 170 * fun(); 171 * } 172 * do { // 5, do-while 173 * fun(); 174 * } while (a); 175 * } 176 * } 177 * } 178 * </pre> 179 * <p> 180 * To configure the check to consider switch-case block as one decision point. 181 * </p> 182 * <pre> 183 * <module name="CyclomaticComplexity"> 184 * <property name="switchBlockAsSingleDecisionPoint" value="true"/> 185 * </module> 186 * </pre> 187 * <p> 188 * Example: 189 * </p> 190 * <pre> 191 * class CyclomaticComplexity { 192 * // Cyclomatic Complexity = 11 193 * int a, b, c, d, e, n; 194 * public void foo() { // 1, function declaration 195 * if (a == b) { // 2, if 196 * fun1(); 197 * } else if (a == 0 // 3, if 198 * && b == c) { // 4, && operator 199 * if (c == -1) { // 5, if 200 * fun2(); 201 * } 202 * } else if (a == c // 6, if 203 * || a == d) { // 7, || operator 204 * fun3(); 205 * } else if (d == e) { // 8, if 206 * try { 207 * fun4(); 208 * } catch (Exception e) { // 9, catch 209 * } 210 * } else { 211 * switch(n) { // 10, switch 212 * case 1: 213 * fun1(); 214 * break; 215 * case 2: 216 * fun2(); 217 * break; 218 * default: 219 * break; 220 * } 221 * } 222 * a = a > 0 ? b : c; // 11, ternary operator 223 * } 224 * } 225 * </pre> 226 * <p> 227 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 228 * </p> 229 * <p> 230 * Violation Message Keys: 231 * </p> 232 * <ul> 233 * <li> 234 * {@code cyclomaticComplexity} 235 * </li> 236 * </ul> 237 * 238 * @since 3.2 239 */ 240@FileStatefulCheck 241public class CyclomaticComplexityCheck 242 extends AbstractCheck { 243 244 /** 245 * A key is pointing to the warning message text in "messages.properties" 246 * file. 247 */ 248 public static final String MSG_KEY = "cyclomaticComplexity"; 249 250 /** The initial current value. */ 251 private static final BigInteger INITIAL_VALUE = BigInteger.ONE; 252 253 /** Default allowed complexity. */ 254 private static final int DEFAULT_COMPLEXITY_VALUE = 10; 255 256 /** Stack of values - all but the current value. */ 257 private final Deque<BigInteger> valueStack = new ArrayDeque<>(); 258 259 /** Control whether to treat the whole switch block as a single decision point. */ 260 private boolean switchBlockAsSingleDecisionPoint; 261 262 /** The current value. */ 263 private BigInteger currentValue = INITIAL_VALUE; 264 265 /** Specify the maximum threshold allowed. */ 266 private int max = DEFAULT_COMPLEXITY_VALUE; 267 268 /** 269 * Setter to control whether to treat the whole switch block as a single decision point. 270 * 271 * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch 272 * block as a single decision point. 273 */ 274 public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) { 275 this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint; 276 } 277 278 /** 279 * Setter to specify the maximum threshold allowed. 280 * 281 * @param max the maximum threshold 282 */ 283 public final void setMax(int max) { 284 this.max = max; 285 } 286 287 @Override 288 public int[] getDefaultTokens() { 289 return new int[] { 290 TokenTypes.CTOR_DEF, 291 TokenTypes.METHOD_DEF, 292 TokenTypes.INSTANCE_INIT, 293 TokenTypes.STATIC_INIT, 294 TokenTypes.LITERAL_WHILE, 295 TokenTypes.LITERAL_DO, 296 TokenTypes.LITERAL_FOR, 297 TokenTypes.LITERAL_IF, 298 TokenTypes.LITERAL_SWITCH, 299 TokenTypes.LITERAL_CASE, 300 TokenTypes.LITERAL_CATCH, 301 TokenTypes.QUESTION, 302 TokenTypes.LAND, 303 TokenTypes.LOR, 304 }; 305 } 306 307 @Override 308 public int[] getAcceptableTokens() { 309 return new int[] { 310 TokenTypes.CTOR_DEF, 311 TokenTypes.METHOD_DEF, 312 TokenTypes.INSTANCE_INIT, 313 TokenTypes.STATIC_INIT, 314 TokenTypes.LITERAL_WHILE, 315 TokenTypes.LITERAL_DO, 316 TokenTypes.LITERAL_FOR, 317 TokenTypes.LITERAL_IF, 318 TokenTypes.LITERAL_SWITCH, 319 TokenTypes.LITERAL_CASE, 320 TokenTypes.LITERAL_CATCH, 321 TokenTypes.QUESTION, 322 TokenTypes.LAND, 323 TokenTypes.LOR, 324 }; 325 } 326 327 @Override 328 public final int[] getRequiredTokens() { 329 return new int[] { 330 TokenTypes.CTOR_DEF, 331 TokenTypes.METHOD_DEF, 332 TokenTypes.INSTANCE_INIT, 333 TokenTypes.STATIC_INIT, 334 }; 335 } 336 337 @Override 338 public void visitToken(DetailAST ast) { 339 switch (ast.getType()) { 340 case TokenTypes.CTOR_DEF: 341 case TokenTypes.METHOD_DEF: 342 case TokenTypes.INSTANCE_INIT: 343 case TokenTypes.STATIC_INIT: 344 visitMethodDef(); 345 break; 346 default: 347 visitTokenHook(ast); 348 } 349 } 350 351 @Override 352 public void leaveToken(DetailAST ast) { 353 switch (ast.getType()) { 354 case TokenTypes.CTOR_DEF: 355 case TokenTypes.METHOD_DEF: 356 case TokenTypes.INSTANCE_INIT: 357 case TokenTypes.STATIC_INIT: 358 leaveMethodDef(ast); 359 break; 360 default: 361 break; 362 } 363 } 364 365 /** 366 * Hook called when visiting a token. Will not be called the method 367 * definition tokens. 368 * 369 * @param ast the token being visited 370 */ 371 private void visitTokenHook(DetailAST ast) { 372 if (switchBlockAsSingleDecisionPoint) { 373 if (ast.getType() != TokenTypes.LITERAL_CASE) { 374 incrementCurrentValue(BigInteger.ONE); 375 } 376 } 377 else if (ast.getType() != TokenTypes.LITERAL_SWITCH) { 378 incrementCurrentValue(BigInteger.ONE); 379 } 380 } 381 382 /** 383 * Process the end of a method definition. 384 * 385 * @param ast the token representing the method definition 386 */ 387 private void leaveMethodDef(DetailAST ast) { 388 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 389 if (currentValue.compareTo(bigIntegerMax) > 0) { 390 log(ast, MSG_KEY, currentValue, bigIntegerMax); 391 } 392 popValue(); 393 } 394 395 /** 396 * Increments the current value by a specified amount. 397 * 398 * @param amount the amount to increment by 399 */ 400 private void incrementCurrentValue(BigInteger amount) { 401 currentValue = currentValue.add(amount); 402 } 403 404 /** Push the current value on the stack. */ 405 private void pushValue() { 406 valueStack.push(currentValue); 407 currentValue = INITIAL_VALUE; 408 } 409 410 /** 411 * Pops a value off the stack and makes it the current value. 412 */ 413 private void popValue() { 414 currentValue = valueStack.pop(); 415 } 416 417 /** Process the start of the method definition. */ 418 private void visitMethodDef() { 419 pushValue(); 420 } 421 422}