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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 @FileStatefulCheck
58 public final class BooleanExpressionComplexityCheck extends AbstractCheck {
59
60
61
62
63
64 public static final String MSG_KEY = "booleanExpressionComplexity";
65
66
67 private static final int DEFAULT_MAX = 3;
68
69
70 private final Deque<Context> contextStack = new ArrayDeque<>();
71
72 private int max;
73
74 private Context context = new Context(false);
75
76
77 public BooleanExpressionComplexityCheck() {
78 max = DEFAULT_MAX;
79 }
80
81 @Override
82 public int[] getDefaultTokens() {
83 return new int[] {
84 TokenTypes.CTOR_DEF,
85 TokenTypes.METHOD_DEF,
86 TokenTypes.EXPR,
87 TokenTypes.LAND,
88 TokenTypes.BAND,
89 TokenTypes.LOR,
90 TokenTypes.BOR,
91 TokenTypes.BXOR,
92 TokenTypes.COMPACT_CTOR_DEF,
93 };
94 }
95
96 @Override
97 public int[] getRequiredTokens() {
98 return new int[] {
99 TokenTypes.CTOR_DEF,
100 TokenTypes.METHOD_DEF,
101 TokenTypes.EXPR,
102 TokenTypes.COMPACT_CTOR_DEF,
103 };
104 }
105
106 @Override
107 public int[] getAcceptableTokens() {
108 return new int[] {
109 TokenTypes.CTOR_DEF,
110 TokenTypes.METHOD_DEF,
111 TokenTypes.EXPR,
112 TokenTypes.LAND,
113 TokenTypes.BAND,
114 TokenTypes.LOR,
115 TokenTypes.BOR,
116 TokenTypes.BXOR,
117 TokenTypes.COMPACT_CTOR_DEF,
118 };
119 }
120
121
122
123
124
125
126
127 public void setMax(int max) {
128 this.max = max;
129 }
130
131 @Override
132 public void visitToken(DetailAST ast) {
133 switch (ast.getType()) {
134 case TokenTypes.CTOR_DEF,
135 TokenTypes.METHOD_DEF,
136 TokenTypes.COMPACT_CTOR_DEF -> visitMethodDef(ast);
137
138 case TokenTypes.EXPR -> visitExpr();
139
140 case TokenTypes.BOR -> {
141 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) {
142 context.visitBooleanOperator();
143 }
144 }
145
146 case TokenTypes.BAND,
147 TokenTypes.BXOR -> {
148 if (!isPassedInParameter(ast)) {
149 context.visitBooleanOperator();
150 }
151 }
152
153 case TokenTypes.LAND,
154 TokenTypes.LOR -> context.visitBooleanOperator();
155
156 default -> throw new IllegalArgumentException("Unknown type: " + ast);
157 }
158 }
159
160
161
162
163
164
165
166 private static boolean isPassedInParameter(DetailAST logicalOperator) {
167 return logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST;
168 }
169
170
171
172
173
174
175
176
177
178
179 private static boolean isPipeOperator(DetailAST binaryOr) {
180 return binaryOr.getParent().getType() == TokenTypes.TYPE;
181 }
182
183 @Override
184 public void leaveToken(DetailAST ast) {
185 switch (ast.getType()) {
186 case TokenTypes.CTOR_DEF,
187 TokenTypes.METHOD_DEF,
188 TokenTypes.COMPACT_CTOR_DEF -> leaveMethodDef();
189
190 case TokenTypes.EXPR -> leaveExpr(ast);
191
192 default -> {
193
194 }
195 }
196 }
197
198
199
200
201
202
203 private void visitMethodDef(DetailAST ast) {
204 contextStack.push(context);
205 final boolean check = !CheckUtil.isEqualsMethod(ast);
206 context = new Context(check);
207 }
208
209
210 private void leaveMethodDef() {
211 context = contextStack.pop();
212 }
213
214
215 private void visitExpr() {
216 contextStack.push(context);
217 context = new Context(context.isChecking());
218 }
219
220
221
222
223
224
225 private void leaveExpr(DetailAST ast) {
226 context.checkCount(ast);
227 context = contextStack.pop();
228 }
229
230
231
232
233
234 private final class Context {
235
236
237
238
239
240 private final boolean checking;
241
242 private int count;
243
244
245
246
247
248
249 private Context(boolean checking) {
250 this.checking = checking;
251 }
252
253
254
255
256
257
258 public boolean isChecking() {
259 return checking;
260 }
261
262
263 public void visitBooleanOperator() {
264 ++count;
265 }
266
267
268
269
270
271
272 public void checkCount(DetailAST ast) {
273 if (checking && count > max) {
274 final DetailAST parentAST = ast.getParent();
275
276 log(parentAST, MSG_KEY, count, max);
277 }
278 }
279
280 }
281
282 }