1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.metrics;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24
25 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
30
31
32
33
34
35
36
37
38
39 @FileStatefulCheck
40 public final class BooleanExpressionComplexityCheck extends AbstractCheck {
41
42
43
44
45
46 public static final String MSG_KEY = "booleanExpressionComplexity";
47
48
49 private static final int DEFAULT_MAX = 3;
50
51
52 private final Deque<Context> contextStack = new ArrayDeque<>();
53
54 private int max;
55
56 private Context context = new Context(false);
57
58
59 public BooleanExpressionComplexityCheck() {
60 max = DEFAULT_MAX;
61 }
62
63 @Override
64 public int[] getDefaultTokens() {
65 return new int[] {
66 TokenTypes.CTOR_DEF,
67 TokenTypes.METHOD_DEF,
68 TokenTypes.EXPR,
69 TokenTypes.LAND,
70 TokenTypes.BAND,
71 TokenTypes.LOR,
72 TokenTypes.BOR,
73 TokenTypes.BXOR,
74 };
75 }
76
77 @Override
78 public int[] getRequiredTokens() {
79 return new int[] {
80 TokenTypes.CTOR_DEF,
81 TokenTypes.METHOD_DEF,
82 TokenTypes.EXPR,
83 };
84 }
85
86 @Override
87 public int[] getAcceptableTokens() {
88 return new int[] {
89 TokenTypes.CTOR_DEF,
90 TokenTypes.METHOD_DEF,
91 TokenTypes.EXPR,
92 TokenTypes.LAND,
93 TokenTypes.BAND,
94 TokenTypes.LOR,
95 TokenTypes.BOR,
96 TokenTypes.BXOR,
97 };
98 }
99
100
101
102
103
104 public void setMax(int max) {
105 this.max = max;
106 }
107
108 @Override
109 public void visitToken(DetailAST ast) {
110 switch (ast.getType()) {
111 case TokenTypes.CTOR_DEF:
112 case TokenTypes.METHOD_DEF:
113 visitMethodDef(ast);
114 break;
115 case TokenTypes.EXPR:
116 visitExpr();
117 break;
118 case TokenTypes.BOR:
119 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) {
120 context.visitBooleanOperator();
121 }
122 break;
123 case TokenTypes.BAND:
124 case TokenTypes.BXOR:
125 if (!isPassedInParameter(ast)) {
126 context.visitBooleanOperator();
127 }
128 break;
129 case TokenTypes.LAND:
130 case TokenTypes.LOR:
131 context.visitBooleanOperator();
132 break;
133 default:
134 throw new IllegalArgumentException("Unknown type: " + ast);
135 }
136 }
137
138
139
140
141
142
143 private static boolean isPassedInParameter(DetailAST logicalOperator) {
144 return logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST;
145 }
146
147
148
149
150
151
152
153
154
155 private static boolean isPipeOperator(DetailAST binaryOr) {
156 return binaryOr.getParent().getType() == TokenTypes.TYPE;
157 }
158
159 @Override
160 public void leaveToken(DetailAST ast) {
161 switch (ast.getType()) {
162 case TokenTypes.CTOR_DEF:
163 case TokenTypes.METHOD_DEF:
164 leaveMethodDef();
165 break;
166 case TokenTypes.EXPR:
167 leaveExpr(ast);
168 break;
169 default:
170
171 }
172 }
173
174
175
176
177
178 private void visitMethodDef(DetailAST ast) {
179 contextStack.push(context);
180 final boolean check = !CheckUtil.isEqualsMethod(ast);
181 context = new Context(check);
182 }
183
184
185 private void leaveMethodDef() {
186 context = contextStack.pop();
187 }
188
189
190 private void visitExpr() {
191 contextStack.push(context);
192 context = new Context(context.isChecking());
193 }
194
195
196
197
198
199 private void leaveExpr(DetailAST ast) {
200 context.checkCount(ast);
201 context = contextStack.pop();
202 }
203
204
205
206
207
208 private class Context {
209
210
211
212
213
214 private final boolean checking;
215
216 private int count;
217
218
219
220
221
222 Context(boolean checking) {
223 this.checking = checking;
224 count = 0;
225 }
226
227
228
229
230
231 public boolean isChecking() {
232 return checking;
233 }
234
235
236 public void visitBooleanOperator() {
237 ++count;
238 }
239
240
241
242
243
244 public void checkCount(DetailAST ast) {
245 if (checking && count > max) {
246 final DetailAST parentAST = ast.getParent();
247
248 log(parentAST, MSG_KEY, count, max);
249 }
250 }
251
252 }
253
254 }