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 }