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