All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.puppycrawl.tools.checkstyle.checks.metrics.NPathComplexityCheck Maven / Gradle / Ivy

Go to download

Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard

There is a newer version: 10.18.1
Show newest version
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.checks.metrics;

import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.Deque;

import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

/**
 * 

* Checks the NPATH complexity against a specified limit. *

*

* The NPATH metric computes the number of possible execution paths through a * function(method). It takes into account the nesting of conditional statements * and multi-part boolean expressions (A && B, C || D, E ? F :G and * their combinations). *

*

* The NPATH metric was designed base on Cyclomatic complexity to avoid problem * of Cyclomatic complexity metric like nesting level within a function(method). *

*

* Metric was described at * "NPATH: a measure of execution pathcomplexity and its applications". * If you need detailed description of algorithm, please read that article, * it is well written and have number of examples and details. *

*

* Here is some quotes: *

*
* An NPATH threshold value of 200 has been established for a function. * The value 200 is based on studies done at AT&T Bell Laboratories [1988 year]. *
*
* Some of the most effective methods of reducing the NPATH value include: *
    *
  • * distributing functionality; *
  • *
  • * implementing multiple if statements as a switch statement; *
  • *
  • * creating a separate function for logical expressions with a high count of * variables and (&&) and or (||) operators. *
  • *
*
*
* Although strategies to reduce the NPATH complexity of functions are important, * care must be taken not to distort the logical clarity of the software by * applying a strategy to reduce the complexity of functions. That is, there is * a point of diminishing return beyond which a further attempt at reduction of * complexity distorts the logical clarity of the system structure. *
* * * * * * * * * * * * * * * * * * * * *
Examples
StructureComplexity expression
if ([expr]) { [if-range] }NP(if-range) + 1 + NP(expr)
if ([expr]) { [if-range] } else { [else-range] }NP(if-range)+ NP(else-range) + NP(expr)
while ([expr]) { [while-range] }NP(while-range) + NP(expr) + 1
do { [do-range] } while ([expr])NP(do-range) + NP(expr) + 1
for([expr1]; [expr2]; [expr3]) { [for-range] }NP(for-range) + NP(expr1)+ NP(expr2) + NP(expr3) + 1
switch ([expr]) { case : [case-range] default: [default-range] }S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr)
[expr1] ? [expr2] : [expr3]NP(expr1) + NP(expr2) + NP(expr3) + 2
goto label1
break1
ExpressionsNumber of && and || operators in expression. No operators - 0
continue1
return1
Statement (even sequential statements)1
Empty block {}1
Function call1
Function(Method) declaration or BlockP(i=1:i=N)NP(Statement[i])
*

* Rationale: Nejmeh says that his group had an informal NPATH limit of * 200 on individual routines; functions(methods) that exceeded this value were * candidates for further decomposition - or at least a closer look. * Please do not be fanatic with limit 200 - choose number that suites * your project style. Limit 200 is empirical number base on some sources of at * AT&T Bell Laboratories of 1988 year. *

*
    *
  • * Property {@code max} - Specify the maximum threshold allowed. * Default value is {@code 200}. *
  • *
*

* To configure the check: *

*
 * <module name="NPathComplexity"/>
 * 
*

* To configure the check with a threshold of 1000: *

*
 * <module name="NPathComplexity">
 *   <property name="max" value="1000"/>
 * </module>
 * 
* * @since 3.4 */ // -@cs[AbbreviationAsWordInName] Can't change check name @FileStatefulCheck public final class NPathComplexityCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY = "npathComplexity"; /** Default allowed complexity. */ private static final int DEFAULT_MAX = 200; /** The initial current value. */ private static final BigInteger INITIAL_VALUE = BigInteger.ZERO; /** * Stack of NP values for ranges. */ private final Deque rangeValues = new ArrayDeque<>(); /** Stack of NP values for expressions. */ private final Deque expressionValues = new ArrayDeque<>(); /** Stack of belongs to range values for question operator. */ private final Deque afterValues = new ArrayDeque<>(); /** * Range of the last processed expression. Used for checking that ternary operation * which is a part of expression won't be processed for second time. */ private final TokenEnd processingTokenEnd = new TokenEnd(); /** NP value for current range. */ private BigInteger currentRangeValue = INITIAL_VALUE; /** Specify the maximum threshold allowed. */ private int max = DEFAULT_MAX; /** True, when branch is visited, but not leaved. */ private boolean branchVisited; /** * Setter to specify the maximum threshold allowed. * * @param max the maximum threshold */ public void setMax(int max) { this.max = max; } @Override public int[] getDefaultTokens() { return getRequiredTokens(); } @Override public int[] getAcceptableTokens() { return getRequiredTokens(); } @Override public int[] getRequiredTokens() { return new int[] { TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_SWITCH, TokenTypes.CASE_GROUP, TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH, TokenTypes.QUESTION, TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_DEFAULT, }; } @Override public void beginTree(DetailAST rootAST) { rangeValues.clear(); expressionValues.clear(); afterValues.clear(); processingTokenEnd.reset(); currentRangeValue = INITIAL_VALUE; branchVisited = false; } @Override public void visitToken(DetailAST ast) { switch (ast.getType()) { case TokenTypes.LITERAL_IF: case TokenTypes.LITERAL_SWITCH: case TokenTypes.LITERAL_WHILE: case TokenTypes.LITERAL_DO: case TokenTypes.LITERAL_FOR: visitConditional(ast, 1); break; case TokenTypes.QUESTION: visitUnitaryOperator(ast, 2); break; case TokenTypes.LITERAL_RETURN: visitUnitaryOperator(ast, 0); break; case TokenTypes.CASE_GROUP: final int caseNumber = countCaseTokens(ast); branchVisited = true; pushValue(caseNumber); break; case TokenTypes.LITERAL_ELSE: branchVisited = true; if (currentRangeValue.equals(BigInteger.ZERO)) { currentRangeValue = BigInteger.ONE; } pushValue(0); break; case TokenTypes.LITERAL_TRY: case TokenTypes.LITERAL_CATCH: case TokenTypes.LITERAL_DEFAULT: pushValue(1); break; case TokenTypes.CTOR_DEF: case TokenTypes.METHOD_DEF: case TokenTypes.INSTANCE_INIT: case TokenTypes.STATIC_INIT: pushValue(0); break; default: break; } } @Override public void leaveToken(DetailAST ast) { switch (ast.getType()) { case TokenTypes.LITERAL_WHILE: case TokenTypes.LITERAL_DO: case TokenTypes.LITERAL_FOR: case TokenTypes.LITERAL_IF: case TokenTypes.LITERAL_SWITCH: leaveConditional(); break; case TokenTypes.LITERAL_TRY: leaveMultiplyingConditional(); break; case TokenTypes.LITERAL_RETURN: case TokenTypes.QUESTION: leaveUnitaryOperator(); break; case TokenTypes.LITERAL_CATCH: leaveAddingConditional(); break; case TokenTypes.LITERAL_DEFAULT: leaveBranch(); break; case TokenTypes.LITERAL_ELSE: case TokenTypes.CASE_GROUP: leaveBranch(); branchVisited = false; break; case TokenTypes.CTOR_DEF: case TokenTypes.METHOD_DEF: case TokenTypes.INSTANCE_INIT: case TokenTypes.STATIC_INIT: leaveMethodDef(ast); break; default: break; } } /** * Visits if, while, do-while, for and switch tokens - all of them have expression in * parentheses which is used for calculation. * @param ast visited token. * @param basicBranchingFactor default number of branches added. */ private void visitConditional(DetailAST ast, int basicBranchingFactor) { int expressionValue = basicBranchingFactor; DetailAST bracketed; for (bracketed = ast.findFirstToken(TokenTypes.LPAREN).getNextSibling(); bracketed.getType() != TokenTypes.RPAREN; bracketed = bracketed.getNextSibling()) { expressionValue += countConditionalOperators(bracketed); } processingTokenEnd.setToken(bracketed); pushValue(expressionValue); } /** * Visits ternary operator (?:) and return tokens. They differ from those processed by * visitConditional method in that their expression isn't bracketed. * @param ast visited token. * @param basicBranchingFactor number of branches inherently added by this token. */ private void visitUnitaryOperator(DetailAST ast, int basicBranchingFactor) { final boolean isAfter = processingTokenEnd.isAfter(ast); afterValues.push(isAfter); if (!isAfter) { processingTokenEnd.setToken(getLastToken(ast)); final int expressionValue = basicBranchingFactor + countConditionalOperators(ast); pushValue(expressionValue); } } /** * Leaves ternary operator (?:) and return tokens. */ private void leaveUnitaryOperator() { if (!afterValues.pop()) { final Values valuePair = popValue(); BigInteger basicRangeValue = valuePair.getRangeValue(); BigInteger expressionValue = valuePair.getExpressionValue(); if (expressionValue.equals(BigInteger.ZERO)) { expressionValue = BigInteger.ONE; } if (basicRangeValue.equals(BigInteger.ZERO)) { basicRangeValue = BigInteger.ONE; } currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue); } } /** Leaves while, do, for, if, ternary (?::), return or switch. */ private void leaveConditional() { final Values valuePair = popValue(); final BigInteger expressionValue = valuePair.getExpressionValue(); BigInteger basicRangeValue = valuePair.getRangeValue(); if (currentRangeValue.equals(BigInteger.ZERO)) { currentRangeValue = BigInteger.ONE; } if (basicRangeValue.equals(BigInteger.ZERO)) { basicRangeValue = BigInteger.ONE; } currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue); } /** Leaves else, default or case group tokens. */ private void leaveBranch() { final Values valuePair = popValue(); final BigInteger basicRangeValue = valuePair.getRangeValue(); final BigInteger expressionValue = valuePair.getExpressionValue(); if (branchVisited && currentRangeValue.equals(BigInteger.ZERO)) { currentRangeValue = BigInteger.ONE; } currentRangeValue = currentRangeValue.subtract(BigInteger.ONE) .add(basicRangeValue) .add(expressionValue); } /** * Process the end of a method definition. * @param ast the token type representing the method definition */ private void leaveMethodDef(DetailAST ast) { final BigInteger bigIntegerMax = BigInteger.valueOf(max); if (currentRangeValue.compareTo(bigIntegerMax) > 0) { log(ast, MSG_KEY, currentRangeValue, bigIntegerMax); } popValue(); currentRangeValue = INITIAL_VALUE; } /** Leaves catch. */ private void leaveAddingConditional() { currentRangeValue = currentRangeValue.add(popValue().getRangeValue().add(BigInteger.ONE)); } /** * Pushes the current range value on the range value stack. Pushes this token expression value * on the expression value stack. * @param expressionValue value of expression calculated for current token. */ private void pushValue(Integer expressionValue) { rangeValues.push(currentRangeValue); expressionValues.push(expressionValue); currentRangeValue = INITIAL_VALUE; } /** * Pops values from both stack of expression values and stack of range values. * @return pair of head values from both of the stacks. */ private Values popValue() { final int expressionValue = expressionValues.pop(); return new Values(rangeValues.pop(), BigInteger.valueOf(expressionValue)); } /** Leaves try. */ private void leaveMultiplyingConditional() { currentRangeValue = currentRangeValue.add(BigInteger.ONE) .multiply(popValue().getRangeValue().add(BigInteger.ONE)); } /** * Calculates number of conditional operators, including inline ternary operator, for a token. * @param ast inspected token. * @return number of conditional operators. * @see * Java Language Specification, §15.23 * @see * Java Language Specification, §15.24 * @see * Java Language Specification, §15.25 */ private static int countConditionalOperators(DetailAST ast) { int number = 0; for (DetailAST child = ast.getFirstChild(); child != null; child = child.getNextSibling()) { final int type = child.getType(); if (type == TokenTypes.LOR || type == TokenTypes.LAND) { number++; } else if (type == TokenTypes.QUESTION) { number += 2; } number += countConditionalOperators(child); } return number; } /** * Finds a leaf, which is the most distant from the root. * @param ast the root of tree. * @return the leaf. */ private static DetailAST getLastToken(DetailAST ast) { final DetailAST lastChild = ast.getLastChild(); final DetailAST result; if (lastChild.getFirstChild() == null) { result = lastChild; } else { result = getLastToken(lastChild); } return result; } /** * Counts number of case tokens subject to a case group token. * @param ast case group token. * @return number of case tokens. */ private static int countCaseTokens(DetailAST ast) { int counter = 0; for (DetailAST iterator = ast.getFirstChild(); iterator != null; iterator = iterator.getNextSibling()) { if (iterator.getType() == TokenTypes.LITERAL_CASE) { counter++; } } return counter; } /** * Coordinates of token end. Used to prevent inline ternary * operator from being processed twice. */ private static class TokenEnd { /** End line of token. */ private int endLineNo; /** End column of token. */ private int endColumnNo; /** * Sets end coordinates from given token. * @param endToken token. */ public void setToken(DetailAST endToken) { if (!isAfter(endToken)) { endLineNo = endToken.getLineNo(); endColumnNo = endToken.getColumnNo(); } } /** Sets end token coordinates to the start of the file. */ public void reset() { endLineNo = 0; endColumnNo = 0; } /** * Checks if saved coordinates located after given token. * @param ast given token. * @return true, if saved coordinates located after given token. */ public boolean isAfter(DetailAST ast) { final int lineNo = ast.getLineNo(); final int columnNo = ast.getColumnNo(); boolean isAfter = true; if (lineNo > endLineNo || lineNo == endLineNo && columnNo > endColumnNo) { isAfter = false; } return isAfter; } } /** * Class that store range value and expression value. */ private static class Values { /** NP value for range. */ private final BigInteger rangeValue; /** NP value for expression. */ private final BigInteger expressionValue; /** * Constructor that assigns all of class fields. * @param valueOfRange NP value for range * @param valueOfExpression NP value for expression */ /* package */ Values(BigInteger valueOfRange, BigInteger valueOfExpression) { rangeValue = valueOfRange; expressionValue = valueOfExpression; } /** * Returns NP value for range. * @return NP value for range */ public BigInteger getRangeValue() { return rangeValue; } /** * Returns NP value for expression. * @return NP value for expression */ public BigInteger getExpressionValue() { return expressionValue; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy