View Javadoc
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 }