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