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
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
58
59
60
61
62
63
64 @FileStatefulCheck
65 public class JavaNCSSCheck extends AbstractCheck {
66
67
68
69
70
71 public static final String MSG_METHOD = "ncss.method";
72
73
74
75
76
77 public static final String MSG_CLASS = "ncss.class";
78
79
80
81
82
83 public static final String MSG_RECORD = "ncss.record";
84
85
86
87
88
89 public static final String MSG_FILE = "ncss.file";
90
91
92 private static final int FILE_MAX_NCSS = 2000;
93
94
95 private static final int CLASS_MAX_NCSS = 1500;
96
97
98 private static final int RECORD_MAX_NCSS = 150;
99
100
101 private static final int METHOD_MAX_NCSS = 50;
102
103
104
105
106
107 private int fileMaximum = FILE_MAX_NCSS;
108
109
110 private int classMaximum = CLASS_MAX_NCSS;
111
112
113 private int recordMaximum = RECORD_MAX_NCSS;
114
115
116 private int methodMaximum = METHOD_MAX_NCSS;
117
118
119 private Deque<Counter> counters;
120
121 @Override
122 public int[] getDefaultTokens() {
123 return getRequiredTokens();
124 }
125
126 @Override
127 public int[] getRequiredTokens() {
128 return new int[] {
129 TokenTypes.CLASS_DEF,
130 TokenTypes.INTERFACE_DEF,
131 TokenTypes.METHOD_DEF,
132 TokenTypes.CTOR_DEF,
133 TokenTypes.INSTANCE_INIT,
134 TokenTypes.STATIC_INIT,
135 TokenTypes.PACKAGE_DEF,
136 TokenTypes.IMPORT,
137 TokenTypes.VARIABLE_DEF,
138 TokenTypes.CTOR_CALL,
139 TokenTypes.SUPER_CTOR_CALL,
140 TokenTypes.LITERAL_IF,
141 TokenTypes.LITERAL_ELSE,
142 TokenTypes.LITERAL_WHILE,
143 TokenTypes.LITERAL_DO,
144 TokenTypes.LITERAL_FOR,
145 TokenTypes.LITERAL_SWITCH,
146 TokenTypes.LITERAL_BREAK,
147 TokenTypes.LITERAL_CONTINUE,
148 TokenTypes.LITERAL_RETURN,
149 TokenTypes.LITERAL_THROW,
150 TokenTypes.LITERAL_SYNCHRONIZED,
151 TokenTypes.LITERAL_CATCH,
152 TokenTypes.LITERAL_FINALLY,
153 TokenTypes.EXPR,
154 TokenTypes.LABELED_STAT,
155 TokenTypes.LITERAL_CASE,
156 TokenTypes.LITERAL_DEFAULT,
157 TokenTypes.RECORD_DEF,
158 TokenTypes.COMPACT_CTOR_DEF,
159 };
160 }
161
162 @Override
163 public int[] getAcceptableTokens() {
164 return getRequiredTokens();
165 }
166
167 @Override
168 public void beginTree(DetailAST rootAST) {
169 counters = new ArrayDeque<>();
170
171
172 counters.push(new Counter());
173 }
174
175 @Override
176 public void visitToken(DetailAST ast) {
177 final int tokenType = ast.getType();
178
179 if (tokenType == TokenTypes.CLASS_DEF
180 || tokenType == TokenTypes.RECORD_DEF
181 || isMethodOrCtorOrInitDefinition(tokenType)) {
182
183 counters.push(new Counter());
184 }
185
186
187 if (isCountable(ast)) {
188
189 counters.forEach(Counter::increment);
190 }
191 }
192
193 @Override
194 public void leaveToken(DetailAST ast) {
195 final int tokenType = ast.getType();
196
197 if (isMethodOrCtorOrInitDefinition(tokenType)) {
198
199 final Counter counter = counters.pop();
200
201 final int count = counter.getCount();
202 if (count > methodMaximum) {
203 log(ast, MSG_METHOD, count, methodMaximum);
204 }
205 }
206 else if (tokenType == TokenTypes.CLASS_DEF) {
207
208 final Counter counter = counters.pop();
209
210 final int count = counter.getCount();
211 if (count > classMaximum) {
212 log(ast, MSG_CLASS, count, classMaximum);
213 }
214 }
215 else if (tokenType == TokenTypes.RECORD_DEF) {
216
217 final Counter counter = counters.pop();
218
219 final int count = counter.getCount();
220 if (count > recordMaximum) {
221 log(ast, MSG_RECORD, count, recordMaximum);
222 }
223 }
224 }
225
226 @Override
227 public void finishTree(DetailAST rootAST) {
228
229 final Counter counter = counters.pop();
230
231 final int count = counter.getCount();
232 if (count > fileMaximum) {
233 log(rootAST, MSG_FILE, count, fileMaximum);
234 }
235 }
236
237
238
239
240
241
242
243
244
245 public void setFileMaximum(int fileMaximum) {
246 this.fileMaximum = fileMaximum;
247 }
248
249
250
251
252
253
254
255
256 public void setClassMaximum(int classMaximum) {
257 this.classMaximum = classMaximum;
258 }
259
260
261
262
263
264
265
266
267 public void setRecordMaximum(int recordMaximum) {
268 this.recordMaximum = recordMaximum;
269 }
270
271
272
273
274
275
276
277
278 public void setMethodMaximum(int methodMaximum) {
279 this.methodMaximum = methodMaximum;
280 }
281
282
283
284
285
286
287
288
289 private static boolean isCountable(DetailAST ast) {
290 boolean countable = true;
291
292 final int tokenType = ast.getType();
293
294
295 if (tokenType == TokenTypes.EXPR) {
296 countable = isExpressionCountable(ast);
297 }
298
299 else if (tokenType == TokenTypes.VARIABLE_DEF) {
300 countable = isVariableDefCountable(ast);
301 }
302 return countable;
303 }
304
305
306
307
308
309
310
311 private static boolean isVariableDefCountable(DetailAST ast) {
312 boolean countable = false;
313
314
315
316 final int parentType = ast.getParent().getType();
317
318 if (parentType == TokenTypes.SLIST
319 || parentType == TokenTypes.OBJBLOCK) {
320 final DetailAST prevSibling = ast.getPreviousSibling();
321
322
323
324
325
326 countable = prevSibling == null
327 || prevSibling.getType() != TokenTypes.COMMA;
328 }
329
330 return countable;
331 }
332
333
334
335
336
337
338
339 private static boolean isExpressionCountable(DetailAST ast) {
340
341
342
343
344 final int parentType = ast.getParent().getType();
345 return switch (parentType) {
346 case TokenTypes.SLIST, TokenTypes.LABELED_STAT, TokenTypes.LITERAL_FOR,
347 TokenTypes.LITERAL_DO,
348 TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE -> {
349
350 final DetailAST prevSibling = ast.getPreviousSibling();
351 yield prevSibling == null
352 || prevSibling.getType() != TokenTypes.LPAREN;
353 }
354 default -> false;
355 };
356 }
357
358
359
360
361
362
363
364 private static boolean isMethodOrCtorOrInitDefinition(int tokenType) {
365 return tokenType == TokenTypes.METHOD_DEF
366 || tokenType == TokenTypes.COMPACT_CTOR_DEF
367 || tokenType == TokenTypes.CTOR_DEF
368 || tokenType == TokenTypes.STATIC_INIT
369 || tokenType == TokenTypes.INSTANCE_INIT;
370 }
371
372
373
374
375
376 private static final class Counter {
377
378
379 private int count;
380
381
382
383
384 public void increment() {
385 count++;
386 }
387
388
389
390
391
392
393 public int getCount() {
394 return count;
395 }
396
397 }
398
399 }