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.design;
21
22 import java.util.ArrayDeque;
23 import java.util.Comparator;
24 import java.util.Deque;
25 import java.util.HashMap;
26 import java.util.LinkedHashMap;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.function.Function;
30 import java.util.function.ToIntFunction;
31
32 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
33 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
34 import com.puppycrawl.tools.checkstyle.api.DetailAST;
35 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
37 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
39 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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 @FileStatefulCheck
83 public class FinalClassCheck
84 extends AbstractCheck {
85
86
87
88
89
90 public static final String MSG_KEY = "final.class";
91
92
93
94
95 private static final String PACKAGE_SEPARATOR = ".";
96
97
98 private Map<String, ClassDesc> innerClasses;
99
100
101
102
103
104 private Map<DetailAST, String> anonInnerClassToOuterTypeDecl;
105
106
107 private Deque<TypeDeclarationDescription> typeDeclarations;
108
109
110 private String packageName;
111
112 @Override
113 public int[] getDefaultTokens() {
114 return getRequiredTokens();
115 }
116
117 @Override
118 public int[] getAcceptableTokens() {
119 return getRequiredTokens();
120 }
121
122 @Override
123 public int[] getRequiredTokens() {
124 return new int[] {
125 TokenTypes.ANNOTATION_DEF,
126 TokenTypes.CLASS_DEF,
127 TokenTypes.ENUM_DEF,
128 TokenTypes.INTERFACE_DEF,
129 TokenTypes.RECORD_DEF,
130 TokenTypes.CTOR_DEF,
131 TokenTypes.PACKAGE_DEF,
132 TokenTypes.LITERAL_NEW,
133 };
134 }
135
136 @Override
137 public void beginTree(DetailAST rootAST) {
138 typeDeclarations = new ArrayDeque<>();
139 innerClasses = new LinkedHashMap<>();
140 anonInnerClassToOuterTypeDecl = new HashMap<>();
141 packageName = "";
142 }
143
144 @Override
145 public void visitToken(DetailAST ast) {
146 switch (ast.getType()) {
147 case TokenTypes.PACKAGE_DEF ->
148 packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling());
149
150 case TokenTypes.ANNOTATION_DEF,
151 TokenTypes.ENUM_DEF,
152 TokenTypes.INTERFACE_DEF,
153 TokenTypes.RECORD_DEF -> {
154 final TypeDeclarationDescription description = new TypeDeclarationDescription(
155 extractQualifiedTypeName(ast), 0, ast);
156 typeDeclarations.push(description);
157 }
158
159 case TokenTypes.CLASS_DEF -> visitClass(ast);
160
161 case TokenTypes.CTOR_DEF -> visitCtor(ast);
162
163 case TokenTypes.LITERAL_NEW -> {
164 if (ast.getFirstChild() != null
165 && ast.getLastChild().getType() == TokenTypes.OBJBLOCK) {
166 anonInnerClassToOuterTypeDecl
167 .put(ast, typeDeclarations.peek().getQualifiedName());
168 }
169 }
170
171 default -> throw new IllegalStateException(ast.toString());
172 }
173 }
174
175
176
177
178
179
180 private void visitClass(DetailAST ast) {
181 final String qualifiedClassName = extractQualifiedTypeName(ast);
182 final ClassDesc currClass = new ClassDesc(qualifiedClassName, typeDeclarations.size(), ast);
183 typeDeclarations.push(currClass);
184 innerClasses.put(qualifiedClassName, currClass);
185 }
186
187
188
189
190
191
192 private void visitCtor(DetailAST ast) {
193 if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) {
194 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
195 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
196
197 final ClassDesc desc = (ClassDesc) typeDeclarations.getFirst();
198 desc.registerNonPrivateCtor();
199 }
200 }
201 }
202
203 @Override
204 public void leaveToken(DetailAST ast) {
205 if (TokenUtil.isTypeDeclaration(ast.getType())) {
206 typeDeclarations.pop();
207 }
208 if (TokenUtil.isRootNode(ast.getParent())) {
209 anonInnerClassToOuterTypeDecl.forEach(this::registerAnonymousInnerClassToSuperClass);
210
211 innerClasses.forEach(this::registerExtendedClass);
212
213 innerClasses.forEach((qualifiedClassName, classDesc) -> {
214 if (shouldBeDeclaredAsFinal(classDesc)) {
215 final String className = CommonUtil.baseClassName(qualifiedClassName);
216 log(classDesc.getTypeDeclarationAst(), MSG_KEY, className);
217 }
218 });
219 }
220 }
221
222
223
224
225
226
227
228 private static boolean shouldBeDeclaredAsFinal(ClassDesc classDesc) {
229 final boolean shouldBeFinal;
230
231 final boolean skipClass = classDesc.isDeclaredAsFinal()
232 || classDesc.isDeclaredAsAbstract()
233 || classDesc.isSuperClassOfAnonymousInnerClass()
234 || classDesc.isWithNestedSubclass();
235
236 if (skipClass) {
237 shouldBeFinal = false;
238 }
239 else if (classDesc.isHasDeclaredConstructor()) {
240 shouldBeFinal = classDesc.isDeclaredAsPrivate();
241 }
242 else {
243 shouldBeFinal = !classDesc.isWithNonPrivateCtor();
244 }
245 return shouldBeFinal;
246 }
247
248
249
250
251
252
253
254
255 private void registerExtendedClass(String qualifiedClassName,
256 ClassDesc currentClass) {
257 final String superClassName = getSuperClassName(currentClass.getTypeDeclarationAst());
258 if (superClassName != null) {
259 final ToIntFunction<ClassDesc> nestedClassCountProvider = classDesc -> {
260 return CheckUtil.typeDeclarationNameMatchingCount(qualifiedClassName,
261 classDesc.getQualifiedName());
262 };
263 getNearestClassWithSameName(superClassName, nestedClassCountProvider)
264 .or(() -> Optional.ofNullable(innerClasses.get(superClassName)))
265 .ifPresent(ClassDesc::registerNestedSubclass);
266 }
267 }
268
269
270
271
272
273
274
275
276
277
278 private void registerAnonymousInnerClassToSuperClass(DetailAST literalNewAst,
279 String outerTypeDeclName) {
280 final String superClassName = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst);
281
282 final ToIntFunction<ClassDesc> anonClassCountProvider = classDesc -> {
283 return getAnonSuperTypeMatchingCount(outerTypeDeclName, classDesc.getQualifiedName());
284 };
285 getNearestClassWithSameName(superClassName, anonClassCountProvider)
286 .or(() -> Optional.ofNullable(innerClasses.get(superClassName)))
287 .ifPresent(ClassDesc::registerSuperClassOfAnonymousInnerClass);
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 private Optional<ClassDesc> getNearestClassWithSameName(String className,
330 ToIntFunction<ClassDesc> countProvider) {
331 final String dotAndClassName = PACKAGE_SEPARATOR.concat(className);
332 final Comparator<ClassDesc> longestMatch = Comparator.comparingInt(countProvider);
333 return innerClasses.entrySet().stream()
334 .filter(entry -> entry.getKey().endsWith(dotAndClassName))
335 .map(Map.Entry::getValue)
336 .min(longestMatch.reversed().thenComparingInt(ClassDesc::getDepth));
337 }
338
339
340
341
342
343
344
345 private String extractQualifiedTypeName(DetailAST typeDeclarationAst) {
346 final String className = typeDeclarationAst.findFirstToken(TokenTypes.IDENT).getText();
347 String outerTypeDeclarationQualifiedName = null;
348 if (!typeDeclarations.isEmpty()) {
349 outerTypeDeclarationQualifiedName = typeDeclarations.peek().getQualifiedName();
350 }
351 return CheckUtil.getQualifiedTypeDeclarationName(packageName,
352 outerTypeDeclarationQualifiedName,
353 className);
354 }
355
356
357
358
359
360
361
362 private static String getSuperClassName(DetailAST classAst) {
363 String superClassName = null;
364 final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
365 if (classExtend != null) {
366 superClassName = CheckUtil.extractQualifiedName(classExtend.getFirstChild());
367 }
368 return superClassName;
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390 private static int getAnonSuperTypeMatchingCount(String patternTypeDeclaration,
391 String typeDeclarationToBeMatched) {
392 final int typeDeclarationToBeMatchedLength = typeDeclarationToBeMatched.length();
393 final int minLength = Math
394 .min(typeDeclarationToBeMatchedLength, patternTypeDeclaration.length());
395 final char packageSeparator = PACKAGE_SEPARATOR.charAt(0);
396 final boolean shouldCountBeUpdatedAtLastCharacter =
397 typeDeclarationToBeMatchedLength > minLength
398 && typeDeclarationToBeMatched.charAt(minLength) == packageSeparator;
399
400 int result = 0;
401 for (int idx = 0;
402 idx < minLength
403 && patternTypeDeclaration.charAt(idx) == typeDeclarationToBeMatched.charAt(idx);
404 idx++) {
405
406 if (idx == minLength - 1 && shouldCountBeUpdatedAtLastCharacter
407 || patternTypeDeclaration.charAt(idx) == packageSeparator) {
408 result = idx;
409 }
410 }
411 return result;
412 }
413
414
415
416
417
418
419
420
421
422 private static class TypeDeclarationDescription {
423
424
425
426
427 private final String qualifiedName;
428
429
430
431
432 private final int depth;
433
434
435
436
437 private final DetailAST typeDeclarationAst;
438
439
440
441
442
443
444
445
446
447 private TypeDeclarationDescription(String qualifiedName, int depth,
448 DetailAST typeDeclarationAst) {
449 this.qualifiedName = qualifiedName;
450 this.depth = depth;
451 this.typeDeclarationAst = typeDeclarationAst;
452 }
453
454
455
456
457
458
459
460 protected String getQualifiedName() {
461 return qualifiedName;
462 }
463
464
465
466
467
468
469 protected int getDepth() {
470 return depth;
471 }
472
473
474
475
476
477
478 protected DetailAST getTypeDeclarationAst() {
479 return typeDeclarationAst;
480 }
481 }
482
483
484
485
486 private static final class ClassDesc extends TypeDeclarationDescription {
487
488
489 private final boolean declaredAsFinal;
490
491
492 private final boolean declaredAsAbstract;
493
494
495 private final boolean declaredAsPrivate;
496
497
498 private final boolean hasDeclaredConstructor;
499
500
501 private boolean withNonPrivateCtor;
502
503
504 private boolean withNestedSubclass;
505
506
507 private boolean superClassOfAnonymousInnerClass;
508
509
510
511
512
513
514
515
516 private ClassDesc(String qualifiedName, int depth, DetailAST classAst) {
517 super(qualifiedName, depth, classAst);
518 final DetailAST modifiers = classAst.findFirstToken(TokenTypes.MODIFIERS);
519 declaredAsFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
520 declaredAsAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
521 declaredAsPrivate = modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
522 hasDeclaredConstructor =
523 classAst.getLastChild().findFirstToken(TokenTypes.CTOR_DEF) == null;
524 }
525
526
527 private void registerNonPrivateCtor() {
528 withNonPrivateCtor = true;
529 }
530
531
532 private void registerNestedSubclass() {
533 withNestedSubclass = true;
534 }
535
536
537 private void registerSuperClassOfAnonymousInnerClass() {
538 superClassOfAnonymousInnerClass = true;
539 }
540
541
542
543
544
545
546 private boolean isWithNonPrivateCtor() {
547 return withNonPrivateCtor;
548 }
549
550
551
552
553
554
555 private boolean isWithNestedSubclass() {
556 return withNestedSubclass;
557 }
558
559
560
561
562
563
564 private boolean isDeclaredAsFinal() {
565 return declaredAsFinal;
566 }
567
568
569
570
571
572
573 private boolean isDeclaredAsAbstract() {
574 return declaredAsAbstract;
575 }
576
577
578
579
580
581
582 private boolean isSuperClassOfAnonymousInnerClass() {
583 return superClassOfAnonymousInnerClass;
584 }
585
586
587
588
589
590
591 private boolean isHasDeclaredConstructor() {
592 return hasDeclaredConstructor;
593 }
594
595
596
597
598
599
600 private boolean isDeclaredAsPrivate() {
601 return declaredAsPrivate;
602 }
603 }
604 }