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 &amp;&amp;} 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 * &lt;module name="CyclomaticComplexity"/&gt;
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 *       &amp;&amp; a == c) { // 4, &amp;&amp; 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 &lt; 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 * &lt;module name="CyclomaticComplexity"&gt;
148 *   &lt;property name="max" value="4"/&gt;
149 *   &lt;property name="tokens" value="LITERAL_WHILE, LITERAL_DO"/&gt;
150 * &lt;/module&gt;
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 &lt; b // 2, while
161 *       &amp;&amp; a &gt; 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 &gt; 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 * &lt;module name="CyclomaticComplexity"&gt;
184 *   &lt;property name="switchBlockAsSingleDecisionPoint" value="true"/&gt;
185 * &lt;/module&gt;
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 *       &amp;&amp; b == c) { // 4, &amp;&amp; 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 &gt; 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}