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