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