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