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