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