001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.design;
021
022import java.util.ArrayDeque;
023import java.util.Comparator;
024import java.util.Deque;
025import java.util.HashMap;
026import java.util.LinkedHashMap;
027import java.util.Map;
028import java.util.Optional;
029import java.util.function.Function;
030import java.util.function.ToIntFunction;
031
032import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
033import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
034import com.puppycrawl.tools.checkstyle.api.DetailAST;
035import com.puppycrawl.tools.checkstyle.api.TokenTypes;
036import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
037import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
038import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
039import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
040
041/**
042 * <div>
043 * Ensures that identifies classes that can be effectively declared as final are explicitly
044 * marked as final. The following are different types of classes that can be identified:
045 * </div>
046 * <ol>
047 *   <li>
048 *       Private classes with no declared constructors.
049 *   </li>
050 *   <li>
051 *       Classes with any modifier, and contains only private constructors.
052 *   </li>
053 * </ol>
054 *
055 * <p>
056 * Classes are skipped if:
057 * </p>
058 * <ol>
059 *   <li>
060 *       Class is Super class of some Anonymous inner class.
061 *   </li>
062 *   <li>
063 *       Class is extended by another class in the same file.
064 *   </li>
065 * </ol>
066 *
067 * @since 3.1
068 */
069@FileStatefulCheck
070public class FinalClassCheck
071    extends AbstractCheck {
072
073    /**
074     * A key is pointing to the warning message text in "messages.properties"
075     * file.
076     */
077    public static final String MSG_KEY = "final.class";
078
079    /**
080     * Character separate package names in qualified name of java class.
081     */
082    private static final String PACKAGE_SEPARATOR = ".";
083
084    /** Keeps ClassDesc objects for all inner classes. */
085    private Map<String, ClassDesc> innerClasses;
086
087    /**
088     * Maps anonymous inner class's {@link TokenTypes#LITERAL_NEW} node to
089     * the outer type declaration's fully qualified name.
090     */
091    private Map<DetailAST, String> anonInnerClassToOuterTypeDecl;
092
093    /** Keeps TypeDeclarationDescription object for stack of declared type descriptions. */
094    private Deque<TypeDeclarationDescription> typeDeclarations;
095
096    /** Full qualified name of the package. */
097    private String packageName;
098
099    @Override
100    public int[] getDefaultTokens() {
101        return getRequiredTokens();
102    }
103
104    @Override
105    public int[] getAcceptableTokens() {
106        return getRequiredTokens();
107    }
108
109    @Override
110    public int[] getRequiredTokens() {
111        return new int[] {
112            TokenTypes.ANNOTATION_DEF,
113            TokenTypes.CLASS_DEF,
114            TokenTypes.ENUM_DEF,
115            TokenTypes.INTERFACE_DEF,
116            TokenTypes.RECORD_DEF,
117            TokenTypes.CTOR_DEF,
118            TokenTypes.PACKAGE_DEF,
119            TokenTypes.LITERAL_NEW,
120        };
121    }
122
123    @Override
124    public void beginTree(DetailAST rootAST) {
125        typeDeclarations = new ArrayDeque<>();
126        innerClasses = new LinkedHashMap<>();
127        anonInnerClassToOuterTypeDecl = new HashMap<>();
128        packageName = "";
129    }
130
131    @Override
132    public void visitToken(DetailAST ast) {
133        switch (ast.getType()) {
134            case TokenTypes.PACKAGE_DEF ->
135                packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling());
136
137            case TokenTypes.ANNOTATION_DEF,
138                 TokenTypes.ENUM_DEF,
139                 TokenTypes.INTERFACE_DEF,
140                 TokenTypes.RECORD_DEF -> {
141                final TypeDeclarationDescription description = new TypeDeclarationDescription(
142                    extractQualifiedTypeName(ast), 0, ast);
143                typeDeclarations.push(description);
144            }
145
146            case TokenTypes.CLASS_DEF -> visitClass(ast);
147
148            case TokenTypes.CTOR_DEF -> visitCtor(ast);
149
150            case TokenTypes.LITERAL_NEW -> {
151                if (ast.getFirstChild() != null
152                        && ast.getLastChild().getType() == TokenTypes.OBJBLOCK) {
153                    anonInnerClassToOuterTypeDecl
154                        .put(ast, typeDeclarations.peek().getQualifiedName());
155                }
156            }
157
158            default -> throw new IllegalStateException(ast.toString());
159        }
160    }
161
162    /**
163     * Called to process a type definition.
164     *
165     * @param ast the token to process
166     */
167    private void visitClass(DetailAST ast) {
168        final String qualifiedClassName = extractQualifiedTypeName(ast);
169        final ClassDesc currClass = new ClassDesc(qualifiedClassName, typeDeclarations.size(), ast);
170        typeDeclarations.push(currClass);
171        innerClasses.put(qualifiedClassName, currClass);
172    }
173
174    /**
175     * Called to process a constructor definition.
176     *
177     * @param ast the token to process
178     */
179    private void visitCtor(DetailAST ast) {
180        if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) {
181            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
182            if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
183                // Can be only of type ClassDesc, preceding if statements guarantee it.
184                final ClassDesc desc = (ClassDesc) typeDeclarations.getFirst();
185                desc.registerNonPrivateCtor();
186            }
187        }
188    }
189
190    @Override
191    public void leaveToken(DetailAST ast) {
192        if (TokenUtil.isTypeDeclaration(ast.getType())) {
193            typeDeclarations.pop();
194        }
195        if (TokenUtil.isRootNode(ast.getParent())) {
196            anonInnerClassToOuterTypeDecl.forEach(this::registerAnonymousInnerClassToSuperClass);
197            // First pass: mark all classes that have derived inner classes
198            innerClasses.forEach(this::registerExtendedClass);
199            // Second pass: report violation for all classes that should be declared as final
200            innerClasses.forEach((qualifiedClassName, classDesc) -> {
201                if (shouldBeDeclaredAsFinal(classDesc)) {
202                    final String className = CommonUtil.baseClassName(qualifiedClassName);
203                    log(classDesc.getTypeDeclarationAst(), MSG_KEY, className);
204                }
205            });
206        }
207    }
208
209    /**
210     * Checks whether a class should be declared as final or not.
211     *
212     * @param classDesc description of the class
213     * @return true if given class should be declared as final otherwise false
214     */
215    private static boolean shouldBeDeclaredAsFinal(ClassDesc classDesc) {
216        final boolean shouldBeFinal;
217
218        final boolean skipClass = classDesc.isDeclaredAsFinal()
219                    || classDesc.isDeclaredAsAbstract()
220                    || classDesc.isSuperClassOfAnonymousInnerClass()
221                    || classDesc.isWithNestedSubclass();
222
223        if (skipClass) {
224            shouldBeFinal = false;
225        }
226        else if (classDesc.isHasDeclaredConstructor()) {
227            shouldBeFinal = classDesc.isDeclaredAsPrivate();
228        }
229        else {
230            shouldBeFinal = !classDesc.isWithNonPrivateCtor();
231        }
232        return shouldBeFinal;
233    }
234
235    /**
236     * Register to outer super class of given classAst that
237     * given classAst is extending them.
238     *
239     * @param qualifiedClassName qualifies class name(with package) of the current class
240     * @param currentClass class which outer super class will be informed about nesting subclass
241     */
242    private void registerExtendedClass(String qualifiedClassName,
243                                       ClassDesc currentClass) {
244        final String superClassName = getSuperClassName(currentClass.getTypeDeclarationAst());
245        if (superClassName != null) {
246            final ToIntFunction<ClassDesc> nestedClassCountProvider = classDesc -> {
247                return CheckUtil.typeDeclarationNameMatchingCount(qualifiedClassName,
248                                                                  classDesc.getQualifiedName());
249            };
250            getNearestClassWithSameName(superClassName, nestedClassCountProvider)
251                .or(() -> Optional.ofNullable(innerClasses.get(superClassName)))
252                .ifPresent(ClassDesc::registerNestedSubclass);
253        }
254    }
255
256    /**
257     * Register to the super class of anonymous inner class that the given class is instantiated
258     * by an anonymous inner class.
259     *
260     * @param literalNewAst ast node of {@link TokenTypes#LITERAL_NEW} representing anonymous inner
261     *                      class
262     * @param outerTypeDeclName Fully qualified name of the outer type declaration of anonymous
263     *                          inner class
264     */
265    private void registerAnonymousInnerClassToSuperClass(DetailAST literalNewAst,
266                                                         String outerTypeDeclName) {
267        final String superClassName = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst);
268
269        final ToIntFunction<ClassDesc> anonClassCountProvider = classDesc -> {
270            return getAnonSuperTypeMatchingCount(outerTypeDeclName, classDesc.getQualifiedName());
271        };
272        getNearestClassWithSameName(superClassName, anonClassCountProvider)
273            .or(() -> Optional.ofNullable(innerClasses.get(superClassName)))
274            .ifPresent(ClassDesc::registerSuperClassOfAnonymousInnerClass);
275    }
276
277    /**
278     * Get the nearest class with same name.
279     *
280     * <p>The parameter {@code countProvider} exists because if the class being searched is the
281     * super class of anonymous inner class, the rules of evaluation are a bit different,
282     * consider the following example-
283     * <pre>
284     * {@code
285     * public class Main {
286     *     static class One {
287     *         static class Two {
288     *         }
289     *     }
290     *
291     *     class Three {
292     *         One.Two object = new One.Two() { // Object of Main.Three.One.Two
293     *                                          // and not of Main.One.Two
294     *         };
295     *
296     *         static class One {
297     *             static class Two {
298     *             }
299     *         }
300     *     }
301     * }
302     * }
303     * </pre>
304     * If the {@link Function} {@code countProvider} hadn't used
305     * {@link FinalClassCheck#getAnonSuperTypeMatchingCount} to
306     * calculate the matching count then the logic would have falsely evaluated
307     * {@code Main.One.Two} to be the super class of the anonymous inner class.
308     *
309     * @param className name of the class
310     * @param countProvider the function to apply to calculate the name matching count
311     * @return {@link Optional} of {@link ClassDesc} object of the nearest class with the same name.
312     * @noinspection CallToStringConcatCanBeReplacedByOperator
313     * @noinspectionreason CallToStringConcatCanBeReplacedByOperator - operator causes
314     *      pitest to fail
315     */
316    private Optional<ClassDesc> getNearestClassWithSameName(String className,
317        ToIntFunction<ClassDesc> countProvider) {
318        final String dotAndClassName = PACKAGE_SEPARATOR.concat(className);
319        final Comparator<ClassDesc> longestMatch = Comparator.comparingInt(countProvider);
320        return innerClasses.entrySet().stream()
321                .filter(entry -> entry.getKey().endsWith(dotAndClassName))
322                .map(Map.Entry::getValue)
323                .min(longestMatch.reversed().thenComparingInt(ClassDesc::getDepth));
324    }
325
326    /**
327     * Extract the qualified type declaration name from given type declaration Ast.
328     *
329     * @param typeDeclarationAst type declaration for which qualified name is being fetched
330     * @return qualified name of a type declaration
331     */
332    private String extractQualifiedTypeName(DetailAST typeDeclarationAst) {
333        final String className = typeDeclarationAst.findFirstToken(TokenTypes.IDENT).getText();
334        String outerTypeDeclarationQualifiedName = null;
335        if (!typeDeclarations.isEmpty()) {
336            outerTypeDeclarationQualifiedName = typeDeclarations.peek().getQualifiedName();
337        }
338        return CheckUtil.getQualifiedTypeDeclarationName(packageName,
339                                                         outerTypeDeclarationQualifiedName,
340                                                         className);
341    }
342
343    /**
344     * Get super class name of given class.
345     *
346     * @param classAst class
347     * @return super class name or null if super class is not specified
348     */
349    private static String getSuperClassName(DetailAST classAst) {
350        String superClassName = null;
351        final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
352        if (classExtend != null) {
353            superClassName = CheckUtil.extractQualifiedName(classExtend.getFirstChild());
354        }
355        return superClassName;
356    }
357
358    /**
359     * Calculates and returns the type declaration matching count when {@code classToBeMatched} is
360     * considered to be super class of an anonymous inner class.
361     *
362     * <p>
363     * Suppose our pattern class is {@code Main.ClassOne} and class to be matched is
364     * {@code Main.ClassOne.ClassTwo.ClassThree} then type declaration name matching count would
365     * be calculated by comparing every character, and updating main counter when we hit "." or
366     * when it is the last character of the pattern class and certain conditions are met. This is
367     * done so that matching count is 13 instead of 5. This is due to the fact that pattern class
368     * can contain anonymous inner class object of a nested class which isn't true in case of
369     * extending classes as you can't extend nested classes.
370     * </p>
371     *
372     * @param patternTypeDeclaration type declaration against which the given type declaration has
373     *                               to be matched
374     * @param typeDeclarationToBeMatched type declaration to be matched
375     * @return type declaration matching count
376     */
377    private static int getAnonSuperTypeMatchingCount(String patternTypeDeclaration,
378                                                    String typeDeclarationToBeMatched) {
379        final int typeDeclarationToBeMatchedLength = typeDeclarationToBeMatched.length();
380        final int minLength = Math
381            .min(typeDeclarationToBeMatchedLength, patternTypeDeclaration.length());
382        final char packageSeparator = PACKAGE_SEPARATOR.charAt(0);
383        final boolean shouldCountBeUpdatedAtLastCharacter =
384            typeDeclarationToBeMatchedLength > minLength
385                && typeDeclarationToBeMatched.charAt(minLength) == packageSeparator;
386
387        int result = 0;
388        for (int idx = 0;
389             idx < minLength
390                 && patternTypeDeclaration.charAt(idx) == typeDeclarationToBeMatched.charAt(idx);
391             idx++) {
392
393            if (idx == minLength - 1 && shouldCountBeUpdatedAtLastCharacter
394                || patternTypeDeclaration.charAt(idx) == packageSeparator) {
395                result = idx;
396            }
397        }
398        return result;
399    }
400
401    /**
402     * Maintains information about the type of declaration.
403     * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF}
404     * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF}
405     * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration.
406     * It does not maintain information about classes, a subclass called {@link ClassDesc}
407     * does that job.
408     */
409    private static class TypeDeclarationDescription {
410
411        /**
412         * Complete type declaration name with package name and outer type declaration name.
413         */
414        private final String qualifiedName;
415
416        /**
417         * Depth of nesting of type declaration.
418         */
419        private final int depth;
420
421        /**
422         * Type declaration ast node.
423         */
424        private final DetailAST typeDeclarationAst;
425
426        /**
427         * Create an instance of TypeDeclarationDescription.
428         *
429         * @param qualifiedName Complete type declaration name with package name and outer type
430         *                      declaration name.
431         * @param depth Depth of nesting of type declaration
432         * @param typeDeclarationAst Type declaration ast node
433         */
434        private TypeDeclarationDescription(String qualifiedName, int depth,
435                                          DetailAST typeDeclarationAst) {
436            this.qualifiedName = qualifiedName;
437            this.depth = depth;
438            this.typeDeclarationAst = typeDeclarationAst;
439        }
440
441        /**
442         * Get the complete type declaration name i.e. type declaration name with package name
443         * and outer type declaration name.
444         *
445         * @return qualified class name
446         */
447        protected String getQualifiedName() {
448            return qualifiedName;
449        }
450
451        /**
452         * Get the depth of type declaration.
453         *
454         * @return the depth of nesting of type declaration
455         */
456        protected int getDepth() {
457            return depth;
458        }
459
460        /**
461         * Get the type declaration ast node.
462         *
463         * @return ast node of the type declaration
464         */
465        protected DetailAST getTypeDeclarationAst() {
466            return typeDeclarationAst;
467        }
468    }
469
470    /**
471     * Maintains information about the class.
472     */
473    private static final class ClassDesc extends TypeDeclarationDescription {
474
475        /** Is class declared as final. */
476        private final boolean declaredAsFinal;
477
478        /** Is class declared as abstract. */
479        private final boolean declaredAsAbstract;
480
481        /** Is class contains private modifier. */
482        private final boolean declaredAsPrivate;
483
484        /** Does class have implicit constructor. */
485        private final boolean hasDeclaredConstructor;
486
487        /** Does class have non-private ctors. */
488        private boolean withNonPrivateCtor;
489
490        /** Does class have nested subclass. */
491        private boolean withNestedSubclass;
492
493        /** Whether the class is the super class of an anonymous inner class. */
494        private boolean superClassOfAnonymousInnerClass;
495
496        /**
497         *  Create a new ClassDesc instance.
498         *
499         *  @param qualifiedName qualified class name(with package)
500         *  @param depth class nesting level
501         *  @param classAst classAst node
502         */
503        private ClassDesc(String qualifiedName, int depth, DetailAST classAst) {
504            super(qualifiedName, depth, classAst);
505            final DetailAST modifiers = classAst.findFirstToken(TokenTypes.MODIFIERS);
506            declaredAsFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
507            declaredAsAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
508            declaredAsPrivate = modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
509            hasDeclaredConstructor =
510                    classAst.getLastChild().findFirstToken(TokenTypes.CTOR_DEF) == null;
511        }
512
513        /** Adds non-private ctor. */
514        private void registerNonPrivateCtor() {
515            withNonPrivateCtor = true;
516        }
517
518        /** Adds nested subclass. */
519        private void registerNestedSubclass() {
520            withNestedSubclass = true;
521        }
522
523        /** Adds anonymous inner class. */
524        private void registerSuperClassOfAnonymousInnerClass() {
525            superClassOfAnonymousInnerClass = true;
526        }
527
528        /**
529         *  Does class have non-private ctors.
530         *
531         *  @return true if class has non-private ctors
532         */
533        private boolean isWithNonPrivateCtor() {
534            return withNonPrivateCtor;
535        }
536
537        /**
538         * Does class have nested subclass.
539         *
540         * @return true if class has nested subclass
541         */
542        private boolean isWithNestedSubclass() {
543            return withNestedSubclass;
544        }
545
546        /**
547         *  Is class declared as final.
548         *
549         *  @return true if class is declared as final
550         */
551        private boolean isDeclaredAsFinal() {
552            return declaredAsFinal;
553        }
554
555        /**
556         *  Is class declared as abstract.
557         *
558         *  @return true if class is declared as final
559         */
560        private boolean isDeclaredAsAbstract() {
561            return declaredAsAbstract;
562        }
563
564        /**
565         * Whether the class is the super class of an anonymous inner class.
566         *
567         * @return {@code true} if the class is the super class of an anonymous inner class.
568         */
569        private boolean isSuperClassOfAnonymousInnerClass() {
570            return superClassOfAnonymousInnerClass;
571        }
572
573        /**
574         * Does class have implicit constructor.
575         *
576         * @return true if class have implicit constructor
577         */
578        private boolean isHasDeclaredConstructor() {
579            return hasDeclaredConstructor;
580        }
581
582        /**
583         * Does class is private.
584         *
585         * @return true if class is private
586         */
587        private boolean isDeclaredAsPrivate() {
588            return declaredAsPrivate;
589        }
590    }
591}