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