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