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