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 * Default value is {@code 10}.
064 * </li>
065 * <li>
066 * Property {@code switchBlockAsSingleDecisionPoint} - Control whether to treat
067 * the whole switch block as a single decision point.
068 * Default value is {@code false}.
069 * </li>
070 * <li>
071 * Property {@code tokens} - tokens to check
072 * Default value is:
073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
074 * LITERAL_WHILE</a>,
075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
076 * LITERAL_DO</a>,
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
078 * LITERAL_FOR</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
080 * LITERAL_IF</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
082 * LITERAL_SWITCH</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE">
084 * LITERAL_CASE</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
086 * LITERAL_CATCH</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
088 * QUESTION</a>,
089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND">
090 * LAND</a>,
091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR">
092 * LOR</a>.
093 * </li>
094 * </ul>
095 * <p>
096 * To configure the check:
097 * </p>
098 * <pre>
099 * &lt;module name="CyclomaticComplexity"/&gt;
100 * </pre>
101 * <p>
102 * To configure the check with a threshold of 15:
103 * </p>
104 * <pre>
105 * &lt;module name="CyclomaticComplexity"&gt;
106 *   &lt;property name="max" value="15"/&gt;
107 * &lt;/module&gt;
108 * </pre>
109 * <p>
110 * Explanation on how complexity is calculated (switchBlockAsSingleDecisionPoint is set to false):
111 * </p>
112 * <pre>
113 * class CC {
114 *   // Cyclomatic Complexity = 12
115 *   public void doSmth()  {         // 1
116 *     if (a == b)  {                // 2
117 *       if (a1 == b1                // 3
118 *         &amp;&amp; c1 == d1) {            // 4
119 *         fiddle();
120 *       }
121 *       else if (a2 == b2           // 5
122 *               || c1 &lt; d1) {       // 6
123 *         fiddle();
124 *       }
125 *       else {
126 *         fiddle();
127 *       }
128 *     }
129 *     else if (c == d) {            // 7
130 *       while (c == d) {            // 8
131 *         fiddle();
132 *       }
133 *     }
134 *     else if (e == f) {
135 *       for (n = 0; n &lt; h            // 9
136 *             || n &lt; 6; n++) {       // 10
137 *         fiddle();
138 *       }
139 *     }
140 *       else {
141 *         switch (z) {
142 *           case 1:                  // 11
143 *             fiddle();
144 *             break;
145 *           case 2:                  // 12
146 *             fiddle();
147 *             break;
148 *           default:
149 *             fiddle();
150 *             break;
151 *       }
152 *     }
153 *   }
154 * }
155 * </pre>
156 * <p>
157 * Explanation on how complexity is calculated (switchBlockAsSingleDecisionPoint is set to true):
158 * </p>
159 * <pre>
160 * class SwitchExample {
161 *   // Cyclomatic Complexity = 2
162 *   public void doSmth()  {          // 1
163 *     int z = 1;
164 *     switch (z) {                   // 2
165 *       case 1:
166 *         foo1();
167 *         break;
168 *       case 2:
169 *         foo2();
170 *         break;
171 *       default:
172 *         fooDefault();
173 *         break;
174 *      }
175 *   }
176 * }
177 * </pre>
178 *
179 * @since 3.2
180 */
181@FileStatefulCheck
182public class CyclomaticComplexityCheck
183    extends AbstractCheck {
184
185    /**
186     * A key is pointing to the warning message text in "messages.properties"
187     * file.
188     */
189    public static final String MSG_KEY = "cyclomaticComplexity";
190
191    /** The initial current value. */
192    private static final BigInteger INITIAL_VALUE = BigInteger.ONE;
193
194    /** Default allowed complexity. */
195    private static final int DEFAULT_COMPLEXITY_VALUE = 10;
196
197    /** Stack of values - all but the current value. */
198    private final Deque<BigInteger> valueStack = new ArrayDeque<>();
199
200    /** Control whether to treat the whole switch block as a single decision point. */
201    private boolean switchBlockAsSingleDecisionPoint;
202
203    /** The current value. */
204    private BigInteger currentValue = INITIAL_VALUE;
205
206    /** Specify the maximum threshold allowed. */
207    private int max = DEFAULT_COMPLEXITY_VALUE;
208
209    /**
210     * Setter to control whether to treat the whole switch block as a single decision point.
211     *
212     * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch
213     *                                          block as a single decision point.
214     */
215    public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) {
216        this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint;
217    }
218
219    /**
220     * Setter to specify the maximum threshold allowed.
221     *
222     * @param max the maximum threshold
223     */
224    public final void setMax(int max) {
225        this.max = max;
226    }
227
228    @Override
229    public int[] getDefaultTokens() {
230        return new int[] {
231            TokenTypes.CTOR_DEF,
232            TokenTypes.METHOD_DEF,
233            TokenTypes.INSTANCE_INIT,
234            TokenTypes.STATIC_INIT,
235            TokenTypes.LITERAL_WHILE,
236            TokenTypes.LITERAL_DO,
237            TokenTypes.LITERAL_FOR,
238            TokenTypes.LITERAL_IF,
239            TokenTypes.LITERAL_SWITCH,
240            TokenTypes.LITERAL_CASE,
241            TokenTypes.LITERAL_CATCH,
242            TokenTypes.QUESTION,
243            TokenTypes.LAND,
244            TokenTypes.LOR,
245        };
246    }
247
248    @Override
249    public int[] getAcceptableTokens() {
250        return new int[] {
251            TokenTypes.CTOR_DEF,
252            TokenTypes.METHOD_DEF,
253            TokenTypes.INSTANCE_INIT,
254            TokenTypes.STATIC_INIT,
255            TokenTypes.LITERAL_WHILE,
256            TokenTypes.LITERAL_DO,
257            TokenTypes.LITERAL_FOR,
258            TokenTypes.LITERAL_IF,
259            TokenTypes.LITERAL_SWITCH,
260            TokenTypes.LITERAL_CASE,
261            TokenTypes.LITERAL_CATCH,
262            TokenTypes.QUESTION,
263            TokenTypes.LAND,
264            TokenTypes.LOR,
265        };
266    }
267
268    @Override
269    public final int[] getRequiredTokens() {
270        return new int[] {
271            TokenTypes.CTOR_DEF,
272            TokenTypes.METHOD_DEF,
273            TokenTypes.INSTANCE_INIT,
274            TokenTypes.STATIC_INIT,
275        };
276    }
277
278    @Override
279    public void visitToken(DetailAST ast) {
280        switch (ast.getType()) {
281            case TokenTypes.CTOR_DEF:
282            case TokenTypes.METHOD_DEF:
283            case TokenTypes.INSTANCE_INIT:
284            case TokenTypes.STATIC_INIT:
285                visitMethodDef();
286                break;
287            default:
288                visitTokenHook(ast);
289        }
290    }
291
292    @Override
293    public void leaveToken(DetailAST ast) {
294        switch (ast.getType()) {
295            case TokenTypes.CTOR_DEF:
296            case TokenTypes.METHOD_DEF:
297            case TokenTypes.INSTANCE_INIT:
298            case TokenTypes.STATIC_INIT:
299                leaveMethodDef(ast);
300                break;
301            default:
302                break;
303        }
304    }
305
306    /**
307     * Hook called when visiting a token. Will not be called the method
308     * definition tokens.
309     *
310     * @param ast the token being visited
311     */
312    private void visitTokenHook(DetailAST ast) {
313        if (switchBlockAsSingleDecisionPoint) {
314            if (ast.getType() != TokenTypes.LITERAL_CASE) {
315                incrementCurrentValue(BigInteger.ONE);
316            }
317        }
318        else if (ast.getType() != TokenTypes.LITERAL_SWITCH) {
319            incrementCurrentValue(BigInteger.ONE);
320        }
321    }
322
323    /**
324     * Process the end of a method definition.
325     *
326     * @param ast the token representing the method definition
327     */
328    private void leaveMethodDef(DetailAST ast) {
329        final BigInteger bigIntegerMax = BigInteger.valueOf(max);
330        if (currentValue.compareTo(bigIntegerMax) > 0) {
331            log(ast, MSG_KEY, currentValue, bigIntegerMax);
332        }
333        popValue();
334    }
335
336    /**
337     * Increments the current value by a specified amount.
338     *
339     * @param amount the amount to increment by
340     */
341    private void incrementCurrentValue(BigInteger amount) {
342        currentValue = currentValue.add(amount);
343    }
344
345    /** Push the current value on the stack. */
346    private void pushValue() {
347        valueStack.push(currentValue);
348        currentValue = INITIAL_VALUE;
349    }
350
351    /**
352     * Pops a value off the stack and makes it the current value.
353     */
354    private void popValue() {
355        currentValue = valueStack.pop();
356    }
357
358    /** Process the start of the method definition. */
359    private void visitMethodDef() {
360        pushValue();
361    }
362
363}