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