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