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