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.BitSet;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.Set;
26 import java.util.regex.Pattern;
27
28 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
29 import com.puppycrawl.tools.checkstyle.PropertyType;
30 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
31 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.FullIdent;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
36 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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
86
87
88
89
90
91
92 @FileStatefulCheck
93 public final class IllegalTypeCheck extends AbstractCheck {
94
95
96
97
98
99 public static final String MSG_KEY = "illegal.type";
100
101
102 private static final String[] DEFAULT_ILLEGAL_TYPES = {
103 "HashSet",
104 "HashMap",
105 "LinkedHashMap",
106 "LinkedHashSet",
107 "TreeSet",
108 "TreeMap",
109 "java.util.HashSet",
110 "java.util.HashMap",
111 "java.util.LinkedHashMap",
112 "java.util.LinkedHashSet",
113 "java.util.TreeSet",
114 "java.util.TreeMap",
115 };
116
117
118 private static final String[] DEFAULT_IGNORED_METHOD_NAMES = {
119 "getInitialContext",
120 "getEnvironment",
121 };
122
123
124
125
126
127 private final Set<String> illegalClassNames = new HashSet<>();
128
129 private final Set<String> illegalShortClassNames = new HashSet<>();
130
131 private final Set<String> legalAbstractClassNames = new HashSet<>();
132
133 private final Set<String> ignoredMethodNames = new HashSet<>();
134
135
136
137
138 @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
139 private BitSet memberModifiers = new BitSet();
140
141
142 private Pattern illegalAbstractClassNameFormat = Pattern.compile("^(.*[.])?Abstract.*$");
143
144
145
146
147 private boolean validateAbstractClassNames;
148
149
150 public IllegalTypeCheck() {
151 setIllegalClassNames(DEFAULT_ILLEGAL_TYPES);
152 setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES);
153 }
154
155
156
157
158
159
160
161 public void setIllegalAbstractClassNameFormat(Pattern pattern) {
162 illegalAbstractClassNameFormat = pattern;
163 }
164
165
166
167
168
169
170
171 public void setValidateAbstractClassNames(boolean validateAbstractClassNames) {
172 this.validateAbstractClassNames = validateAbstractClassNames;
173 }
174
175 @Override
176 public int[] getDefaultTokens() {
177 return getAcceptableTokens();
178 }
179
180 @Override
181 public int[] getAcceptableTokens() {
182 return new int[] {
183 TokenTypes.ANNOTATION_FIELD_DEF,
184 TokenTypes.CLASS_DEF,
185 TokenTypes.IMPORT,
186 TokenTypes.INTERFACE_DEF,
187 TokenTypes.METHOD_CALL,
188 TokenTypes.METHOD_DEF,
189 TokenTypes.METHOD_REF,
190 TokenTypes.PARAMETER_DEF,
191 TokenTypes.VARIABLE_DEF,
192 TokenTypes.PATTERN_VARIABLE_DEF,
193 TokenTypes.RECORD_DEF,
194 TokenTypes.RECORD_COMPONENT_DEF,
195 TokenTypes.RECORD_PATTERN_DEF,
196 };
197 }
198
199 @Override
200 public void beginTree(DetailAST rootAST) {
201 illegalShortClassNames.clear();
202 illegalShortClassNames.addAll(illegalClassNames);
203 }
204
205 @Override
206 public int[] getRequiredTokens() {
207 return new int[] {TokenTypes.IMPORT};
208 }
209
210 @Override
211 public void visitToken(DetailAST ast) {
212 switch (ast.getType()) {
213 case TokenTypes.CLASS_DEF,
214 TokenTypes.INTERFACE_DEF,
215 TokenTypes.RECORD_DEF -> visitTypeDef(ast);
216
217 case TokenTypes.METHOD_CALL,
218 TokenTypes.METHOD_REF -> visitMethodCallOrRef(ast);
219
220 case TokenTypes.METHOD_DEF -> visitMethodDef(ast);
221
222 case TokenTypes.VARIABLE_DEF,
223 TokenTypes.ANNOTATION_FIELD_DEF,
224 TokenTypes.PATTERN_VARIABLE_DEF -> visitVariableDef(ast);
225
226 case TokenTypes.RECORD_COMPONENT_DEF,
227 TokenTypes.RECORD_PATTERN_DEF -> checkClassName(ast);
228
229 case TokenTypes.PARAMETER_DEF -> visitParameterDef(ast);
230
231 case TokenTypes.IMPORT -> visitImport(ast);
232
233 default -> throw new IllegalStateException(ast.toString());
234 }
235 }
236
237
238
239
240
241
242
243
244 private boolean isVerifiable(DetailAST methodOrVariableDef) {
245 boolean result = true;
246 if (!memberModifiers.isEmpty()) {
247 final DetailAST modifiersAst = methodOrVariableDef
248 .findFirstToken(TokenTypes.MODIFIERS);
249 result = isContainVerifiableType(modifiersAst);
250 }
251 return result;
252 }
253
254
255
256
257
258
259
260
261 private boolean isContainVerifiableType(DetailAST modifiers) {
262 boolean result = false;
263 for (DetailAST modifier = modifiers.getFirstChild(); modifier != null;
264 modifier = modifier.getNextSibling()) {
265 if (memberModifiers.get(modifier.getType())) {
266 result = true;
267 break;
268 }
269 }
270 return result;
271 }
272
273
274
275
276
277
278 private void visitTypeDef(DetailAST typeDef) {
279 if (isVerifiable(typeDef)) {
280 checkTypeParameters(typeDef);
281 final DetailAST extendsClause = typeDef.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
282 if (extendsClause != null) {
283 checkBaseTypes(extendsClause);
284 }
285 final DetailAST implementsClause = typeDef.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE);
286 if (implementsClause != null) {
287 checkBaseTypes(implementsClause);
288 }
289 }
290 }
291
292
293
294
295
296
297 private void visitMethodDef(DetailAST methodDef) {
298 if (isCheckedMethod(methodDef)) {
299 checkClassName(methodDef);
300 }
301 }
302
303
304
305
306
307
308 private void visitParameterDef(DetailAST parameterDef) {
309 final DetailAST grandParentAST = parameterDef.getParent().getParent();
310
311 if (grandParentAST.getType() == TokenTypes.METHOD_DEF && isCheckedMethod(grandParentAST)) {
312 checkClassName(parameterDef);
313 }
314 }
315
316
317
318
319
320
321 private void visitVariableDef(DetailAST variableDef) {
322 if (isVerifiable(variableDef)) {
323 checkClassName(variableDef);
324 }
325 }
326
327
328
329
330
331
332 private void visitMethodCallOrRef(DetailAST methodCallOrRef) {
333 checkTypeArguments(methodCallOrRef);
334 }
335
336
337
338
339
340
341
342
343 private void visitImport(DetailAST importAst) {
344 if (!isStarImport(importAst)) {
345 final String canonicalName = getImportedTypeCanonicalName(importAst);
346 extendIllegalClassNamesWithShortName(canonicalName);
347 }
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361
362 private static boolean isStarImport(DetailAST importAst) {
363 boolean result = false;
364 DetailAST toVisit = importAst;
365 while (toVisit != null) {
366 toVisit = getNextSubTreeNode(toVisit, importAst);
367 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
368 result = true;
369 break;
370 }
371 }
372 return result;
373 }
374
375
376
377
378
379
380
381 private void checkClassName(DetailAST ast) {
382 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
383 checkType(type);
384 checkTypeParameters(ast);
385 }
386
387
388
389
390
391
392 private void checkIdent(DetailAST type) {
393 final FullIdent ident = FullIdent.createFullIdent(type);
394 if (isMatchingClassName(ident.getText())) {
395 log(ident.getDetailAst(), MSG_KEY, ident.getText());
396 }
397 }
398
399
400
401
402
403
404
405 private void checkBaseTypes(DetailAST clause) {
406 DetailAST child = clause.getFirstChild();
407 while (child != null) {
408 if (child.getType() == TokenTypes.IDENT) {
409 checkIdent(child);
410 }
411 else {
412 TokenUtil.forEachChild(child, TokenTypes.TYPE_ARGUMENT, this::checkType);
413 }
414 child = child.getNextSibling();
415 }
416 }
417
418
419
420
421
422
423 private void checkType(DetailAST type) {
424 checkIdent(type.getFirstChild());
425 checkTypeArguments(type);
426 checkTypeBounds(type);
427 }
428
429
430
431
432
433
434 private void checkTypeBounds(DetailAST type) {
435 final DetailAST upperBounds = type.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS);
436 if (upperBounds != null) {
437 checkType(upperBounds);
438 }
439 final DetailAST lowerBounds = type.findFirstToken(TokenTypes.TYPE_LOWER_BOUNDS);
440 if (lowerBounds != null) {
441 checkType(lowerBounds);
442 }
443 }
444
445
446
447
448
449
450 private void checkTypeParameters(final DetailAST node) {
451 final DetailAST typeParameters = node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
452 if (typeParameters != null) {
453 TokenUtil.forEachChild(typeParameters, TokenTypes.TYPE_PARAMETER, this::checkType);
454 }
455 }
456
457
458
459
460
461
462 private void checkTypeArguments(final DetailAST node) {
463 DetailAST typeArguments = node.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
464 if (typeArguments == null) {
465 typeArguments = node.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
466 }
467
468 if (typeArguments != null) {
469 TokenUtil.forEachChild(typeArguments, TokenTypes.TYPE_ARGUMENT, this::checkType);
470 }
471 }
472
473
474
475
476
477
478
479
480 private boolean isMatchingClassName(String className) {
481 final String shortName = className.substring(className.lastIndexOf('.') + 1);
482 return illegalClassNames.contains(className)
483 || illegalShortClassNames.contains(shortName)
484 || validateAbstractClassNames
485 && !legalAbstractClassNames.contains(className)
486 && illegalAbstractClassNameFormat.matcher(className).find();
487 }
488
489
490
491
492
493
494
495
496 private void extendIllegalClassNamesWithShortName(String canonicalName) {
497 if (illegalClassNames.contains(canonicalName)) {
498 final String shortName = canonicalName
499 .substring(canonicalName.lastIndexOf('.') + 1);
500 illegalShortClassNames.add(shortName);
501 }
502 }
503
504
505
506
507
508
509
510
511
512 private static String getImportedTypeCanonicalName(DetailAST importAst) {
513 final StringBuilder canonicalNameBuilder = new StringBuilder(256);
514 DetailAST toVisit = importAst;
515 while (toVisit != null) {
516 toVisit = getNextSubTreeNode(toVisit, importAst);
517 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
518 if (!canonicalNameBuilder.isEmpty()) {
519 canonicalNameBuilder.append('.');
520 }
521 canonicalNameBuilder.append(toVisit.getText());
522 }
523 }
524 return canonicalNameBuilder.toString();
525 }
526
527
528
529
530
531
532
533
534
535
536 private static DetailAST
537 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
538 DetailAST currentNode = currentNodeAst;
539 DetailAST toVisitAst = currentNode.getFirstChild();
540 while (toVisitAst == null) {
541 toVisitAst = currentNode.getNextSibling();
542 if (currentNode.getParent().equals(subTreeRootAst)) {
543 break;
544 }
545 currentNode = currentNode.getParent();
546 }
547 return toVisitAst;
548 }
549
550
551
552
553
554
555
556 private boolean isCheckedMethod(DetailAST ast) {
557 final String methodName =
558 ast.findFirstToken(TokenTypes.IDENT).getText();
559 return isVerifiable(ast) && !ignoredMethodNames.contains(methodName)
560 && !AnnotationUtil.hasOverrideAnnotation(ast);
561 }
562
563
564
565
566
567
568
569
570
571
572 public void setIllegalClassNames(String... classNames) {
573 illegalClassNames.clear();
574 Collections.addAll(illegalClassNames, classNames);
575 }
576
577
578
579
580
581
582
583
584
585 public void setIgnoredMethodNames(String... methodNames) {
586 ignoredMethodNames.clear();
587 Collections.addAll(ignoredMethodNames, methodNames);
588 }
589
590
591
592
593
594
595
596
597
598 public void setLegalAbstractClassNames(String... classNames) {
599 Collections.addAll(legalAbstractClassNames, classNames);
600 }
601
602
603
604
605
606
607
608
609
610 public void setMemberModifiers(String modifiers) {
611 memberModifiers = TokenUtil.asBitSet(modifiers.split(","));
612 }
613
614 }