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.modifier;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Optional;
25
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 @StatelessCheck
164 public class RedundantModifierCheck
165 extends AbstractCheck {
166
167
168
169
170
171 public static final String MSG_KEY = "redundantModifier";
172
173
174
175
176 private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = {
177 TokenTypes.LITERAL_STATIC,
178 TokenTypes.ABSTRACT,
179 };
180
181
182
183
184 private static final int JDK_22 = 22;
185
186
187
188
189
190 private static final int JDK_17 = 17;
191
192
193
194
195
196
197
198
199
200
201 private int jdkVersion = JDK_22;
202
203
204
205
206
207
208
209
210
211
212
213
214 public void setJdkVersion(String jdkVersion) {
215 final String singleVersionNumber;
216 if (jdkVersion.startsWith("1.")) {
217 singleVersionNumber = jdkVersion.substring(2);
218 }
219 else {
220 singleVersionNumber = jdkVersion;
221 }
222
223 this.jdkVersion = Integer.parseInt(singleVersionNumber);
224 }
225
226 @Override
227 public int[] getDefaultTokens() {
228 return getAcceptableTokens();
229 }
230
231 @Override
232 public int[] getRequiredTokens() {
233 return CommonUtil.EMPTY_INT_ARRAY;
234 }
235
236 @Override
237 public int[] getAcceptableTokens() {
238 return new int[] {
239 TokenTypes.METHOD_DEF,
240 TokenTypes.VARIABLE_DEF,
241 TokenTypes.ANNOTATION_FIELD_DEF,
242 TokenTypes.INTERFACE_DEF,
243 TokenTypes.CTOR_DEF,
244 TokenTypes.CLASS_DEF,
245 TokenTypes.ENUM_DEF,
246 TokenTypes.RESOURCE,
247 TokenTypes.ANNOTATION_DEF,
248 TokenTypes.RECORD_DEF,
249 TokenTypes.PATTERN_VARIABLE_DEF,
250 TokenTypes.LITERAL_CATCH,
251 TokenTypes.LAMBDA,
252 };
253 }
254
255 @Override
256 public void visitToken(DetailAST ast) {
257 switch (ast.getType()) {
258 case TokenTypes.INTERFACE_DEF:
259 case TokenTypes.ANNOTATION_DEF:
260 checkInterfaceModifiers(ast);
261 break;
262 case TokenTypes.ENUM_DEF:
263 checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
264 break;
265 case TokenTypes.CTOR_DEF:
266 checkConstructorModifiers(ast);
267 break;
268 case TokenTypes.METHOD_DEF:
269 processMethods(ast);
270 break;
271 case TokenTypes.RESOURCE:
272 processResources(ast);
273 break;
274 case TokenTypes.RECORD_DEF:
275 checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
276 break;
277 case TokenTypes.VARIABLE_DEF:
278 case TokenTypes.PATTERN_VARIABLE_DEF:
279 checkUnnamedVariables(ast);
280 break;
281 case TokenTypes.LITERAL_CATCH:
282 checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
283 break;
284 case TokenTypes.LAMBDA:
285 processLambdaParameters(ast);
286 break;
287 case TokenTypes.CLASS_DEF:
288 case TokenTypes.ANNOTATION_FIELD_DEF:
289 break;
290 default:
291 throw new IllegalStateException("Unexpected token type: " + ast.getType());
292 }
293
294 if (isInterfaceOrAnnotationMember(ast)) {
295 processInterfaceOrAnnotation(ast);
296 }
297
298 if (jdkVersion >= JDK_17) {
299 checkForRedundantModifier(ast, TokenTypes.STRICTFP);
300 }
301 }
302
303
304
305
306
307
308 private void processLambdaParameters(DetailAST lambdaAst) {
309 final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
310 if (lambdaParameters != null) {
311 TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
312 this::checkUnnamedVariables);
313 }
314 }
315
316
317
318
319
320
321
322
323 private void checkUnnamedVariables(DetailAST ast) {
324 if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
325 checkForRedundantModifier(ast, TokenTypes.FINAL);
326 }
327 }
328
329
330
331
332
333
334
335
336
337 private static boolean isUnnamedVariable(DetailAST ast) {
338 return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
339 }
340
341
342
343
344
345
346 private void checkConstructorModifiers(DetailAST ctorDefAst) {
347 if (isEnumMember(ctorDefAst)) {
348 checkEnumConstructorModifiers(ctorDefAst);
349 }
350 else {
351 checkClassConstructorModifiers(ctorDefAst);
352 }
353 }
354
355
356
357
358
359
360 private void checkInterfaceModifiers(DetailAST ast) {
361 final DetailAST modifiers =
362 ast.findFirstToken(TokenTypes.MODIFIERS);
363
364 for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
365 final DetailAST modifier =
366 modifiers.findFirstToken(tokenType);
367 if (modifier != null) {
368 log(modifier, MSG_KEY, modifier.getText());
369 }
370 }
371 }
372
373
374
375
376
377
378 private void checkEnumConstructorModifiers(DetailAST ast) {
379 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
380 TokenUtil.findFirstTokenByPredicate(
381 modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
382 ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
383 }
384
385
386
387
388
389
390 private void processInterfaceOrAnnotation(DetailAST ast) {
391 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
392 DetailAST modifier = modifiers.getFirstChild();
393 while (modifier != null) {
394
395
396
397
398 final int type = modifier.getType();
399 if (type == TokenTypes.LITERAL_PUBLIC
400 || type == TokenTypes.LITERAL_STATIC
401 && ast.getType() != TokenTypes.METHOD_DEF
402 || type == TokenTypes.ABSTRACT
403 && ast.getType() != TokenTypes.CLASS_DEF
404 || type == TokenTypes.FINAL
405 && ast.getType() != TokenTypes.CLASS_DEF) {
406 log(modifier, MSG_KEY, modifier.getText());
407 }
408
409 modifier = modifier.getNextSibling();
410 }
411 }
412
413
414
415
416
417
418 private void processMethods(DetailAST ast) {
419 final DetailAST modifiers =
420 ast.findFirstToken(TokenTypes.MODIFIERS);
421
422 boolean checkFinal =
423 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
424
425 DetailAST parent = ast;
426 while (parent != null && !checkFinal) {
427 if (parent.getType() == TokenTypes.CLASS_DEF) {
428 final DetailAST classModifiers =
429 parent.findFirstToken(TokenTypes.MODIFIERS);
430 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
431 parent = null;
432 }
433 else if (parent.getType() == TokenTypes.LITERAL_NEW
434 || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
435 checkFinal = true;
436 parent = null;
437 }
438 else if (parent.getType() == TokenTypes.ENUM_DEF) {
439 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
440 parent = null;
441 }
442 else {
443 parent = parent.getParent();
444 }
445 }
446 if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
447 checkForRedundantModifier(ast, TokenTypes.FINAL);
448 }
449
450 if (ast.findFirstToken(TokenTypes.SLIST) == null) {
451 processAbstractMethodParameters(ast);
452 }
453 }
454
455
456
457
458
459
460 private void processAbstractMethodParameters(DetailAST ast) {
461 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
462 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
463 checkForRedundantModifier(paramDef, TokenTypes.FINAL);
464 });
465 }
466
467
468
469
470
471
472 private void checkClassConstructorModifiers(DetailAST classCtorAst) {
473 final DetailAST classDef = classCtorAst.getParent().getParent();
474 if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
475 checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
476 }
477 }
478
479
480
481
482
483
484 private void processResources(DetailAST ast) {
485 checkForRedundantModifier(ast, TokenTypes.FINAL);
486 }
487
488
489
490
491
492
493
494 private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
495 Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
496 .ifPresent(modifiers -> {
497 for (DetailAST childAst = modifiers.getFirstChild();
498 childAst != null; childAst = childAst.getNextSibling()) {
499 if (TokenUtil.isOfType(childAst, modifierTypes)) {
500 log(childAst, MSG_KEY, childAst.getText());
501 }
502 }
503 });
504 }
505
506
507
508
509
510
511
512 private static boolean isClassProtected(DetailAST classDef) {
513 final DetailAST classModifiers =
514 classDef.findFirstToken(TokenTypes.MODIFIERS);
515 return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
516 }
517
518
519
520
521
522
523
524 private static boolean isClassPublic(DetailAST ast) {
525 boolean isAccessibleFromPublic = false;
526 final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
527 final boolean hasPublicModifier =
528 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
529
530 if (TokenUtil.isRootNode(ast.getParent())) {
531 isAccessibleFromPublic = hasPublicModifier;
532 }
533 else {
534 final DetailAST parentClassAst = ast.getParent().getParent();
535
536 if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
537 isAccessibleFromPublic = isClassPublic(parentClassAst);
538 }
539 }
540
541 return isAccessibleFromPublic;
542 }
543
544
545
546
547
548
549
550 private static boolean isEnumMember(DetailAST ast) {
551 final DetailAST parentTypeDef = ast.getParent().getParent();
552 return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
553 }
554
555
556
557
558
559
560
561 private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
562 DetailAST parentTypeDef = ast.getParent();
563 parentTypeDef = parentTypeDef.getParent();
564 return parentTypeDef != null
565 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
566 || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
567 }
568
569
570
571
572
573
574
575
576
577 private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
578 boolean result = false;
579 final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
580 for (DetailAST annotationNode : methodAnnotationsList) {
581 if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
582 result = true;
583 break;
584 }
585 }
586 return result;
587 }
588
589
590
591
592
593
594
595 private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
596 final List<DetailAST> annotationsList = new ArrayList<>();
597 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
598 TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
599 return annotationsList;
600 }
601
602 }