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