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