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