1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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 * <div>
43 * Ensures that identifies classes that can be effectively declared as final are explicitly
44 * marked as final. The following are different types of classes that can be identified:
45 * </div>
46 * <ol>
47 * <li>
48 * Private classes with no declared constructors.
49 * </li>
50 * <li>
51 * Classes with any modifier, and contains only private constructors.
52 * </li>
53 * </ol>
54 *
55 * <p>
56 * Classes are skipped if:
57 * </p>
58 * <ol>
59 * <li>
60 * Class is Super class of some Anonymous inner class.
61 * </li>
62 * <li>
63 * Class is extended by another class in the same file.
64 * </li>
65 * </ol>
66 *
67 * @since 3.1
68 */
69 @FileStatefulCheck
70 public class FinalClassCheck
71 extends AbstractCheck {
72
73 /**
74 * A key is pointing to the warning message text in "messages.properties"
75 * file.
76 */
77 public static final String MSG_KEY = "final.class";
78
79 /**
80 * Character separate package names in qualified name of java class.
81 */
82 private static final String PACKAGE_SEPARATOR = ".";
83
84 /** Keeps ClassDesc objects for all inner classes. */
85 private Map<String, ClassDesc> innerClasses;
86
87 /**
88 * Maps anonymous inner class's {@link TokenTypes#LITERAL_NEW} node to
89 * the outer type declaration's fully qualified name.
90 */
91 private Map<DetailAST, String> anonInnerClassToOuterTypeDecl;
92
93 /** Keeps TypeDeclarationDescription object for stack of declared type descriptions. */
94 private Deque<TypeDeclarationDescription> typeDeclarations;
95
96 /** Full qualified name of the package. */
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 * Called to process a type definition.
164 *
165 * @param ast the token to process
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 * Called to process a constructor definition.
176 *
177 * @param ast the token to process
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 // Can be only of type ClassDesc, preceding if statements guarantee it.
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 // First pass: mark all classes that have derived inner classes
198 innerClasses.forEach(this::registerExtendedClass);
199 // Second pass: report violation for all classes that should be declared as final
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 * Checks whether a class should be declared as final or not.
211 *
212 * @param classDesc description of the class
213 * @return true if given class should be declared as final otherwise false
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 * Register to outer super class of given classAst that
237 * given classAst is extending them.
238 *
239 * @param qualifiedClassName qualifies class name(with package) of the current class
240 * @param currentClass class which outer super class will be informed about nesting subclass
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 * Register to the super class of anonymous inner class that the given class is instantiated
258 * by an anonymous inner class.
259 *
260 * @param literalNewAst ast node of {@link TokenTypes#LITERAL_NEW} representing anonymous inner
261 * class
262 * @param outerTypeDeclName Fully qualified name of the outer type declaration of anonymous
263 * inner class
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 * Get the nearest class with same name.
279 *
280 * <p>The parameter {@code countProvider} exists because if the class being searched is the
281 * super class of anonymous inner class, the rules of evaluation are a bit different,
282 * consider the following example-
283 * <pre>
284 * {@code
285 * public class Main {
286 * static class One {
287 * static class Two {
288 * }
289 * }
290 *
291 * class Three {
292 * One.Two object = new One.Two() { // Object of Main.Three.One.Two
293 * // and not of Main.One.Two
294 * };
295 *
296 * static class One {
297 * static class Two {
298 * }
299 * }
300 * }
301 * }
302 * }
303 * </pre>
304 * If the {@link Function} {@code countProvider} hadn't used
305 * {@link FinalClassCheck#getAnonSuperTypeMatchingCount} to
306 * calculate the matching count then the logic would have falsely evaluated
307 * {@code Main.One.Two} to be the super class of the anonymous inner class.
308 *
309 * @param className name of the class
310 * @param countProvider the function to apply to calculate the name matching count
311 * @return {@link Optional} of {@link ClassDesc} object of the nearest class with the same name.
312 * @noinspection CallToStringConcatCanBeReplacedByOperator
313 * @noinspectionreason CallToStringConcatCanBeReplacedByOperator - operator causes
314 * pitest to fail
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 * Extract the qualified type declaration name from given type declaration Ast.
328 *
329 * @param typeDeclarationAst type declaration for which qualified name is being fetched
330 * @return qualified name of a type declaration
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 * Get super class name of given class.
345 *
346 * @param classAst class
347 * @return super class name or null if super class is not specified
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 * Calculates and returns the type declaration matching count when {@code classToBeMatched} is
360 * considered to be super class of an anonymous inner class.
361 *
362 * <p>
363 * Suppose our pattern class is {@code Main.ClassOne} and class to be matched is
364 * {@code Main.ClassOne.ClassTwo.ClassThree} then type declaration name matching count would
365 * be calculated by comparing every character, and updating main counter when we hit "." or
366 * when it is the last character of the pattern class and certain conditions are met. This is
367 * done so that matching count is 13 instead of 5. This is due to the fact that pattern class
368 * can contain anonymous inner class object of a nested class which isn't true in case of
369 * extending classes as you can't extend nested classes.
370 * </p>
371 *
372 * @param patternTypeDeclaration type declaration against which the given type declaration has
373 * to be matched
374 * @param typeDeclarationToBeMatched type declaration to be matched
375 * @return type declaration matching count
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 * Maintains information about the type of declaration.
403 * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF}
404 * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF}
405 * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration.
406 * It does not maintain information about classes, a subclass called {@link ClassDesc}
407 * does that job.
408 */
409 private static class TypeDeclarationDescription {
410
411 /**
412 * Complete type declaration name with package name and outer type declaration name.
413 */
414 private final String qualifiedName;
415
416 /**
417 * Depth of nesting of type declaration.
418 */
419 private final int depth;
420
421 /**
422 * Type declaration ast node.
423 */
424 private final DetailAST typeDeclarationAst;
425
426 /**
427 * Create an instance of TypeDeclarationDescription.
428 *
429 * @param qualifiedName Complete type declaration name with package name and outer type
430 * declaration name.
431 * @param depth Depth of nesting of type declaration
432 * @param typeDeclarationAst Type declaration ast node
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 * Get the complete type declaration name i.e. type declaration name with package name
443 * and outer type declaration name.
444 *
445 * @return qualified class name
446 */
447 protected String getQualifiedName() {
448 return qualifiedName;
449 }
450
451 /**
452 * Get the depth of type declaration.
453 *
454 * @return the depth of nesting of type declaration
455 */
456 protected int getDepth() {
457 return depth;
458 }
459
460 /**
461 * Get the type declaration ast node.
462 *
463 * @return ast node of the type declaration
464 */
465 protected DetailAST getTypeDeclarationAst() {
466 return typeDeclarationAst;
467 }
468 }
469
470 /**
471 * Maintains information about the class.
472 */
473 private static final class ClassDesc extends TypeDeclarationDescription {
474
475 /** Is class declared as final. */
476 private final boolean declaredAsFinal;
477
478 /** Is class declared as abstract. */
479 private final boolean declaredAsAbstract;
480
481 /** Is class contains private modifier. */
482 private final boolean declaredAsPrivate;
483
484 /** Does class have implicit constructor. */
485 private final boolean hasDeclaredConstructor;
486
487 /** Does class have non-private ctors. */
488 private boolean withNonPrivateCtor;
489
490 /** Does class have nested subclass. */
491 private boolean withNestedSubclass;
492
493 /** Whether the class is the super class of an anonymous inner class. */
494 private boolean superClassOfAnonymousInnerClass;
495
496 /**
497 * Create a new ClassDesc instance.
498 *
499 * @param qualifiedName qualified class name(with package)
500 * @param depth class nesting level
501 * @param classAst classAst node
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 /** Adds non-private ctor. */
514 private void registerNonPrivateCtor() {
515 withNonPrivateCtor = true;
516 }
517
518 /** Adds nested subclass. */
519 private void registerNestedSubclass() {
520 withNestedSubclass = true;
521 }
522
523 /** Adds anonymous inner class. */
524 private void registerSuperClassOfAnonymousInnerClass() {
525 superClassOfAnonymousInnerClass = true;
526 }
527
528 /**
529 * Does class have non-private ctors.
530 *
531 * @return true if class has non-private ctors
532 */
533 private boolean isWithNonPrivateCtor() {
534 return withNonPrivateCtor;
535 }
536
537 /**
538 * Does class have nested subclass.
539 *
540 * @return true if class has nested subclass
541 */
542 private boolean isWithNestedSubclass() {
543 return withNestedSubclass;
544 }
545
546 /**
547 * Is class declared as final.
548 *
549 * @return true if class is declared as final
550 */
551 private boolean isDeclaredAsFinal() {
552 return declaredAsFinal;
553 }
554
555 /**
556 * Is class declared as abstract.
557 *
558 * @return true if class is declared as final
559 */
560 private boolean isDeclaredAsAbstract() {
561 return declaredAsAbstract;
562 }
563
564 /**
565 * Whether the class is the super class of an anonymous inner class.
566 *
567 * @return {@code true} if the class is the super class of an anonymous inner class.
568 */
569 private boolean isSuperClassOfAnonymousInnerClass() {
570 return superClassOfAnonymousInnerClass;
571 }
572
573 /**
574 * Does class have implicit constructor.
575 *
576 * @return true if class have implicit constructor
577 */
578 private boolean isHasDeclaredConstructor() {
579 return hasDeclaredConstructor;
580 }
581
582 /**
583 * Does class is private.
584 *
585 * @return true if class is private
586 */
587 private boolean isDeclaredAsPrivate() {
588 return declaredAsPrivate;
589 }
590 }
591 }