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