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