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