001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code 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.Deque;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.Optional;
027
028import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
033import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
034import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
035
036/**
037 * <p>
038 * Checks that a class that has only private constructors and has no descendant
039 * classes is declared as final.
040 * </p>
041 * <p>
042 * To configure the check:
043 * </p>
044 * <pre>
045 * &lt;module name=&quot;FinalClass&quot;/&gt;
046 * </pre>
047 * <p>
048 * Example:
049 * </p>
050 * <pre>
051 * final class MyClass {  // OK
052 *   private MyClass() { }
053 * }
054 *
055 * class MyClass { // violation, class should be declared final
056 *   private MyClass() { }
057 * }
058 *
059 * class MyClass { // OK, since it has a public constructor
060 *   int field1;
061 *   String field2;
062 *   private MyClass(int value) {
063 *     this.field1 = value;
064 *     this.field2 = " ";
065 *   }
066 *   public MyClass(String value) {
067 *     this.field2 = value;
068 *     this.field1 = 0;
069 *   }
070 * }
071 *
072 * class TestAnonymousInnerClasses { // OK, class has an anonymous inner class.
073 *     public static final TestAnonymousInnerClasses ONE = new TestAnonymousInnerClasses() {
074 *
075 *     };
076 *
077 *     private TestAnonymousInnerClasses() {
078 *     }
079 * }
080 * </pre>
081 * <p>
082 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
083 * </p>
084 * <p>
085 * Violation Message Keys:
086 * </p>
087 * <ul>
088 * <li>
089 * {@code final.class}
090 * </li>
091 * </ul>
092 *
093 * @since 3.1
094 */
095@FileStatefulCheck
096public class FinalClassCheck
097    extends AbstractCheck {
098
099    /**
100     * A key is pointing to the warning message text in "messages.properties"
101     * file.
102     */
103    public static final String MSG_KEY = "final.class";
104
105    /**
106     * Character separate package names in qualified name of java class.
107     */
108    private static final String PACKAGE_SEPARATOR = ".";
109
110    /** Keeps ClassDesc objects for all inner classes. */
111    private Map<String, ClassDesc> innerClasses;
112
113    /** Keeps ClassDesc objects for stack of declared classes. */
114    private Deque<ClassDesc> classes;
115
116    /** Full qualified name of the package. */
117    private String packageName;
118
119    @Override
120    public int[] getDefaultTokens() {
121        return getRequiredTokens();
122    }
123
124    @Override
125    public int[] getAcceptableTokens() {
126        return getRequiredTokens();
127    }
128
129    @Override
130    public int[] getRequiredTokens() {
131        return new int[] {
132            TokenTypes.ANNOTATION_DEF,
133            TokenTypes.CLASS_DEF,
134            TokenTypes.ENUM_DEF,
135            TokenTypes.INTERFACE_DEF,
136            TokenTypes.RECORD_DEF,
137            TokenTypes.CTOR_DEF,
138            TokenTypes.PACKAGE_DEF,
139            TokenTypes.LITERAL_NEW,
140        };
141    }
142
143    @Override
144    public void beginTree(DetailAST rootAST) {
145        classes = new ArrayDeque<>();
146        innerClasses = new HashMap<>();
147        packageName = "";
148    }
149
150    @Override
151    public void visitToken(DetailAST ast) {
152        switch (ast.getType()) {
153            case TokenTypes.PACKAGE_DEF:
154                packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling());
155                break;
156
157            case TokenTypes.ANNOTATION_DEF:
158            case TokenTypes.ENUM_DEF:
159            case TokenTypes.INTERFACE_DEF:
160            case TokenTypes.RECORD_DEF:
161                break;
162
163            case TokenTypes.CLASS_DEF:
164                visitClass(ast);
165                break;
166
167            case TokenTypes.CTOR_DEF:
168                visitCtor(ast);
169                break;
170
171            case TokenTypes.LITERAL_NEW:
172                if (ast.getFirstChild() != null
173                        && ast.getLastChild().getType() == TokenTypes.OBJBLOCK) {
174                    for (ClassDesc classDesc : classes) {
175                        if (doesNameOfClassMatchAnonymousInnerClassName(ast, classDesc)) {
176                            classDesc.registerAnonymousInnerClass();
177                        }
178                    }
179                }
180                break;
181
182            default:
183                throw new IllegalStateException(ast.toString());
184        }
185    }
186
187    /**
188     * Called to process a type definition.
189     *
190     * @param ast the token to process
191     */
192    private void visitClass(DetailAST ast) {
193        final String qualifiedClassName = getQualifiedClassName(ast);
194        final ClassDesc currClass = new ClassDesc(qualifiedClassName, classes.size(), ast);
195        classes.push(currClass);
196        innerClasses.put(qualifiedClassName, currClass);
197    }
198
199    /**
200     * Called to process a constructor definition.
201     *
202     * @param ast the token to process
203     */
204    private void visitCtor(DetailAST ast) {
205        if (!ScopeUtil.isInEnumBlock(ast) && !ScopeUtil.isInRecordBlock(ast)) {
206            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
207            final ClassDesc desc = classes.getFirst();
208            if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
209                desc.registerNonPrivateCtor();
210            }
211            else {
212                desc.registerPrivateCtor();
213            }
214        }
215    }
216
217    @Override
218    public void leaveToken(DetailAST ast) {
219        if (ast.getType() == TokenTypes.CLASS_DEF) {
220            classes.pop();
221        }
222        if (TokenUtil.isRootNode(ast.getParent())) {
223            // First pass: mark all classes that have derived inner classes
224            innerClasses.forEach(this::registerNestedSubclassToOuterSuperClasses);
225            // Second pass: report violation for all classes that should be declared as final
226            innerClasses.forEach((qualifiedClassName, classDesc) -> {
227                if (shouldBeDeclaredAsFinal(classDesc)) {
228                    final String className = getClassNameFromQualifiedName(qualifiedClassName);
229                    log(classDesc.getClassAst(), MSG_KEY, className);
230                }
231            });
232        }
233    }
234
235    /**
236     * Checks whether a class should be declared as final or not.
237     *
238     * @param desc description of the class
239     * @return true if given class should be declared as final otherwise false
240     */
241    private static boolean shouldBeDeclaredAsFinal(ClassDesc desc) {
242        return desc.isWithPrivateCtor()
243                && !(desc.isDeclaredAsAbstract()
244                    || desc.isWithAnonymousInnerClass())
245                && !desc.isDeclaredAsFinal()
246                && !desc.isWithNonPrivateCtor()
247                && !desc.isWithNestedSubclass();
248    }
249
250    /**
251     * Register to outer super class of given classAst that
252     * given classAst is extending them.
253     *
254     * @param qualifiedClassName qualifies class name(with package) of the current class
255     * @param currentClass class which outer super class will be informed about nesting subclass
256     */
257    private void registerNestedSubclassToOuterSuperClasses(String qualifiedClassName,
258                                                           ClassDesc currentClass) {
259        final String superClassName = getSuperClassName(currentClass.getClassAst());
260        if (superClassName != null) {
261            final ClassDesc nearest =
262                    getNearestClassWithSameName(superClassName, qualifiedClassName);
263            if (nearest == null) {
264                Optional.ofNullable(innerClasses.get(superClassName))
265                        .ifPresent(ClassDesc::registerNestedSubclass);
266            }
267            else {
268                nearest.registerNestedSubclass();
269            }
270        }
271    }
272
273    /**
274     * Checks if there is a class with same name.
275     *
276     * @param className name of the class
277     * @param superClassName name of the super class
278     * @return true if there is another class with same name.
279     * @noinspection CallToStringConcatCanBeReplacedByOperator
280     */
281    private ClassDesc getNearestClassWithSameName(String className, String superClassName) {
282        final String dotAndClassName = PACKAGE_SEPARATOR.concat(className);
283        return innerClasses.entrySet().stream()
284                .filter(entry -> entry.getKey().endsWith(dotAndClassName))
285                .map(Map.Entry::getValue)
286                .min((first, second) -> {
287                    return getClassDeclarationNameMatchingCountDiff(superClassName, first, second);
288                })
289                .orElse(null);
290    }
291
292    /**
293     * Get the difference between class declaration name matching count. If the difference between
294     * them is zero, then their depth is compared to obtain the result.
295     *
296     * @param superClassName name of the super class
297     * @param firstClass first input class
298     * @param secondClass second input class
299     * @return difference between class declaration name matching count
300     */
301    private static int getClassDeclarationNameMatchingCountDiff(String superClassName,
302                                                                ClassDesc firstClass,
303                                                                ClassDesc secondClass) {
304        int diff = Integer.compare(
305            CheckUtil
306                .typeDeclarationNameMatchingCount(superClassName, secondClass.getQualifiedName()),
307            CheckUtil
308                .typeDeclarationNameMatchingCount(superClassName, firstClass.getQualifiedName()));
309        if (diff == 0) {
310            diff = Integer.compare(firstClass.getDepth(), secondClass.getDepth());
311        }
312        return diff;
313    }
314
315    /**
316     * Check if class name matches with anonymous inner class name.
317     *
318     * @param ast current ast.
319     * @param classDesc class to match.
320     * @return true if current class name matches anonymous inner
321     *         class name.
322     */
323    private static boolean doesNameOfClassMatchAnonymousInnerClassName(DetailAST ast,
324                                                               ClassDesc classDesc) {
325        final String[] className = classDesc.getQualifiedName().split("\\.");
326        return ast.getFirstChild().getText().equals(className[className.length - 1]);
327    }
328
329    /**
330     * Get qualified class name from given class Ast.
331     *
332     * @param classAst class to get qualified class name
333     * @return qualified class name of a class
334     */
335    private String getQualifiedClassName(DetailAST classAst) {
336        final String className = classAst.findFirstToken(TokenTypes.IDENT).getText();
337        String outerClassQualifiedName = null;
338        if (!classes.isEmpty()) {
339            outerClassQualifiedName = classes.peek().getQualifiedName();
340        }
341        return CheckUtil
342            .getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className);
343    }
344
345    /**
346     * Get super class name of given class.
347     *
348     * @param classAst class
349     * @return super class name or null if super class is not specified
350     */
351    private static String getSuperClassName(DetailAST classAst) {
352        String superClassName = null;
353        final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
354        if (classExtend != null) {
355            superClassName = CheckUtil.extractQualifiedName(classExtend.getFirstChild());
356        }
357        return superClassName;
358    }
359
360    /**
361     * Get class name from qualified name.
362     *
363     * @param qualifiedName qualified class name
364     * @return class name
365     */
366    private static String getClassNameFromQualifiedName(String qualifiedName) {
367        return qualifiedName.substring(qualifiedName.lastIndexOf(PACKAGE_SEPARATOR) + 1);
368    }
369
370    /** Maintains information about class' ctors. */
371    private static final class ClassDesc {
372
373        /** Corresponding node. */
374        private final DetailAST classAst;
375
376        /** Qualified class name(with package). */
377        private final String qualifiedName;
378
379        /** Is class declared as final. */
380        private final boolean declaredAsFinal;
381
382        /** Is class declared as abstract. */
383        private final boolean declaredAsAbstract;
384
385        /** Class nesting level. */
386        private final int depth;
387
388        /** Does class have non-private ctors. */
389        private boolean withNonPrivateCtor;
390
391        /** Does class have private ctors. */
392        private boolean withPrivateCtor;
393
394        /** Does class have nested subclass. */
395        private boolean withNestedSubclass;
396
397        /** Does class have anonymous inner class. */
398        private boolean withAnonymousInnerClass;
399
400        /**
401         *  Create a new ClassDesc instance.
402         *
403         *  @param qualifiedName qualified class name(with package)
404         *  @param depth class nesting level
405         *  @param classAst classAst node
406         */
407        /* package */ ClassDesc(String qualifiedName, int depth, DetailAST classAst) {
408            this.qualifiedName = qualifiedName;
409            this.depth = depth;
410            this.classAst = classAst;
411            final DetailAST modifiers = classAst.findFirstToken(TokenTypes.MODIFIERS);
412            declaredAsFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
413            declaredAsAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
414        }
415
416        /**
417         * Get qualified class name.
418         *
419         * @return qualified class name
420         */
421        private String getQualifiedName() {
422            return qualifiedName;
423        }
424
425        /**
426         * Get the classAst node.
427         *
428         * @return classAst node
429         */
430        public DetailAST getClassAst() {
431            return classAst;
432        }
433
434        /** Adds private ctor. */
435        private void registerPrivateCtor() {
436            withPrivateCtor = true;
437        }
438
439        /** Adds non-private ctor. */
440        private void registerNonPrivateCtor() {
441            withNonPrivateCtor = true;
442        }
443
444        /** Adds nested subclass. */
445        private void registerNestedSubclass() {
446            withNestedSubclass = true;
447        }
448
449        /** Adds anonymous inner class. */
450        private void registerAnonymousInnerClass() {
451            withAnonymousInnerClass = true;
452        }
453
454        /**
455         *  Returns class nesting level.
456         *
457         *  @return class nesting level
458         */
459        private int getDepth() {
460            return depth;
461        }
462
463        /**
464         *  Does class have private ctors.
465         *
466         *  @return true if class has private ctors
467         */
468        private boolean isWithPrivateCtor() {
469            return withPrivateCtor;
470        }
471
472        /**
473         *  Does class have non-private ctors.
474         *
475         *  @return true if class has non-private ctors
476         */
477        private boolean isWithNonPrivateCtor() {
478            return withNonPrivateCtor;
479        }
480
481        /**
482         * Does class have nested subclass.
483         *
484         * @return true if class has nested subclass
485         */
486        private boolean isWithNestedSubclass() {
487            return withNestedSubclass;
488        }
489
490        /**
491         *  Is class declared as final.
492         *
493         *  @return true if class is declared as final
494         */
495        private boolean isDeclaredAsFinal() {
496            return declaredAsFinal;
497        }
498
499        /**
500         *  Is class declared as abstract.
501         *
502         *  @return true if class is declared as final
503         */
504        private boolean isDeclaredAsAbstract() {
505            return declaredAsAbstract;
506        }
507
508        /**
509         * Does class have an anonymous inner class.
510         *
511         * @return true if class has anonymous inner class
512         */
513        private boolean isWithAnonymousInnerClass() {
514            return withAnonymousInnerClass;
515        }
516
517    }
518}