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