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.coding;
21
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import com.puppycrawl.tools.checkstyle.StatelessCheck;
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.CommonUtil;
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 @StatelessCheck
112 public class FallThroughCheck extends AbstractCheck {
113
114
115
116
117
118 public static final String MSG_FALL_THROUGH = "fall.through";
119
120
121
122
123
124 public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
125
126
127 private boolean checkLastCaseGroup;
128
129
130
131
132
133 private Pattern reliefPattern = Pattern.compile("fallthru|falls? ?through");
134
135 @Override
136 public int[] getDefaultTokens() {
137 return getRequiredTokens();
138 }
139
140 @Override
141 public int[] getRequiredTokens() {
142 return new int[] {TokenTypes.CASE_GROUP};
143 }
144
145 @Override
146 public int[] getAcceptableTokens() {
147 return getRequiredTokens();
148 }
149
150
151
152
153
154
155
156
157 public void setReliefPattern(Pattern pattern) {
158 reliefPattern = pattern;
159 }
160
161
162
163
164
165 public void setCheckLastCaseGroup(boolean value) {
166 checkLastCaseGroup = value;
167 }
168
169 @Override
170 public void visitToken(DetailAST ast) {
171 final DetailAST nextGroup = ast.getNextSibling();
172 final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
173 if (!isLastGroup || checkLastCaseGroup) {
174 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
175
176 if (slist != null && !isTerminated(slist, true, true)
177 && !hasFallThroughComment(ast, nextGroup)) {
178 if (isLastGroup) {
179 log(ast, MSG_FALL_THROUGH_LAST);
180 }
181 else {
182 log(nextGroup, MSG_FALL_THROUGH);
183 }
184 }
185 }
186 }
187
188
189
190
191
192
193
194
195
196 private boolean isTerminated(final DetailAST ast, boolean useBreak,
197 boolean useContinue) {
198 final boolean terminated;
199
200 switch (ast.getType()) {
201 case TokenTypes.LITERAL_RETURN:
202 case TokenTypes.LITERAL_THROW:
203 terminated = true;
204 break;
205 case TokenTypes.LITERAL_BREAK:
206 terminated = useBreak;
207 break;
208 case TokenTypes.LITERAL_CONTINUE:
209 terminated = useContinue;
210 break;
211 case TokenTypes.SLIST:
212 terminated = checkSlist(ast, useBreak, useContinue);
213 break;
214 case TokenTypes.LITERAL_IF:
215 terminated = checkIf(ast, useBreak, useContinue);
216 break;
217 case TokenTypes.LITERAL_FOR:
218 case TokenTypes.LITERAL_WHILE:
219 case TokenTypes.LITERAL_DO:
220 terminated = checkLoop(ast);
221 break;
222 case TokenTypes.LITERAL_TRY:
223 terminated = checkTry(ast, useBreak, useContinue);
224 break;
225 case TokenTypes.LITERAL_SWITCH:
226 terminated = checkSwitch(ast, useContinue);
227 break;
228 case TokenTypes.LITERAL_SYNCHRONIZED:
229 terminated = checkSynchronized(ast, useBreak, useContinue);
230 break;
231 default:
232 terminated = false;
233 }
234 return terminated;
235 }
236
237
238
239
240
241
242
243
244
245 private boolean checkSlist(final DetailAST slistAst, boolean useBreak,
246 boolean useContinue) {
247 DetailAST lastStmt = slistAst.getLastChild();
248
249 if (lastStmt.getType() == TokenTypes.RCURLY) {
250 lastStmt = lastStmt.getPreviousSibling();
251 }
252
253 return lastStmt != null
254 && isTerminated(lastStmt, useBreak, useContinue);
255 }
256
257
258
259
260
261
262
263
264
265 private boolean checkIf(final DetailAST ast, boolean useBreak,
266 boolean useContinue) {
267 final DetailAST thenStmt = ast.findFirstToken(TokenTypes.RPAREN)
268 .getNextSibling();
269 final DetailAST elseStmt = thenStmt.getNextSibling();
270 boolean isTerminated = isTerminated(thenStmt, useBreak, useContinue);
271
272 if (isTerminated && elseStmt != null) {
273 isTerminated = isTerminated(elseStmt.getFirstChild(),
274 useBreak, useContinue);
275 }
276 else if (elseStmt == null) {
277 isTerminated = false;
278 }
279 return isTerminated;
280 }
281
282
283
284
285
286
287
288 private boolean checkLoop(final DetailAST ast) {
289 final DetailAST loopBody;
290 if (ast.getType() == TokenTypes.LITERAL_DO) {
291 final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
292 loopBody = lparen.getPreviousSibling();
293 }
294 else {
295 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
296 loopBody = rparen.getNextSibling();
297 }
298 return isTerminated(loopBody, false, false);
299 }
300
301
302
303
304
305
306
307
308
309 private boolean checkTry(final DetailAST ast, boolean useBreak,
310 boolean useContinue) {
311 final DetailAST finalStmt = ast.getLastChild();
312 boolean isTerminated = false;
313 if (finalStmt.getType() == TokenTypes.LITERAL_FINALLY) {
314 isTerminated = isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
315 useBreak, useContinue);
316 }
317
318 if (!isTerminated) {
319 DetailAST firstChild = ast.getFirstChild();
320
321 if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
322 firstChild = firstChild.getNextSibling();
323 }
324
325 isTerminated = isTerminated(firstChild,
326 useBreak, useContinue);
327
328 DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
329 while (catchStmt != null
330 && isTerminated
331 && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
332 final DetailAST catchBody =
333 catchStmt.findFirstToken(TokenTypes.SLIST);
334 isTerminated = isTerminated(catchBody, useBreak, useContinue);
335 catchStmt = catchStmt.getNextSibling();
336 }
337 }
338 return isTerminated;
339 }
340
341
342
343
344
345
346
347
348 private boolean checkSwitch(final DetailAST literalSwitchAst, boolean useContinue) {
349 DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
350 boolean isTerminated = caseGroup != null;
351 while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
352 final DetailAST caseBody =
353 caseGroup.findFirstToken(TokenTypes.SLIST);
354 isTerminated = caseBody != null && isTerminated(caseBody, false, useContinue);
355 caseGroup = caseGroup.getNextSibling();
356 }
357 return isTerminated;
358 }
359
360
361
362
363
364
365
366
367
368 private boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
369 boolean useContinue) {
370 return isTerminated(
371 synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue);
372 }
373
374
375
376
377
378
379
380
381
382 private boolean hasFallThroughComment(DetailAST currentCase, DetailAST nextCase) {
383 boolean allThroughComment = false;
384 final int endLineNo = nextCase.getLineNo();
385 final int endColNo = nextCase.getColumnNo();
386
387
388
389
390 final String[] lines = getLines();
391
392
393
394
395
396
397
398
399
400
401 final String linePart = lines[endLineNo - 1].substring(0, endColNo);
402 if (matchesComment(reliefPattern, linePart, endLineNo)) {
403 allThroughComment = true;
404 }
405 else {
406
407
408
409
410
411
412
413
414
415
416
417 final int startLineNo = currentCase.getLineNo();
418 for (int i = endLineNo - 2; i > startLineNo - 1; i--) {
419 if (!CommonUtil.isBlank(lines[i])) {
420 allThroughComment = matchesComment(reliefPattern, lines[i], i + 1);
421 break;
422 }
423 }
424 }
425 return allThroughComment;
426 }
427
428
429
430
431
432
433
434
435
436 private boolean matchesComment(Pattern pattern, String line, int lineNo) {
437 final Matcher matcher = pattern.matcher(line);
438 boolean matches = false;
439
440 if (matcher.find()) {
441 matches = getFileContents().hasIntersectionWithComment(lineNo, matcher.start(),
442 lineNo, matcher.end());
443 }
444 return matches;
445 }
446
447 }