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.coding;
21
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.Set;
25 import java.util.stream.Collectors;
26
27 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.FullIdent;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
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 @FileStatefulCheck
86 public class IllegalInstantiationCheck
87 extends AbstractCheck {
88
89
90
91
92
93 public static final String MSG_KEY = "instantiation.avoid";
94
95
96 private static final String JAVA_LANG = "java.lang.";
97
98
99 private final Set<FullIdent> imports = new HashSet<>();
100
101
102 private final Set<String> classNames = new HashSet<>();
103
104
105 private final Set<DetailAST> instantiations = new HashSet<>();
106
107
108 private Set<String> classes = new HashSet<>();
109
110
111 private String pkgName;
112
113 @Override
114 public int[] getDefaultTokens() {
115 return getRequiredTokens();
116 }
117
118 @Override
119 public int[] getAcceptableTokens() {
120 return getRequiredTokens();
121 }
122
123 @Override
124 public int[] getRequiredTokens() {
125 return new int[] {
126 TokenTypes.IMPORT,
127 TokenTypes.LITERAL_NEW,
128 TokenTypes.PACKAGE_DEF,
129 TokenTypes.CLASS_DEF,
130 };
131 }
132
133 @Override
134 public void beginTree(DetailAST rootAST) {
135 pkgName = null;
136 imports.clear();
137 instantiations.clear();
138 classNames.clear();
139 }
140
141 @Override
142 public void visitToken(DetailAST ast) {
143 switch (ast.getType()) {
144 case TokenTypes.LITERAL_NEW -> processLiteralNew(ast);
145 case TokenTypes.PACKAGE_DEF -> processPackageDef(ast);
146 case TokenTypes.IMPORT -> processImport(ast);
147 case TokenTypes.CLASS_DEF -> processClassDef(ast);
148 default -> throw new IllegalArgumentException("Unknown type " + ast);
149 }
150 }
151
152 @Override
153 public void finishTree(DetailAST rootAST) {
154 instantiations.forEach(this::postProcessLiteralNew);
155 }
156
157
158
159
160
161
162
163 private void processClassDef(DetailAST ast) {
164 final DetailAST identToken = ast.findFirstToken(TokenTypes.IDENT);
165 final String className = identToken.getText();
166 classNames.add(className);
167 }
168
169
170
171
172
173
174 private void processImport(DetailAST ast) {
175 final FullIdent name = FullIdent.createFullIdentBelow(ast);
176
177
178 imports.add(name);
179 }
180
181
182
183
184
185
186 private void processPackageDef(DetailAST ast) {
187 final DetailAST packageNameAST = ast.getLastChild()
188 .getPreviousSibling();
189 final FullIdent packageIdent =
190 FullIdent.createFullIdent(packageNameAST);
191 pkgName = packageIdent.getText();
192 }
193
194
195
196
197
198
199 private void processLiteralNew(DetailAST ast) {
200 if (ast.getParent().getType() != TokenTypes.METHOD_REF) {
201 instantiations.add(ast);
202 }
203 }
204
205
206
207
208
209
210
211 private void postProcessLiteralNew(DetailAST newTokenAst) {
212 final DetailAST typeNameAst = newTokenAst.getFirstChild();
213 final DetailAST nameSibling = typeNameAst.getNextSibling();
214 if (nameSibling.getType() != TokenTypes.ARRAY_DECLARATOR) {
215
216 final FullIdent typeIdent = FullIdent.createFullIdent(typeNameAst);
217 final String typeName = typeIdent.getText();
218 final String fqClassName = getIllegalInstantiation(typeName);
219 if (fqClassName != null) {
220 log(newTokenAst, MSG_KEY, fqClassName);
221 }
222 }
223 }
224
225
226
227
228
229
230
231
232 private String getIllegalInstantiation(String className) {
233 String fullClassName = null;
234
235 if (classes.contains(className)) {
236 fullClassName = className;
237 }
238 else {
239 final int pkgNameLen;
240
241 if (pkgName == null) {
242 pkgNameLen = 0;
243 }
244 else {
245 pkgNameLen = pkgName.length();
246 }
247
248 for (String illegal : classes) {
249 if (isSamePackage(className, pkgNameLen, illegal)
250 || isStandardClass(className, illegal)) {
251 fullClassName = illegal;
252 }
253 else {
254 fullClassName = checkImportStatements(className);
255 }
256
257 if (fullClassName != null) {
258 break;
259 }
260 }
261 }
262 return fullClassName;
263 }
264
265
266
267
268
269
270
271 private String checkImportStatements(String className) {
272 String illegalType = null;
273
274 for (FullIdent importLineText : imports) {
275 String importArg = importLineText.getText();
276 if (importArg.endsWith(".*")) {
277 importArg = importArg.substring(0, importArg.length() - 1)
278 + className;
279 }
280 if (CommonUtil.baseClassName(importArg).equals(className)
281 && classes.contains(importArg)) {
282 illegalType = importArg;
283 break;
284 }
285 }
286 return illegalType;
287 }
288
289
290
291
292
293
294
295
296
297 private boolean isSamePackage(String className, int pkgNameLen, String illegal) {
298
299
300
301
302
303
304
305 return pkgName != null
306 && className.length() == illegal.length() - pkgNameLen - 1
307 && illegal.charAt(pkgNameLen) == '.'
308 && illegal.endsWith(className)
309 && illegal.startsWith(pkgName);
310 }
311
312
313
314
315
316
317
318
319 private boolean isStandardClass(String className, String illegal) {
320 boolean isStandardClass = false;
321
322 if (illegal.length() - JAVA_LANG.length() == className.length()
323 && illegal.endsWith(className)
324 && illegal.startsWith(JAVA_LANG)) {
325
326
327
328
329
330
331 final boolean isSameFile = classNames.contains(className);
332
333 if (!isSameFile) {
334 isStandardClass = true;
335 }
336 }
337 return isStandardClass;
338 }
339
340
341
342
343
344
345
346 public void setClasses(String... names) {
347 classes = Arrays.stream(names).collect(Collectors.toUnmodifiableSet());
348 }
349
350 }