1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
34
35
36
37
38
39
40
41
42
43
44
45 @FileStatefulCheck
46 public class FinalClassCheck
47 extends AbstractCheck {
48
49
50
51
52
53 public static final String MSG_KEY = "final.class";
54
55
56
57
58 private static final String PACKAGE_SEPARATOR = ".";
59
60
61 private Deque<ClassDesc> classes;
62
63
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
142
143
144
145 private static String extractQualifiedName(DetailAST ast) {
146 return FullIdent.createFullIdent(ast).getText();
147 }
148
149
150
151
152
153
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
170
171
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
184
185
186
187
188
189
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
211
212
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
225
226
227
228
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
241
242
243
244 private static String getClassNameFromQualifiedName(String qualifiedName) {
245 return qualifiedName.substring(qualifiedName.lastIndexOf(PACKAGE_SEPARATOR) + 1);
246 }
247
248
249 private static final class ClassDesc {
250
251
252 private final String qualifiedName;
253
254
255 private final boolean declaredAsFinal;
256
257
258 private final boolean declaredAsAbstract;
259
260
261 private boolean withNonPrivateCtor;
262
263
264 private boolean withPrivateCtor;
265
266
267 private boolean withNestedSubclass;
268
269
270
271
272
273
274
275
276
277 ClassDesc(String qualifiedName, boolean declaredAsFinal,
278 boolean declaredAsAbstract) {
279 this.qualifiedName = qualifiedName;
280 this.declaredAsFinal = declaredAsFinal;
281 this.declaredAsAbstract = declaredAsAbstract;
282 }
283
284
285
286
287
288 private String getQualifiedName() {
289 return qualifiedName;
290 }
291
292
293 private void registerPrivateCtor() {
294 withPrivateCtor = true;
295 }
296
297
298 private void registerNonPrivateCtor() {
299 withNonPrivateCtor = true;
300 }
301
302
303 private void registerNestedSubclass() {
304 withNestedSubclass = true;
305 }
306
307
308
309
310
311 private boolean isWithPrivateCtor() {
312 return withPrivateCtor;
313 }
314
315
316
317
318
319 private boolean isWithNonPrivateCtor() {
320 return withNonPrivateCtor;
321 }
322
323
324
325
326
327 private boolean isWithNestedSubclass() {
328 return withNestedSubclass;
329 }
330
331
332
333
334
335 private boolean isDeclaredAsFinal() {
336 return declaredAsFinal;
337 }
338
339
340
341
342
343 private boolean isDeclaredAsAbstract() {
344 return declaredAsAbstract;
345 }
346
347 }
348
349 }