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