1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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
154 String outerTypeName = packageName;
155 if (!typeDeclarations.isEmpty()) {
156 outerTypeName = typeDeclarations.peek().getQualifiedName();
157 }
158 anonInnerClassToOuterTypeDecl.put(ast, outerTypeName);
159 }
160 }
161
162 default -> throw new IllegalStateException(ast.toString());
163 }
164 }
165
166 /**
167 * Called to process a type definition.
168 *
169 * @param ast the token to process
170 */
171 private void visitClass(DetailAST ast) {
172 final String qualifiedClassName = extractQualifiedTypeName(ast);
173 final ClassDesc currClass = new ClassDesc(qualifiedClassName, typeDeclarations.size(), ast);
174 typeDeclarations.push(currClass);
175 innerClasses.put(qualifiedClassName, currClass);
176 }
177
178 /**
179 * Called to process a constructor definition.
180 *
181 * @param ast the token to process
182 */
183 private void visitCtor(DetailAST ast) {
184 if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) {
185 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
186 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
187 // Can be only of type ClassDesc, preceding if statements guarantee it.
188 final ClassDesc desc = (ClassDesc) typeDeclarations.getFirst();
189 desc.registerNonPrivateCtor();
190 }
191 }
192 }
193
194 @Override
195 public void leaveToken(DetailAST ast) {
196 if (TokenUtil.isTypeDeclaration(ast.getType())) {
197 typeDeclarations.pop();
198 }
199 }
200
201 @Override
202 public void finishTree(DetailAST rootAST) {
203 anonInnerClassToOuterTypeDecl.forEach(this::registerAnonymousInnerClassToSuperClass);
204 innerClasses.forEach(this::registerExtendedClass);
205 innerClasses.forEach((qualifiedClassName, classDesc) -> {
206 if (shouldBeDeclaredAsFinal(classDesc)) {
207 final String className = CommonUtil.baseClassName(qualifiedClassName);
208 log(classDesc.getTypeDeclarationAst(), MSG_KEY, className);
209 }
210 });
211 }
212
213 /**
214 * Checks whether a class should be declared as final or not.
215 *
216 * @param classDesc description of the class
217 * @return true if given class should be declared as final otherwise false
218 */
219 private static boolean shouldBeDeclaredAsFinal(ClassDesc classDesc) {
220 final boolean shouldBeFinal;
221
222 final boolean skipClass = classDesc.isDeclaredAsFinal()
223 || classDesc.isDeclaredAsAbstract()
224 || classDesc.isSuperClassOfAnonymousInnerClass()
225 || classDesc.isWithNestedSubclass();
226
227 if (skipClass) {
228 shouldBeFinal = false;
229 }
230 else if (classDesc.isHasDeclaredConstructor()) {
231 shouldBeFinal = classDesc.isDeclaredAsPrivate();
232 }
233 else {
234 shouldBeFinal = !classDesc.isWithNonPrivateCtor();
235 }
236 return shouldBeFinal;
237 }
238
239 /**
240 * Register to outer super class of given classAst that
241 * given classAst is extending them.
242 *
243 * @param qualifiedClassName qualifies class name(with package) of the current class
244 * @param currentClass class which outer super class will be informed about nesting subclass
245 */
246 private void registerExtendedClass(String qualifiedClassName,
247 ClassDesc currentClass) {
248 final String superClassName = getSuperClassName(currentClass.getTypeDeclarationAst());
249 if (superClassName != null) {
250 final ToIntFunction<ClassDesc> nestedClassCountProvider = classDesc -> {
251 return CheckUtil.typeDeclarationNameMatchingCount(qualifiedClassName,
252 classDesc.getQualifiedName());
253 };
254 getNearestClassWithSameName(superClassName, nestedClassCountProvider)
255 .or(() -> Optional.ofNullable(innerClasses.get(superClassName)))
256 .ifPresent(ClassDesc::registerNestedSubclass);
257 }
258 }
259
260 /**
261 * Register to the super class of anonymous inner class that the given class is instantiated
262 * by an anonymous inner class.
263 *
264 * @param literalNewAst ast node of {@link TokenTypes#LITERAL_NEW} representing anonymous inner
265 * class
266 * @param outerTypeDeclName Fully qualified name of the outer type declaration of anonymous
267 * inner class
268 */
269 private void registerAnonymousInnerClassToSuperClass(DetailAST literalNewAst,
270 String outerTypeDeclName) {
271 final String superClassName = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst);
272
273 final ToIntFunction<ClassDesc> anonClassCountProvider = classDesc -> {
274 return getAnonSuperTypeMatchingCount(outerTypeDeclName, classDesc.getQualifiedName());
275 };
276 getNearestClassWithSameName(superClassName, anonClassCountProvider)
277 .or(() -> Optional.ofNullable(innerClasses.get(superClassName)))
278 .ifPresent(ClassDesc::registerSuperClassOfAnonymousInnerClass);
279 }
280
281 /**
282 * Get the nearest class with same name.
283 *
284 * <p>The parameter {@code countProvider} exists because if the class being searched is the
285 * super class of anonymous inner class, the rules of evaluation are a bit different,
286 * consider the following example-
287 * <pre>
288 * {@code
289 * public class Main {
290 * static class One {
291 * static class Two {
292 * }
293 * }
294 *
295 * class Three {
296 * One.Two object = new One.Two() { // Object of Main.Three.One.Two
297 * // and not of Main.One.Two
298 * };
299 *
300 * static class One {
301 * static class Two {
302 * }
303 * }
304 * }
305 * }
306 * }
307 * </pre>
308 * If the {@link Function} {@code countProvider} hadn't used
309 * {@link FinalClassCheck#getAnonSuperTypeMatchingCount} to
310 * calculate the matching count then the logic would have falsely evaluated
311 * {@code Main.One.Two} to be the super class of the anonymous inner class.
312 *
313 * @param className name of the class
314 * @param countProvider the function to apply to calculate the name matching count
315 * @return {@link Optional} of {@link ClassDesc} object of the nearest class with the same name.
316 * @noinspection CallToStringConcatCanBeReplacedByOperator
317 * @noinspectionreason CallToStringConcatCanBeReplacedByOperator - operator causes
318 * pitest to fail
319 */
320 private Optional<ClassDesc> getNearestClassWithSameName(String className,
321 ToIntFunction<ClassDesc> countProvider) {
322 final String dotAndClassName = PACKAGE_SEPARATOR.concat(className);
323 final Comparator<ClassDesc> longestMatch = Comparator.comparingInt(countProvider);
324 return innerClasses.entrySet().stream()
325 .filter(entry -> entry.getKey().endsWith(dotAndClassName))
326 .map(Map.Entry::getValue)
327 .min(longestMatch.reversed().thenComparingInt(ClassDesc::getDepth));
328 }
329
330 /**
331 * Extract the qualified type declaration name from given type declaration Ast.
332 *
333 * @param typeDeclarationAst type declaration for which qualified name is being fetched
334 * @return qualified name of a type declaration
335 */
336 private String extractQualifiedTypeName(DetailAST typeDeclarationAst) {
337 final String className = typeDeclarationAst.findFirstToken(TokenTypes.IDENT).getText();
338 String outerTypeDeclarationQualifiedName = null;
339 if (!typeDeclarations.isEmpty()) {
340 outerTypeDeclarationQualifiedName = typeDeclarations.peek().getQualifiedName();
341 }
342 return CheckUtil.getQualifiedTypeDeclarationName(packageName,
343 outerTypeDeclarationQualifiedName,
344 className);
345 }
346
347 /**
348 * Get super class name of given class.
349 *
350 * @param classAst class
351 * @return super class name or null if super class is not specified
352 */
353 private static String getSuperClassName(DetailAST classAst) {
354 String superClassName = null;
355 final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
356 if (classExtend != null) {
357 superClassName = CheckUtil.extractQualifiedName(classExtend.getFirstChild());
358 }
359 return superClassName;
360 }
361
362 /**
363 * Calculates and returns the type declaration matching count when {@code classToBeMatched} is
364 * considered to be super class of an anonymous inner class.
365 *
366 * <p>
367 * Suppose our pattern class is {@code Main.ClassOne} and class to be matched is
368 * {@code Main.ClassOne.ClassTwo.ClassThree} then type declaration name matching count would
369 * be calculated by comparing every character, and updating main counter when we hit "." or
370 * when it is the last character of the pattern class and certain conditions are met. This is
371 * done so that matching count is 13 instead of 5. This is due to the fact that pattern class
372 * can contain anonymous inner class object of a nested class which isn't true in case of
373 * extending classes as you can't extend nested classes.
374 * </p>
375 *
376 * @param patternTypeDeclaration type declaration against which the given type declaration has
377 * to be matched
378 * @param typeDeclarationToBeMatched type declaration to be matched
379 * @return type declaration matching count
380 */
381 private static int getAnonSuperTypeMatchingCount(String patternTypeDeclaration,
382 String typeDeclarationToBeMatched) {
383 final int typeDeclarationToBeMatchedLength = typeDeclarationToBeMatched.length();
384 final int minLength = Math
385 .min(typeDeclarationToBeMatchedLength, patternTypeDeclaration.length());
386 final char packageSeparator = PACKAGE_SEPARATOR.charAt(0);
387 final boolean shouldCountBeUpdatedAtLastCharacter =
388 typeDeclarationToBeMatchedLength > minLength
389 && typeDeclarationToBeMatched.charAt(minLength) == packageSeparator;
390
391 int result = 0;
392 for (int idx = 0;
393 idx < minLength
394 && patternTypeDeclaration.charAt(idx) == typeDeclarationToBeMatched.charAt(idx);
395 idx++) {
396
397 if (idx == minLength - 1 && shouldCountBeUpdatedAtLastCharacter
398 || patternTypeDeclaration.charAt(idx) == packageSeparator) {
399 result = idx;
400 }
401 }
402 return result;
403 }
404
405 /**
406 * Maintains information about the type of declaration.
407 * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF}
408 * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF}
409 * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration.
410 * It does not maintain information about classes, a subclass called {@link ClassDesc}
411 * does that job.
412 */
413 private static class TypeDeclarationDescription {
414
415 /**
416 * Complete type declaration name with package name and outer type declaration name.
417 */
418 private final String qualifiedName;
419
420 /**
421 * Depth of nesting of type declaration.
422 */
423 private final int depth;
424
425 /**
426 * Type declaration ast node.
427 */
428 private final DetailAST typeDeclarationAst;
429
430 /**
431 * Create an instance of TypeDeclarationDescription.
432 *
433 * @param qualifiedName Complete type declaration name with package name and outer type
434 * declaration name.
435 * @param depth Depth of nesting of type declaration
436 * @param typeDeclarationAst Type declaration ast node
437 */
438 private TypeDeclarationDescription(String qualifiedName, int depth,
439 DetailAST typeDeclarationAst) {
440 this.qualifiedName = qualifiedName;
441 this.depth = depth;
442 this.typeDeclarationAst = typeDeclarationAst;
443 }
444
445 /**
446 * Get the complete type declaration name i.e. type declaration name with package name
447 * and outer type declaration name.
448 *
449 * @return qualified class name
450 */
451 /* package */ String getQualifiedName() {
452 return qualifiedName;
453 }
454
455 /**
456 * Get the depth of type declaration.
457 *
458 * @return the depth of nesting of type declaration
459 */
460 /* package */ int getDepth() {
461 return depth;
462 }
463
464 /**
465 * Get the type declaration ast node.
466 *
467 * @return ast node of the type declaration
468 */
469
470 /* package */ DetailAST getTypeDeclarationAst() {
471 return typeDeclarationAst;
472 }
473 }
474
475 /**
476 * Maintains information about the class.
477 */
478 private static final class ClassDesc extends TypeDeclarationDescription {
479
480 /** Is class declared as final. */
481 private final boolean declaredAsFinal;
482
483 /** Is class declared as abstract. */
484 private final boolean declaredAsAbstract;
485
486 /** Is class contains private modifier. */
487 private final boolean declaredAsPrivate;
488
489 /** Does class have implicit constructor. */
490 private final boolean hasDeclaredConstructor;
491
492 /** Does class have non-private ctors. */
493 private boolean withNonPrivateCtor;
494
495 /** Does class have nested subclass. */
496 private boolean withNestedSubclass;
497
498 /** Whether the class is the super class of an anonymous inner class. */
499 private boolean superClassOfAnonymousInnerClass;
500
501 /**
502 * Create a new ClassDesc instance.
503 *
504 * @param qualifiedName qualified class name(with package)
505 * @param depth class nesting level
506 * @param classAst classAst node
507 */
508 private ClassDesc(String qualifiedName, int depth, DetailAST classAst) {
509 super(qualifiedName, depth, classAst);
510 final DetailAST modifiers = classAst.findFirstToken(TokenTypes.MODIFIERS);
511 declaredAsFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
512 declaredAsAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
513 declaredAsPrivate = modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
514 hasDeclaredConstructor =
515 classAst.getLastChild().findFirstToken(TokenTypes.CTOR_DEF) == null;
516 }
517
518 /** Adds non-private ctor. */
519 private void registerNonPrivateCtor() {
520 withNonPrivateCtor = true;
521 }
522
523 /** Adds nested subclass. */
524 private void registerNestedSubclass() {
525 withNestedSubclass = true;
526 }
527
528 /** Adds anonymous inner class. */
529 private void registerSuperClassOfAnonymousInnerClass() {
530 superClassOfAnonymousInnerClass = true;
531 }
532
533 /**
534 * Does class have non-private ctors.
535 *
536 * @return true if class has non-private ctors
537 */
538 private boolean isWithNonPrivateCtor() {
539 return withNonPrivateCtor;
540 }
541
542 /**
543 * Does class have nested subclass.
544 *
545 * @return true if class has nested subclass
546 */
547 private boolean isWithNestedSubclass() {
548 return withNestedSubclass;
549 }
550
551 /**
552 * Is class declared as final.
553 *
554 * @return true if class is declared as final
555 */
556 private boolean isDeclaredAsFinal() {
557 return declaredAsFinal;
558 }
559
560 /**
561 * Is class declared as abstract.
562 *
563 * @return true if class is declared as final
564 */
565 private boolean isDeclaredAsAbstract() {
566 return declaredAsAbstract;
567 }
568
569 /**
570 * Whether the class is the super class of an anonymous inner class.
571 *
572 * @return {@code true} if the class is the super class of an anonymous inner class.
573 */
574 private boolean isSuperClassOfAnonymousInnerClass() {
575 return superClassOfAnonymousInnerClass;
576 }
577
578 /**
579 * Does class have implicit constructor.
580 *
581 * @return true if class have implicit constructor
582 */
583 private boolean isHasDeclaredConstructor() {
584 return hasDeclaredConstructor;
585 }
586
587 /**
588 * Does class is private.
589 *
590 * @return true if class is private
591 */
592 private boolean isDeclaredAsPrivate() {
593 return declaredAsPrivate;
594 }
595 }
596 }