001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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;
030import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
031
032/**
033 * <div>
034 * Checks the NPATH complexity against a specified limit.
035 * </div>
036 *
037 * <p>
038 * The NPATH metric computes the number of possible execution paths through a
039 * function(method). It takes into account the nesting of conditional statements
040 * and multipart boolean expressions (A &amp;&amp; B, C || D, E ? F :G and
041 * their combinations).
042 * </p>
043 *
044 * <p>
045 * The NPATH metric was designed base on Cyclomatic complexity to avoid problem
046 * of Cyclomatic complexity metric like nesting level within a function(method).
047 * </p>
048 *
049 * <p>
050 * Metric was described at <a href="http://dl.acm.org/citation.cfm?id=42379">
051 * "NPATH: a measure of execution pathcomplexity and its applications"</a>.
052 * If you need detailed description of algorithm, please read that article,
053 * it is well written and have number of examples and details.
054 * </p>
055 *
056 * <p>
057 * Here is some quotes:
058 * </p>
059 * <blockquote>
060 * An NPATH threshold value of 200 has been established for a function.
061 * The value 200 is based on studies done at AT&amp;T Bell Laboratories [1988 year].
062 * </blockquote>
063 * <blockquote>
064 * Some of the most effective methods of reducing the NPATH value include:
065 * <ul>
066 * <li>
067 * distributing functionality;
068 * </li>
069 * <li>
070 * implementing multiple if statements as a switch statement;
071 * </li>
072 * <li>
073 * creating a separate function for logical expressions with a high count of
074 * variables and (&amp;&amp;) and or (||) operators.
075 * </li>
076 * </ul>
077 * </blockquote>
078 * <blockquote>
079 * Although strategies to reduce the NPATH complexity of functions are important,
080 * care must be taken not to distort the logical clarity of the software by
081 * applying a strategy to reduce the complexity of functions. That is, there is
082 * a point of diminishing return beyond which a further attempt at reduction of
083 * complexity distorts the logical clarity of the system structure.
084 * </blockquote>
085 * <div class="wrapper">
086 * <table>
087 * <caption>Examples</caption>
088 * <thead><tr><th>Structure</th><th>Complexity expression</th></tr></thead>
089 * <tr><td>if ([expr]) { [if-range] }</td><td>NP(if-range) + 1 + NP(expr)</td></tr>
090 * <tr><td>if ([expr]) { [if-range] } else { [else-range] }</td>
091 * <td>NP(if-range)+ NP(else-range) + NP(expr)</td></tr>
092 * <tr><td>while ([expr]) { [while-range] }</td><td>NP(while-range) + NP(expr) + 1</td></tr>
093 * <tr><td>do { [do-range] } while ([expr])</td><td>NP(do-range) + NP(expr) + 1</td></tr>
094 * <tr><td>for([expr1]; [expr2]; [expr3]) { [for-range] }</td>
095 * <td>NP(for-range) + NP(expr1)+ NP(expr2) + NP(expr3) + 1</td></tr>
096 * <tr><td>switch ([expr]) { case : [case-range] default: [default-range] }</td>
097 * <td>S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr)</td></tr>
098 * <tr><td>when[expr]</td><td>NP(expr) + 1</td></tr>
099 * <tr><td>[expr1] ? [expr2] : [expr3]</td><td>NP(expr1) + NP(expr2) + NP(expr3) + 2</td></tr>
100 * <tr><td>goto label</td><td>1</td></tr><tr><td>break</td><td>1</td></tr>
101 * <tr><td>Expressions</td>
102 * <td>Number of &amp;&amp; and || operators in expression. No operators - 0</td></tr>
103 * <tr><td>continue</td><td>1</td></tr><tr><td>return</td><td>1</td></tr>
104 * <tr><td>Statement (even sequential statements)</td><td>1</td></tr>
105 * <tr><td>Empty block {}</td><td>1</td></tr><tr><td>Function call</td><td>1</td>
106 * </tr><tr><td>Function(Method) declaration or Block</td><td>P(i=1:i=N)NP(Statement[i])</td></tr>
107 * </table>
108 * </div>
109 *
110 * <p>
111 * <b>Rationale:</b> Nejmeh says that his group had an informal NPATH limit of
112 * 200 on individual routines; functions(methods) that exceeded this value were
113 * candidates for further decomposition - or at least a closer look.
114 * <b>Please do not be fanatic with limit 200</b> - choose number that suites
115 * your project style. Limit 200 is empirical number base on some sources of at
116 * AT&amp;T Bell Laboratories of 1988 year.
117 * </p>
118 *
119 * @since 3.4
120 */
121// -@cs[AbbreviationAsWordInName] Can't change check name
122@FileStatefulCheck
123public final class NPathComplexityCheck extends AbstractCheck {
124
125    /**
126     * A key is pointing to the warning message text in "messages.properties"
127     * file.
128     */
129    public static final String MSG_KEY = "npathComplexity";
130
131    /** Tokens that are considered as case labels. */
132    private static final int[] CASE_LABEL_TOKENS = {
133        TokenTypes.EXPR,
134        TokenTypes.PATTERN_DEF,
135        TokenTypes.PATTERN_VARIABLE_DEF,
136        TokenTypes.RECORD_PATTERN_DEF,
137    };
138
139    /** Default allowed complexity. */
140    private static final int DEFAULT_MAX = 200;
141
142    /** The initial current value. */
143    private static final BigInteger INITIAL_VALUE = BigInteger.ZERO;
144
145    /**
146     * Stack of NP values for ranges.
147     */
148    private final Deque<BigInteger> rangeValues = new ArrayDeque<>();
149
150    /** Stack of NP values for expressions. */
151    private final Deque<Integer> expressionValues = new ArrayDeque<>();
152
153    /** Stack of belongs to range values for question operator. */
154    private final Deque<Boolean> afterValues = new ArrayDeque<>();
155
156    /**
157     * Range of the last processed expression. Used for checking that ternary operation
158     * which is a part of expression won't be processed for second time.
159     */
160    private final TokenEnd processingTokenEnd = new TokenEnd();
161
162    /** NP value for current range. */
163    private BigInteger currentRangeValue;
164
165    /** Specify the maximum threshold allowed. */
166    private int max = DEFAULT_MAX;
167
168    /** True, when branch is visited, but not leaved. */
169    private boolean branchVisited;
170
171    /**
172     * Setter to specify the maximum threshold allowed.
173     *
174     * @param max the maximum threshold
175     * @since 3.4
176     */
177    public void setMax(int max) {
178        this.max = max;
179    }
180
181    @Override
182    public int[] getDefaultTokens() {
183        return getRequiredTokens();
184    }
185
186    @Override
187    public int[] getAcceptableTokens() {
188        return getRequiredTokens();
189    }
190
191    @Override
192    public int[] getRequiredTokens() {
193        return new int[] {
194            TokenTypes.CTOR_DEF,
195            TokenTypes.METHOD_DEF,
196            TokenTypes.STATIC_INIT,
197            TokenTypes.INSTANCE_INIT,
198            TokenTypes.LITERAL_WHILE,
199            TokenTypes.LITERAL_DO,
200            TokenTypes.LITERAL_FOR,
201            TokenTypes.LITERAL_IF,
202            TokenTypes.LITERAL_ELSE,
203            TokenTypes.LITERAL_SWITCH,
204            TokenTypes.CASE_GROUP,
205            TokenTypes.LITERAL_TRY,
206            TokenTypes.LITERAL_CATCH,
207            TokenTypes.QUESTION,
208            TokenTypes.LITERAL_RETURN,
209            TokenTypes.LITERAL_DEFAULT,
210            TokenTypes.COMPACT_CTOR_DEF,
211            TokenTypes.SWITCH_RULE,
212            TokenTypes.LITERAL_WHEN,
213        };
214    }
215
216    @Override
217    public void beginTree(DetailAST rootAST) {
218        rangeValues.clear();
219        expressionValues.clear();
220        afterValues.clear();
221        processingTokenEnd.reset();
222        currentRangeValue = INITIAL_VALUE;
223        branchVisited = false;
224    }
225
226    @Override
227    public void visitToken(DetailAST ast) {
228        switch (ast.getType()) {
229            case TokenTypes.LITERAL_IF, TokenTypes.LITERAL_SWITCH,
230                 TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO,
231                 TokenTypes.LITERAL_FOR -> visitConditional(ast, 1);
232
233            case TokenTypes.QUESTION -> visitUnitaryOperator(ast, 2);
234
235            case TokenTypes.LITERAL_RETURN -> visitUnitaryOperator(ast, 0);
236
237            case TokenTypes.LITERAL_WHEN -> visitWhenExpression(ast, 1);
238
239            case TokenTypes.CASE_GROUP -> {
240                final int caseNumber = countCaseTokens(ast);
241                branchVisited = true;
242                pushValue(caseNumber);
243            }
244
245            case TokenTypes.SWITCH_RULE -> {
246                final int caseConstantNumber = countCaseConstants(ast);
247                branchVisited = true;
248                pushValue(caseConstantNumber);
249            }
250
251            case TokenTypes.LITERAL_ELSE -> {
252                branchVisited = true;
253                if (currentRangeValue.equals(BigInteger.ZERO)) {
254                    currentRangeValue = BigInteger.ONE;
255                }
256                pushValue(0);
257            }
258
259            case TokenTypes.LITERAL_TRY,
260                 TokenTypes.LITERAL_CATCH,
261                 TokenTypes.LITERAL_DEFAULT -> pushValue(1);
262
263            case TokenTypes.CTOR_DEF,
264                 TokenTypes.METHOD_DEF,
265                 TokenTypes.INSTANCE_INIT,
266                 TokenTypes.STATIC_INIT,
267                 TokenTypes.COMPACT_CTOR_DEF -> pushValue(0);
268
269            default -> {
270                // do nothing
271            }
272        }
273    }
274
275    @Override
276    public void leaveToken(DetailAST ast) {
277        switch (ast.getType()) {
278            case TokenTypes.LITERAL_WHILE,
279                 TokenTypes.LITERAL_DO,
280                 TokenTypes.LITERAL_FOR,
281                 TokenTypes.LITERAL_IF,
282                 TokenTypes.LITERAL_SWITCH,
283                 TokenTypes.LITERAL_WHEN -> leaveConditional();
284
285            case TokenTypes.LITERAL_TRY -> leaveMultiplyingConditional();
286
287            case TokenTypes.LITERAL_RETURN,
288                 TokenTypes.QUESTION -> leaveUnitaryOperator();
289
290            case TokenTypes.LITERAL_CATCH -> leaveAddingConditional();
291
292            case TokenTypes.LITERAL_DEFAULT -> leaveBranch();
293
294            case TokenTypes.LITERAL_ELSE,
295                 TokenTypes.CASE_GROUP,
296                 TokenTypes.SWITCH_RULE -> {
297                leaveBranch();
298                branchVisited = false;
299            }
300
301            case TokenTypes.CTOR_DEF,
302                 TokenTypes.METHOD_DEF,
303                 TokenTypes.INSTANCE_INIT,
304                 TokenTypes.STATIC_INIT,
305                 TokenTypes.COMPACT_CTOR_DEF -> leaveMethodDef(ast);
306
307            default -> {
308                // do nothing
309            }
310        }
311    }
312
313    /**
314     * Visits if, while, do-while, for and switch tokens - all of them have expression in
315     * parentheses which is used for calculation.
316     *
317     * @param ast visited token.
318     * @param basicBranchingFactor default number of branches added.
319     */
320    private void visitConditional(DetailAST ast, int basicBranchingFactor) {
321        int expressionValue = basicBranchingFactor;
322        DetailAST bracketed;
323        for (bracketed = ast.findFirstToken(TokenTypes.LPAREN);
324                bracketed.getType() != TokenTypes.RPAREN;
325                bracketed = bracketed.getNextSibling()) {
326            expressionValue += countConditionalOperators(bracketed);
327        }
328        processingTokenEnd.setToken(bracketed);
329        pushValue(expressionValue);
330    }
331
332    /**
333     * Visits when expression token. There is no guarantee that when expression will be
334     * bracketed, so we don't use visitConditional method.
335     *
336     * @param ast visited token.
337     * @param basicBranchingFactor default number of branches added.
338     */
339    private void visitWhenExpression(DetailAST ast, int basicBranchingFactor) {
340        final int expressionValue = basicBranchingFactor + countConditionalOperators(ast);
341        processingTokenEnd.setToken(getLastToken(ast));
342        pushValue(expressionValue);
343    }
344
345    /**
346     * Visits ternary operator (?:) and return tokens. They differ from those processed by
347     * visitConditional method in that their expression isn't bracketed.
348     *
349     * @param ast visited token.
350     * @param basicBranchingFactor number of branches inherently added by this token.
351     */
352    private void visitUnitaryOperator(DetailAST ast, int basicBranchingFactor) {
353        final boolean isAfter = processingTokenEnd.isAfter(ast);
354        afterValues.push(isAfter);
355        if (!isAfter) {
356            processingTokenEnd.setToken(getLastToken(ast));
357            final int expressionValue = basicBranchingFactor + countConditionalOperators(ast);
358            pushValue(expressionValue);
359        }
360    }
361
362    /**
363     * Leaves ternary operator (?:) and return tokens.
364     */
365    private void leaveUnitaryOperator() {
366        if (Boolean.FALSE.equals(afterValues.pop())) {
367            final Values valuePair = popValue();
368            BigInteger basicRangeValue = valuePair.getRangeValue();
369            BigInteger expressionValue = valuePair.getExpressionValue();
370            if (expressionValue.equals(BigInteger.ZERO)) {
371                expressionValue = BigInteger.ONE;
372            }
373            if (basicRangeValue.equals(BigInteger.ZERO)) {
374                basicRangeValue = BigInteger.ONE;
375            }
376            currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue);
377        }
378    }
379
380    /** Leaves while, do, for, if, ternary (?::), return or switch. */
381    private void leaveConditional() {
382        final Values valuePair = popValue();
383        final BigInteger expressionValue = valuePair.getExpressionValue();
384        BigInteger basicRangeValue = valuePair.getRangeValue();
385        if (currentRangeValue.equals(BigInteger.ZERO)) {
386            currentRangeValue = BigInteger.ONE;
387        }
388        if (basicRangeValue.equals(BigInteger.ZERO)) {
389            basicRangeValue = BigInteger.ONE;
390        }
391        currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue);
392    }
393
394    /** Leaves else, default or case group tokens. */
395    private void leaveBranch() {
396        final Values valuePair = popValue();
397        final BigInteger basicRangeValue = valuePair.getRangeValue();
398        final BigInteger expressionValue = valuePair.getExpressionValue();
399        if (branchVisited && currentRangeValue.equals(BigInteger.ZERO)) {
400            currentRangeValue = BigInteger.ONE;
401        }
402        currentRangeValue = currentRangeValue.subtract(BigInteger.ONE)
403                .add(basicRangeValue)
404                .add(expressionValue);
405    }
406
407    /**
408     * Process the end of a method definition.
409     *
410     * @param ast the token type representing the method definition
411     */
412    private void leaveMethodDef(DetailAST ast) {
413        final BigInteger bigIntegerMax = BigInteger.valueOf(max);
414        if (currentRangeValue.compareTo(bigIntegerMax) > 0) {
415            log(ast, MSG_KEY, currentRangeValue, bigIntegerMax);
416        }
417        popValue();
418        currentRangeValue = INITIAL_VALUE;
419    }
420
421    /** Leaves catch. */
422    private void leaveAddingConditional() {
423        currentRangeValue = currentRangeValue.add(popValue().getRangeValue().add(BigInteger.ONE));
424    }
425
426    /**
427     * Pushes the current range value on the range value stack. Pushes this token expression value
428     * on the expression value stack.
429     *
430     * @param expressionValue value of expression calculated for current token.
431     */
432    private void pushValue(Integer expressionValue) {
433        rangeValues.push(currentRangeValue);
434        expressionValues.push(expressionValue);
435        currentRangeValue = INITIAL_VALUE;
436    }
437
438    /**
439     * Pops values from both stack of expression values and stack of range values.
440     *
441     * @return pair of head values from both of the stacks.
442     */
443    private Values popValue() {
444        final int expressionValue = expressionValues.pop();
445        return new Values(rangeValues.pop(), BigInteger.valueOf(expressionValue));
446    }
447
448    /** Leaves try. */
449    private void leaveMultiplyingConditional() {
450        currentRangeValue = currentRangeValue.add(BigInteger.ONE)
451                .multiply(popValue().getRangeValue().add(BigInteger.ONE));
452    }
453
454    /**
455     * Calculates number of conditional operators, including inline ternary operator, for a token.
456     *
457     * @param ast inspected token.
458     * @return number of conditional operators.
459     * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.23">
460     *     Java Language Specification, &sect;15.23</a>
461     * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.24">
462     *     Java Language Specification, &sect;15.24</a>
463     * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25">
464     *     Java Language Specification, &sect;15.25</a>
465     */
466    private static int countConditionalOperators(DetailAST ast) {
467        int number = 0;
468        for (DetailAST child = ast.getFirstChild(); child != null;
469                child = child.getNextSibling()) {
470            final int type = child.getType();
471            if (type == TokenTypes.LOR || type == TokenTypes.LAND) {
472                number++;
473            }
474            else if (type == TokenTypes.QUESTION) {
475                number += 2;
476            }
477            number += countConditionalOperators(child);
478        }
479        return number;
480    }
481
482    /**
483     * Finds a leaf, which is the most distant from the root.
484     *
485     * @param ast the root of tree.
486     * @return the leaf.
487     */
488    private static DetailAST getLastToken(DetailAST ast) {
489        final DetailAST lastChild = ast.getLastChild();
490        final DetailAST result;
491        if (lastChild.getFirstChild() == null) {
492            result = lastChild;
493        }
494        else {
495            result = getLastToken(lastChild);
496        }
497        return result;
498    }
499
500    /**
501     * Counts number of case tokens subject to a case group token.
502     *
503     * @param ast case group token.
504     * @return number of case tokens.
505     */
506    private static int countCaseTokens(DetailAST ast) {
507        int counter = 0;
508        for (DetailAST iterator = ast.getFirstChild(); iterator != null;
509                iterator = iterator.getNextSibling()) {
510            if (iterator.getType() == TokenTypes.LITERAL_CASE) {
511                counter++;
512            }
513        }
514        return counter;
515    }
516
517    /**
518     * Counts number of case constants tokens in a switch labeled rule.
519     *
520     * @param ast switch rule token.
521     * @return number of case constant tokens.
522     */
523    private static int countCaseConstants(DetailAST ast) {
524        int counter = 0;
525        final DetailAST literalCase = ast.getFirstChild();
526
527        for (DetailAST node = literalCase.getFirstChild(); node != null;
528                    node = node.getNextSibling()) {
529            if (TokenUtil.isOfType(node, CASE_LABEL_TOKENS)) {
530                counter++;
531            }
532        }
533
534        return counter;
535    }
536
537    /**
538     * Coordinates of token end. Used to prevent inline ternary
539     * operator from being processed twice.
540     */
541    private static final class TokenEnd {
542
543        /** End line of token. */
544        private int endLineNo;
545
546        /** End column of token. */
547        private int endColumnNo;
548
549        /**
550         * Sets end coordinates from given token.
551         *
552         * @param endToken token.
553         */
554        public void setToken(DetailAST endToken) {
555            if (!isAfter(endToken)) {
556                endLineNo = endToken.getLineNo();
557                endColumnNo = endToken.getColumnNo();
558            }
559        }
560
561        /** Sets end token coordinates to the start of the file. */
562        public void reset() {
563            endLineNo = 0;
564            endColumnNo = 0;
565        }
566
567        /**
568         * Checks if saved coordinates located after given token.
569         *
570         * @param ast given token.
571         * @return true, if saved coordinates located after given token.
572         */
573        public boolean isAfter(DetailAST ast) {
574            final int lineNo = ast.getLineNo();
575            final int columnNo = ast.getColumnNo();
576            return lineNo <= endLineNo
577                && (lineNo != endLineNo
578                || columnNo <= endColumnNo);
579        }
580
581    }
582
583    /**
584     * Class that store range value and expression value.
585     */
586    private static final class Values {
587
588        /** NP value for range. */
589        private final BigInteger rangeValue;
590
591        /** NP value for expression. */
592        private final BigInteger expressionValue;
593
594        /**
595         * Constructor that assigns all of class fields.
596         *
597         * @param valueOfRange NP value for range
598         * @param valueOfExpression NP value for expression
599         */
600        private Values(BigInteger valueOfRange, BigInteger valueOfExpression) {
601            rangeValue = valueOfRange;
602            expressionValue = valueOfExpression;
603        }
604
605        /**
606         * Returns NP value for range.
607         *
608         * @return NP value for range
609         */
610        public BigInteger getRangeValue() {
611            return rangeValue;
612        }
613
614        /**
615         * Returns NP value for expression.
616         *
617         * @return NP value for expression
618         */
619        public BigInteger getExpressionValue() {
620            return expressionValue;
621        }
622
623    }
624
625}