1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.design;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FullIdent;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
31  
32  /**
33   * <p>
34   * Checks that class which has only private ctors
35   * is declared as final. Doesn't check for classes nested in interfaces
36   * or annotations, as they are always {@code final} there.
37   * </p>
38   * <p>
39   * An example of how to configure the check is:
40   * </p>
41   * <pre>
42   * &lt;module name="FinalClass"/&gt;
43   * </pre>
44   */
45  @FileStatefulCheck
46  public class FinalClassCheck
47      extends AbstractCheck {
48  
49      /**
50       * A key is pointing to the warning message text in "messages.properties"
51       * file.
52       */
53      public static final String MSG_KEY = "final.class";
54  
55      /**
56       * Character separate package names in qualified name of java class.
57       */
58      private static final String PACKAGE_SEPARATOR = ".";
59  
60      /** Keeps ClassDesc objects for stack of declared classes. */
61      private Deque<ClassDesc> classes;
62  
63      /** Full qualified name of the package. */
64      private String packageName;
65  
66      @Override
67      public int[] getDefaultTokens() {
68          return getRequiredTokens();
69      }
70  
71      @Override
72      public int[] getAcceptableTokens() {
73          return getRequiredTokens();
74      }
75  
76      @Override
77      public int[] getRequiredTokens() {
78          return new int[] {TokenTypes.CLASS_DEF, TokenTypes.CTOR_DEF, TokenTypes.PACKAGE_DEF};
79      }
80  
81      @Override
82      public void beginTree(DetailAST rootAST) {
83          classes = new ArrayDeque<>();
84          packageName = "";
85      }
86  
87      @Override
88      public void visitToken(DetailAST ast) {
89          final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
90  
91          switch (ast.getType()) {
92              case TokenTypes.PACKAGE_DEF:
93                  packageName = extractQualifiedName(ast.getFirstChild().getNextSibling());
94                  break;
95  
96              case TokenTypes.CLASS_DEF:
97                  registerNestedSubclassToOuterSuperClasses(ast);
98  
99                  final boolean isFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
100                 final boolean isAbstract = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
101 
102                 final String qualifiedClassName = getQualifiedClassName(ast);
103                 classes.push(new ClassDesc(qualifiedClassName, isFinal, isAbstract));
104                 break;
105 
106             case TokenTypes.CTOR_DEF:
107                 if (!ScopeUtil.isInEnumBlock(ast)) {
108                     final ClassDesc desc = classes.peek();
109                     if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
110                         desc.registerNonPrivateCtor();
111                     }
112                     else {
113                         desc.registerPrivateCtor();
114                     }
115                 }
116                 break;
117 
118             default:
119                 throw new IllegalStateException(ast.toString());
120         }
121     }
122 
123     @Override
124     public void leaveToken(DetailAST ast) {
125         if (ast.getType() == TokenTypes.CLASS_DEF) {
126             final ClassDesc desc = classes.pop();
127             if (desc.isWithPrivateCtor()
128                 && !desc.isDeclaredAsAbstract()
129                 && !desc.isDeclaredAsFinal()
130                 && !desc.isWithNonPrivateCtor()
131                 && !desc.isWithNestedSubclass()
132                 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
133                 final String qualifiedName = desc.getQualifiedName();
134                 final String className = getClassNameFromQualifiedName(qualifiedName);
135                 log(ast.getLineNo(), MSG_KEY, className);
136             }
137         }
138     }
139 
140     /**
141      * Get name of class (with qualified package if specified) in {@code ast}.
142      * @param ast ast to extract class name from
143      * @return qualified name
144      */
145     private static String extractQualifiedName(DetailAST ast) {
146         return FullIdent.createFullIdent(ast).getText();
147     }
148 
149     /**
150      * Register to outer super classes of given classAst that
151      * given classAst is extending them.
152      * @param classAst class which outer super classes will be
153      *                 informed about nesting subclass
154      */
155     private void registerNestedSubclassToOuterSuperClasses(DetailAST classAst) {
156         final String currentAstSuperClassName = getSuperClassName(classAst);
157         if (currentAstSuperClassName != null) {
158             for (ClassDesc classDesc : classes) {
159                 final String classDescQualifiedName = classDesc.getQualifiedName();
160                 if (doesNameInExtendMatchSuperClassName(classDescQualifiedName,
161                         currentAstSuperClassName)) {
162                     classDesc.registerNestedSubclass();
163                 }
164             }
165         }
166     }
167 
168     /**
169      * Get qualified class name from given class Ast.
170      * @param classAst class to get qualified class name
171      * @return qualified class name of a class
172      */
173     private String getQualifiedClassName(DetailAST classAst) {
174         final String className = classAst.findFirstToken(TokenTypes.IDENT).getText();
175         String outerClassQualifiedName = null;
176         if (!classes.isEmpty()) {
177             outerClassQualifiedName = classes.peek().getQualifiedName();
178         }
179         return getQualifiedClassName(packageName, outerClassQualifiedName, className);
180     }
181 
182     /**
183      * Calculate qualified class name(package + class name) laying inside given
184      * outer class.
185      * @param packageName package name, empty string on default package
186      * @param outerClassQualifiedName qualified name(package + class) of outer class,
187      *                           null if doesn't exist
188      * @param className class name
189      * @return qualified class name(package + class name)
190      */
191     private static String getQualifiedClassName(String packageName, String outerClassQualifiedName,
192                                                 String className) {
193         final String qualifiedClassName;
194 
195         if (outerClassQualifiedName == null) {
196             if (packageName.isEmpty()) {
197                 qualifiedClassName = className;
198             }
199             else {
200                 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
201             }
202         }
203         else {
204             qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
205         }
206         return qualifiedClassName;
207     }
208 
209     /**
210      * Get super class name of given class.
211      * @param classAst class
212      * @return super class name or null if super class is not specified
213      */
214     private static String getSuperClassName(DetailAST classAst) {
215         String superClassName = null;
216         final DetailAST classExtend = classAst.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
217         if (classExtend != null) {
218             superClassName = extractQualifiedName(classExtend.getFirstChild());
219         }
220         return superClassName;
221     }
222 
223     /**
224      * Checks if given super class name in extend clause match super class qualified name.
225      * @param superClassQualifiedName super class qualified name (with package)
226      * @param superClassInExtendClause name in extend clause
227      * @return true if given super class name in extend clause match super class qualified name,
228      *         false otherwise
229      */
230     private static boolean doesNameInExtendMatchSuperClassName(String superClassQualifiedName,
231                                                                String superClassInExtendClause) {
232         String superClassNormalizedName = superClassQualifiedName;
233         if (!superClassInExtendClause.contains(PACKAGE_SEPARATOR)) {
234             superClassNormalizedName = getClassNameFromQualifiedName(superClassQualifiedName);
235         }
236         return superClassNormalizedName.equals(superClassInExtendClause);
237     }
238 
239     /**
240      * Get class name from qualified name.
241      * @param qualifiedName qualified class name
242      * @return class name
243      */
244     private static String getClassNameFromQualifiedName(String qualifiedName) {
245         return qualifiedName.substring(qualifiedName.lastIndexOf(PACKAGE_SEPARATOR) + 1);
246     }
247 
248     /** Maintains information about class' ctors. */
249     private static final class ClassDesc {
250 
251         /** Qualified class name(with package). */
252         private final String qualifiedName;
253 
254         /** Is class declared as final. */
255         private final boolean declaredAsFinal;
256 
257         /** Is class declared as abstract. */
258         private final boolean declaredAsAbstract;
259 
260         /** Does class have non-private ctors. */
261         private boolean withNonPrivateCtor;
262 
263         /** Does class have private ctors. */
264         private boolean withPrivateCtor;
265 
266         /** Does class have nested subclass. */
267         private boolean withNestedSubclass;
268 
269         /**
270          *  Create a new ClassDesc instance.
271          *  @param qualifiedName qualified class name(with package)
272          *  @param declaredAsFinal indicates if the
273          *         class declared as final
274          *  @param declaredAsAbstract indicates if the
275          *         class declared as abstract
276          */
277         /* package */ ClassDesc(String qualifiedName, boolean declaredAsFinal,
278                 boolean declaredAsAbstract) {
279             this.qualifiedName = qualifiedName;
280             this.declaredAsFinal = declaredAsFinal;
281             this.declaredAsAbstract = declaredAsAbstract;
282         }
283 
284         /**
285          * Get qualified class name.
286          * @return qualified class name
287          */
288         private String getQualifiedName() {
289             return qualifiedName;
290         }
291 
292         /** Adds private ctor. */
293         private void registerPrivateCtor() {
294             withPrivateCtor = true;
295         }
296 
297         /** Adds non-private ctor. */
298         private void registerNonPrivateCtor() {
299             withNonPrivateCtor = true;
300         }
301 
302         /** Adds nested subclass. */
303         private void registerNestedSubclass() {
304             withNestedSubclass = true;
305         }
306 
307         /**
308          *  Does class have private ctors.
309          *  @return true if class has private ctors
310          */
311         private boolean isWithPrivateCtor() {
312             return withPrivateCtor;
313         }
314 
315         /**
316          *  Does class have non-private ctors.
317          *  @return true if class has non-private ctors
318          */
319         private boolean isWithNonPrivateCtor() {
320             return withNonPrivateCtor;
321         }
322 
323         /**
324          * Does class have nested subclass.
325          * @return true if class has nested subclass
326          */
327         private boolean isWithNestedSubclass() {
328             return withNestedSubclass;
329         }
330 
331         /**
332          *  Is class declared as final.
333          *  @return true if class is declared as final
334          */
335         private boolean isDeclaredAsFinal() {
336             return declaredAsFinal;
337         }
338 
339         /**
340          *  Is class declared as abstract.
341          *  @return true if class is declared as final
342          */
343         private boolean isDeclaredAsAbstract() {
344             return declaredAsAbstract;
345         }
346 
347     }
348 
349 }