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