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