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 package com.qulice.checkstyle;
32
33 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
34 import com.puppycrawl.tools.checkstyle.api.DetailAST;
35 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
37 import java.util.ArrayDeque;
38 import java.util.Deque;
39 import org.cactoos.text.Joined;
40 import org.cactoos.text.UncheckedText;
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public final class ProhibitNonFinalClassesCheck extends AbstractCheck {
55
56
57
58
59 private static final String PACKAGE_SEPARATOR = ".";
60
61
62
63
64 private Deque<ClassDesc> classes = new ArrayDeque<>();
65
66
67
68
69 private String pack;
70
71 @Override
72 public int[] getDefaultTokens() {
73 return this.getRequiredTokens();
74 }
75
76 @Override
77 public int[] getAcceptableTokens() {
78 return this.getRequiredTokens();
79 }
80
81 @Override
82 public int[] getRequiredTokens() {
83 return new int[] {TokenTypes.CLASS_DEF};
84 }
85
86 @Override
87 public void beginTree(final DetailAST root) {
88 this.classes = new ArrayDeque<>();
89 this.pack = "";
90 }
91
92 @Override
93 public void visitToken(final DetailAST ast) {
94 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
95 if (ast.getType() == TokenTypes.CLASS_DEF) {
96 final boolean isfinal =
97 modifiers.findFirstToken(TokenTypes.FINAL) != null;
98 final boolean isabstract =
99 modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
100 final String qualified = this.qualifiedClassName(ast);
101 this.classes.push(
102 new ClassDesc(qualified, isfinal, isabstract)
103 );
104 }
105 }
106
107 @Override
108 public void leaveToken(final DetailAST ast) {
109 if (ast.getType() == TokenTypes.CLASS_DEF) {
110 final ClassDesc desc = this.classes.pop();
111 if (!desc.isDeclaredAsAbstract()
112 && !desc.isAsfinal()
113 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
114 final String qualified = desc.getQualified();
115 final String name =
116 ProhibitNonFinalClassesCheck.getClassNameFromQualifiedName(
117 qualified
118 );
119 log(ast.getLineNo(), "Classes should be final", name);
120 }
121 }
122 }
123
124
125
126
127
128
129 private String qualifiedClassName(final DetailAST classast) {
130 final String name = classast.findFirstToken(
131 TokenTypes.IDENT
132 ).getText();
133 String outer = null;
134 if (!this.classes.isEmpty()) {
135 outer = this.classes.peek().getQualified();
136 }
137 return ProhibitNonFinalClassesCheck.getQualifiedClassName(
138 this.pack,
139 outer,
140 name
141 );
142 }
143
144
145
146
147
148
149
150
151
152
153 private static String getQualifiedClassName(
154 final String pack,
155 final String outer,
156 final String name) {
157 final String qualified;
158 if (outer == null) {
159 if (pack.isEmpty()) {
160 qualified = name;
161 } else {
162 qualified =
163 new UncheckedText(
164 new Joined(
165 ProhibitNonFinalClassesCheck.PACKAGE_SEPARATOR,
166 pack,
167 name
168 )
169 ).asString();
170 }
171 } else {
172 qualified =
173 new UncheckedText(
174 new Joined(
175 ProhibitNonFinalClassesCheck.PACKAGE_SEPARATOR,
176 outer,
177 name
178 )
179 ).asString();
180 }
181 return qualified;
182 }
183
184
185
186
187
188
189 private static String getClassNameFromQualifiedName(
190 final String qualified
191 ) {
192 return qualified.substring(
193 qualified.lastIndexOf(
194 ProhibitNonFinalClassesCheck.PACKAGE_SEPARATOR
195 ) + 1
196 );
197 }
198
199
200
201
202
203
204 private static final class ClassDesc {
205
206
207
208
209 private final String qualified;
210
211
212
213
214 private final boolean asfinal;
215
216
217
218
219 private final boolean asabstract;
220
221
222
223
224
225
226
227
228
229 ClassDesc(final String qualified, final boolean asfinal,
230 final boolean asabstract
231 ) {
232 this.qualified = qualified;
233 this.asfinal = asfinal;
234 this.asabstract = asabstract;
235 }
236
237
238
239
240
241 private String getQualified() {
242 return this.qualified;
243 }
244
245
246
247
248
249 private boolean isAsfinal() {
250 return this.asfinal;
251 }
252
253
254
255
256
257 private boolean isDeclaredAsAbstract() {
258 return this.asabstract;
259 }
260 }
261 }