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