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