1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2019 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.metrics;
21
22 import java.math.BigInteger;
23 import java.util.ArrayDeque;
24 import java.util.Deque;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30
31 /**
32 * Checks cyclomatic complexity against a specified limit. The complexity is
33 * measured by the number of "if", "while", "do", "for", "?:", "catch",
34 * "switch", "case", "&&" and "||" statements (plus one) in the body of
35 * the member. It is a measure of the minimum number of possible paths through
36 * the source and therefore the number of required tests. Generally 1-4 is
37 * considered good, 5-7 ok, 8-10 consider re-factoring, and 11+ re-factor now!
38 *
39 * <p>Check has following properties:
40 *
41 * <p><b>switchBlockAsSingleDecisionPoint</b> - controls whether to treat the whole switch
42 * block as a single decision point. Default value is <b>false</b>
43 *
44 *
45 */
46 @FileStatefulCheck
47 public class CyclomaticComplexityCheck
48 extends AbstractCheck {
49
50 /**
51 * A key is pointing to the warning message text in "messages.properties"
52 * file.
53 */
54 public static final String MSG_KEY = "cyclomaticComplexity";
55
56 /** The initial current value. */
57 private static final BigInteger INITIAL_VALUE = BigInteger.ONE;
58
59 /** Default allowed complexity. */
60 private static final int DEFAULT_COMPLEXITY_VALUE = 10;
61
62 /** Stack of values - all but the current value. */
63 private final Deque<BigInteger> valueStack = new ArrayDeque<>();
64
65 /** Whether to treat the whole switch block as a single decision point.*/
66 private boolean switchBlockAsSingleDecisionPoint;
67
68 /** The current value. */
69 private BigInteger currentValue = INITIAL_VALUE;
70
71 /** Threshold to report error for. */
72 private int max = DEFAULT_COMPLEXITY_VALUE;
73
74 /**
75 * Sets whether to treat the whole switch block as a single decision point.
76 * @param switchBlockAsSingleDecisionPoint whether to treat the whole switch
77 * block as a single decision point.
78 */
79 public void setSwitchBlockAsSingleDecisionPoint(boolean switchBlockAsSingleDecisionPoint) {
80 this.switchBlockAsSingleDecisionPoint = switchBlockAsSingleDecisionPoint;
81 }
82
83 /**
84 * Set the maximum threshold allowed.
85 *
86 * @param max the maximum threshold
87 */
88 public final void setMax(int max) {
89 this.max = max;
90 }
91
92 @Override
93 public int[] getDefaultTokens() {
94 return new int[] {
95 TokenTypes.CTOR_DEF,
96 TokenTypes.METHOD_DEF,
97 TokenTypes.INSTANCE_INIT,
98 TokenTypes.STATIC_INIT,
99 TokenTypes.LITERAL_WHILE,
100 TokenTypes.LITERAL_DO,
101 TokenTypes.LITERAL_FOR,
102 TokenTypes.LITERAL_IF,
103 TokenTypes.LITERAL_SWITCH,
104 TokenTypes.LITERAL_CASE,
105 TokenTypes.LITERAL_CATCH,
106 TokenTypes.QUESTION,
107 TokenTypes.LAND,
108 TokenTypes.LOR,
109 };
110 }
111
112 @Override
113 public int[] getAcceptableTokens() {
114 return new int[] {
115 TokenTypes.CTOR_DEF,
116 TokenTypes.METHOD_DEF,
117 TokenTypes.INSTANCE_INIT,
118 TokenTypes.STATIC_INIT,
119 TokenTypes.LITERAL_WHILE,
120 TokenTypes.LITERAL_DO,
121 TokenTypes.LITERAL_FOR,
122 TokenTypes.LITERAL_IF,
123 TokenTypes.LITERAL_SWITCH,
124 TokenTypes.LITERAL_CASE,
125 TokenTypes.LITERAL_CATCH,
126 TokenTypes.QUESTION,
127 TokenTypes.LAND,
128 TokenTypes.LOR,
129 };
130 }
131
132 @Override
133 public final int[] getRequiredTokens() {
134 return new int[] {
135 TokenTypes.CTOR_DEF,
136 TokenTypes.METHOD_DEF,
137 TokenTypes.INSTANCE_INIT,
138 TokenTypes.STATIC_INIT,
139 };
140 }
141
142 @Override
143 public void visitToken(DetailAST ast) {
144 switch (ast.getType()) {
145 case TokenTypes.CTOR_DEF:
146 case TokenTypes.METHOD_DEF:
147 case TokenTypes.INSTANCE_INIT:
148 case TokenTypes.STATIC_INIT:
149 visitMethodDef();
150 break;
151 default:
152 visitTokenHook(ast);
153 }
154 }
155
156 @Override
157 public void leaveToken(DetailAST ast) {
158 switch (ast.getType()) {
159 case TokenTypes.CTOR_DEF:
160 case TokenTypes.METHOD_DEF:
161 case TokenTypes.INSTANCE_INIT:
162 case TokenTypes.STATIC_INIT:
163 leaveMethodDef(ast);
164 break;
165 default:
166 break;
167 }
168 }
169
170 /**
171 * Hook called when visiting a token. Will not be called the method
172 * definition tokens.
173 *
174 * @param ast the token being visited
175 */
176 private void visitTokenHook(DetailAST ast) {
177 if (switchBlockAsSingleDecisionPoint) {
178 if (ast.getType() != TokenTypes.LITERAL_CASE) {
179 incrementCurrentValue(BigInteger.ONE);
180 }
181 }
182 else if (ast.getType() != TokenTypes.LITERAL_SWITCH) {
183 incrementCurrentValue(BigInteger.ONE);
184 }
185 }
186
187 /**
188 * Process the end of a method definition.
189 *
190 * @param ast the token representing the method definition
191 */
192 private void leaveMethodDef(DetailAST ast) {
193 final BigInteger bigIntegerMax = BigInteger.valueOf(max);
194 if (currentValue.compareTo(bigIntegerMax) > 0) {
195 log(ast, MSG_KEY, currentValue, bigIntegerMax);
196 }
197 popValue();
198 }
199
200 /**
201 * Increments the current value by a specified amount.
202 *
203 * @param amount the amount to increment by
204 */
205 private void incrementCurrentValue(BigInteger amount) {
206 currentValue = currentValue.add(amount);
207 }
208
209 /** Push the current value on the stack. */
210 private void pushValue() {
211 valueStack.push(currentValue);
212 currentValue = INITIAL_VALUE;
213 }
214
215 /**
216 * Pops a value off the stack and makes it the current value.
217 */
218 private void popValue() {
219 currentValue = valueStack.pop();
220 }
221
222 /** Process the start of the method definition. */
223 private void visitMethodDef() {
224 pushValue();
225 }
226
227 }