001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * Checks the npath complexity against a specified limit (default = 200). 032 * The npath metric computes the number of possible execution paths 033 * through a function. Similar to the cyclomatic complexity but also 034 * takes into account the nesting of conditional statements and 035 * multi-part boolean expressions. 036 * 037 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 038 * @author o_sukhodolsky 039 */ 040// -@cs[AbbreviationAsWordInName] Can't change check name 041public final class NPathComplexityCheck extends AbstractCheck { 042 043 /** 044 * A key is pointing to the warning message text in "messages.properties" 045 * file. 046 */ 047 public static final String MSG_KEY = "npathComplexity"; 048 049 /** Default allowed complexity. */ 050 private static final int DEFAULT_MAX = 200; 051 052 /** The initial current value. */ 053 private static final BigInteger INITIAL_VALUE = BigInteger.ZERO; 054 055 /** 056 * Stack of NP values for ranges. 057 */ 058 private final Deque<BigInteger> rangeValues = new ArrayDeque<>(); 059 060 /** Stack of NP values for expressions. */ 061 private final Deque<Integer> expressionValues = new ArrayDeque<>(); 062 063 /** Stack of belongs to range values for question operator. */ 064 private final Deque<Boolean> isAfterValues = new ArrayDeque<>(); 065 066 /** 067 * Range of the last processed expression. Used for checking that ternary operation 068 * which is a part of expression won't be processed for second time. 069 */ 070 private final TokenEnd processingTokenEnd = new TokenEnd(); 071 072 /** NP value for current range. */ 073 private BigInteger currentRangeValue = INITIAL_VALUE; 074 075 /** Threshold to report error for. */ 076 private int max = DEFAULT_MAX; 077 078 /** True, when branch is visited, but not leaved. */ 079 private boolean branchVisited; 080 081 /** 082 * Set the maximum threshold allowed. 083 * @param max the maximum threshold 084 */ 085 public void setMax(int max) { 086 this.max = max; 087 } 088 089 @Override 090 public int[] getDefaultTokens() { 091 return getAcceptableTokens(); 092 } 093 094 @Override 095 public int[] getAcceptableTokens() { 096 return new int[] { 097 TokenTypes.CTOR_DEF, 098 TokenTypes.METHOD_DEF, 099 TokenTypes.STATIC_INIT, 100 TokenTypes.INSTANCE_INIT, 101 TokenTypes.LITERAL_WHILE, 102 TokenTypes.LITERAL_DO, 103 TokenTypes.LITERAL_FOR, 104 TokenTypes.LITERAL_IF, 105 TokenTypes.LITERAL_ELSE, 106 TokenTypes.LITERAL_SWITCH, 107 TokenTypes.CASE_GROUP, 108 TokenTypes.LITERAL_TRY, 109 TokenTypes.LITERAL_CATCH, 110 TokenTypes.QUESTION, 111 TokenTypes.LITERAL_RETURN, 112 TokenTypes.LITERAL_DEFAULT, 113 }; 114 } 115 116 @Override 117 public int[] getRequiredTokens() { 118 return getAcceptableTokens(); 119 } 120 121 @Override 122 public void beginTree(DetailAST rootAST) { 123 rangeValues.clear(); 124 expressionValues.clear(); 125 isAfterValues.clear(); 126 processingTokenEnd.reset(); 127 currentRangeValue = INITIAL_VALUE; 128 branchVisited = false; 129 } 130 131 @Override 132 public void visitToken(DetailAST ast) { 133 switch (ast.getType()) { 134 case TokenTypes.LITERAL_IF: 135 case TokenTypes.LITERAL_SWITCH: 136 case TokenTypes.LITERAL_WHILE: 137 case TokenTypes.LITERAL_DO: 138 case TokenTypes.LITERAL_FOR: 139 visitConditional(ast, 1); 140 break; 141 case TokenTypes.QUESTION: 142 visitUnitaryOperator(ast, 2); 143 break; 144 case TokenTypes.LITERAL_RETURN: 145 visitUnitaryOperator(ast, 0); 146 break; 147 case TokenTypes.CASE_GROUP: 148 final int caseNumber = countCaseTokens(ast); 149 branchVisited = true; 150 pushValue(caseNumber); 151 break; 152 case TokenTypes.LITERAL_ELSE: 153 branchVisited = true; 154 if (currentRangeValue.equals(BigInteger.ZERO)) { 155 currentRangeValue = BigInteger.ONE; 156 } 157 pushValue(0); 158 break; 159 case TokenTypes.LITERAL_TRY: 160 case TokenTypes.LITERAL_CATCH: 161 case TokenTypes.LITERAL_DEFAULT: 162 pushValue(1); 163 break; 164 case TokenTypes.CTOR_DEF: 165 case TokenTypes.METHOD_DEF: 166 case TokenTypes.INSTANCE_INIT: 167 case TokenTypes.STATIC_INIT: 168 pushValue(0); 169 break; 170 default: 171 break; 172 } 173 } 174 175 @Override 176 public void leaveToken(DetailAST ast) { 177 switch (ast.getType()) { 178 case TokenTypes.LITERAL_WHILE: 179 case TokenTypes.LITERAL_DO: 180 case TokenTypes.LITERAL_FOR: 181 case TokenTypes.LITERAL_IF: 182 case TokenTypes.LITERAL_SWITCH: 183 leaveConditional(); 184 break; 185 case TokenTypes.LITERAL_TRY: 186 leaveMultiplyingConditional(); 187 break; 188 case TokenTypes.LITERAL_RETURN: 189 case TokenTypes.QUESTION: 190 leaveUnitaryOperator(); 191 break; 192 case TokenTypes.LITERAL_CATCH: 193 leaveAddingConditional(); 194 break; 195 case TokenTypes.LITERAL_DEFAULT: 196 leaveBranch(); 197 break; 198 case TokenTypes.LITERAL_ELSE: 199 case TokenTypes.CASE_GROUP: 200 leaveBranch(); 201 branchVisited = false; 202 break; 203 case TokenTypes.CTOR_DEF: 204 case TokenTypes.METHOD_DEF: 205 case TokenTypes.INSTANCE_INIT: 206 case TokenTypes.STATIC_INIT: 207 leaveMethodDef(ast); 208 break; 209 default: 210 break; 211 } 212 } 213 214 /** 215 * Visits if, while, do-while, for and switch tokens - all of them have expression in 216 * parentheses which is used for calculation. 217 * @param ast visited token. 218 * @param basicBranchingFactor default number of branches added. 219 */ 220 private void visitConditional(DetailAST ast, int basicBranchingFactor) { 221 int expressionValue = basicBranchingFactor; 222 DetailAST bracketed; 223 for (bracketed = ast.findFirstToken(TokenTypes.LPAREN).getNextSibling(); 224 bracketed.getType() != TokenTypes.RPAREN; 225 bracketed = bracketed.getNextSibling()) { 226 expressionValue += countConditionalOperators(bracketed); 227 } 228 processingTokenEnd.setToken(bracketed); 229 pushValue(expressionValue); 230 } 231 232 /** 233 * Visits ternary operator (?:) and return tokens. They differ from those processed by 234 * visitConditional method in that their expression isn't bracketed. 235 * @param ast visited token. 236 * @param basicBranchingFactor number of branches inherently added by this token. 237 */ 238 private void visitUnitaryOperator(DetailAST ast, int basicBranchingFactor) { 239 final boolean isAfter = processingTokenEnd.isAfter(ast); 240 isAfterValues.push(isAfter); 241 if (!isAfter) { 242 processingTokenEnd.setToken(getLastToken(ast)); 243 final int expressionValue = basicBranchingFactor + countConditionalOperators(ast); 244 pushValue(expressionValue); 245 } 246 } 247 248 /** 249 * Leaves ternary operator (?:) and return tokens. 250 */ 251 private void leaveUnitaryOperator() { 252 if (!isAfterValues.pop()) { 253 final Values valuePair = popValue(); 254 BigInteger basicRangeValue = valuePair.getRangeValue(); 255 BigInteger expressionValue = valuePair.getExpressionValue(); 256 if (expressionValue.equals(BigInteger.ZERO)) { 257 expressionValue = BigInteger.ONE; 258 } 259 if (basicRangeValue.equals(BigInteger.ZERO)) { 260 basicRangeValue = BigInteger.ONE; 261 } 262 currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue); 263 } 264 } 265 266 /** Leaves while, do, for, if, ternary (?::), return or switch. */ 267 private void leaveConditional() { 268 final Values valuePair = popValue(); 269 final BigInteger expressionValue = valuePair.getExpressionValue(); 270 BigInteger basicRangeValue = valuePair.getRangeValue(); 271 if (currentRangeValue.equals(BigInteger.ZERO)) { 272 currentRangeValue = BigInteger.ONE; 273 } 274 if (basicRangeValue.equals(BigInteger.ZERO)) { 275 basicRangeValue = BigInteger.ONE; 276 } 277 currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue); 278 } 279 280 /** Leaves else, default or case group tokens. */ 281 private void leaveBranch() { 282 final Values valuePair = popValue(); 283 final BigInteger basicRangeValue = valuePair.getRangeValue(); 284 final BigInteger expressionValue = valuePair.getExpressionValue(); 285 if (branchVisited && currentRangeValue.equals(BigInteger.ZERO)) { 286 currentRangeValue = BigInteger.ONE; 287 } 288 currentRangeValue = currentRangeValue.subtract(BigInteger.ONE) 289 .add(basicRangeValue) 290 .add(expressionValue); 291 } 292 293 /** 294 * Process the end of a method definition. 295 * @param ast the token type representing the method definition 296 */ 297 private void leaveMethodDef(DetailAST ast) { 298 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 299 if (currentRangeValue.compareTo(bigIntegerMax) > 0) { 300 log(ast, MSG_KEY, currentRangeValue, bigIntegerMax); 301 } 302 popValue(); 303 currentRangeValue = INITIAL_VALUE; 304 } 305 306 /** Leaves catch. */ 307 private void leaveAddingConditional() { 308 currentRangeValue = currentRangeValue.add(popValue().getRangeValue().add(BigInteger.ONE)); 309 } 310 311 /** 312 * Pushes the current range value on the range value stack. Pushes this token expression value 313 * on the expression value stack. 314 * @param expressionValue value of expression calculated for current token. 315 */ 316 private void pushValue(Integer expressionValue) { 317 rangeValues.push(currentRangeValue); 318 expressionValues.push(expressionValue); 319 currentRangeValue = INITIAL_VALUE; 320 } 321 322 /** 323 * Pops values from both stack of expression values and stack of range values. 324 * @return pair of head values from both of the stacks. 325 */ 326 private Values popValue() { 327 final int expressionValue = expressionValues.pop(); 328 return new Values(rangeValues.pop(), BigInteger.valueOf(expressionValue)); 329 } 330 331 /** Leaves try. */ 332 private void leaveMultiplyingConditional() { 333 currentRangeValue = currentRangeValue.add(BigInteger.ONE) 334 .multiply(popValue().getRangeValue().add(BigInteger.ONE)); 335 } 336 337 /** 338 * Calculates number of conditional operators, including inline ternary operatior, for a token. 339 * @param ast inspected token. 340 * @return number of conditional operators. 341 * @see <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.23"> 342 * Java Language Specification, §15.23</a> 343 * @see <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.24"> 344 * Java Language Specification, §15.24</a> 345 * @see <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25"> 346 * Java Language Specification, §15.25</a> 347 */ 348 private static int countConditionalOperators(DetailAST ast) { 349 int number = 0; 350 for (DetailAST child = ast.getFirstChild(); child != null; 351 child = child.getNextSibling()) { 352 final int type = child.getType(); 353 if (type == TokenTypes.LOR || type == TokenTypes.LAND) { 354 number++; 355 } 356 else if (type == TokenTypes.QUESTION) { 357 number += 2; 358 } 359 number += countConditionalOperators(child); 360 } 361 return number; 362 } 363 364 /** 365 * Finds a leaf, which is the most distant from the root. 366 * @param ast the root of tree. 367 * @return the leaf. 368 */ 369 private static DetailAST getLastToken(DetailAST ast) { 370 final DetailAST lastChild = ast.getLastChild(); 371 final DetailAST result; 372 if (lastChild.getFirstChild() == null) { 373 result = lastChild; 374 } 375 else { 376 result = getLastToken(lastChild); 377 } 378 return result; 379 } 380 381 /** 382 * Counts number of case tokens subject to a case group token. 383 * @param ast case group token. 384 * @return number of case tokens. 385 */ 386 private static int countCaseTokens(DetailAST ast) { 387 int counter = 0; 388 for (DetailAST iterator = ast.getFirstChild(); iterator != null; 389 iterator = iterator.getNextSibling()) { 390 if (iterator.getType() == TokenTypes.LITERAL_CASE) { 391 counter++; 392 } 393 } 394 return counter; 395 } 396 397 /** 398 * Coordinates of token end. Used to prevent inline ternary 399 * operator from being processed twice. 400 */ 401 private static class TokenEnd { 402 /** End line of token. */ 403 private int endLineNo; 404 405 /** End column of token. */ 406 private int endColumnNo; 407 408 /** 409 * Sets end coordinates from given token. 410 * @param endToken token. 411 */ 412 public void setToken(DetailAST endToken) { 413 if (!isAfter(endToken)) { 414 endLineNo = endToken.getLineNo(); 415 endColumnNo = endToken.getColumnNo(); 416 } 417 } 418 419 /** Sets end token coordinates to the start of the file. */ 420 public void reset() { 421 endLineNo = 0; 422 endColumnNo = 0; 423 } 424 425 /** 426 * Checks if saved coordinates located after given token. 427 * @param ast given token. 428 * @return true, if saved coordinates located after given token. 429 */ 430 public boolean isAfter(DetailAST ast) { 431 final int lineNo = ast.getLineNo(); 432 final int columnNo = ast.getColumnNo(); 433 boolean isAfter = true; 434 if (lineNo > endLineNo 435 || lineNo == endLineNo 436 && columnNo > endColumnNo) { 437 isAfter = false; 438 } 439 return isAfter; 440 } 441 } 442 443 /** 444 * Class that store range value and expression value. 445 */ 446 private static class Values { 447 448 /** NP value for range. */ 449 private final BigInteger rangeValue; 450 451 /** NP value for expression. */ 452 private final BigInteger expressionValue; 453 454 /** 455 * Constructor that assigns all of class fields. 456 * @param valueOfRange NP value for range 457 * @param valueOfExpression NP value for expression 458 */ 459 Values(BigInteger valueOfRange, BigInteger valueOfExpression) { 460 rangeValue = valueOfRange; 461 expressionValue = valueOfExpression; 462 } 463 464 /** 465 * Returns NP value for range. 466 * @return NP value for range 467 */ 468 public BigInteger getRangeValue() { 469 return rangeValue; 470 } 471 472 /** 473 * Returns NP value for expression. 474 * @return NP value for expression 475 */ 476 public BigInteger getExpressionValue() { 477 return expressionValue; 478 } 479 480 } 481 482}