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.javadoc;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Set;
29
30 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
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.LocalizedMessage;
35 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36
37
38
39
40
41
42
43
44
45
46 @Deprecated
47 @FileStatefulCheck
48 public abstract class AbstractTypeAwareCheck extends AbstractCheck {
49
50
51 private final Deque<Map<String, AbstractClassInfo>> typeParams = new ArrayDeque<>();
52
53
54 private final Set<String> imports = new HashSet<>();
55
56
57 private FullIdent packageFullIdent;
58
59
60 private String currentClassName;
61
62
63 private ClassResolver classResolver;
64
65
66
67
68
69
70
71
72
73
74
75
76
77 private boolean logLoadErrors = true;
78
79
80
81
82
83 private boolean suppressLoadErrors;
84
85
86
87
88
89
90 protected abstract void processAST(DetailAST ast);
91
92
93
94
95
96
97 protected abstract void logLoadError(Token ident);
98
99
100
101
102
103
104
105 public final void setLogLoadErrors(boolean logLoadErrors) {
106 this.logLoadErrors = logLoadErrors;
107 }
108
109
110
111
112
113
114 public final void setSuppressLoadErrors(boolean suppressLoadErrors) {
115 this.suppressLoadErrors = suppressLoadErrors;
116 }
117
118 @Override
119 public final int[] getRequiredTokens() {
120 return new int[] {
121 TokenTypes.PACKAGE_DEF,
122 TokenTypes.IMPORT,
123 TokenTypes.CLASS_DEF,
124 TokenTypes.INTERFACE_DEF,
125 TokenTypes.ENUM_DEF,
126 };
127 }
128
129 @Override
130 public void beginTree(DetailAST rootAST) {
131 packageFullIdent = FullIdent.createFullIdent(null);
132 imports.clear();
133
134 imports.add("java.lang.*");
135 classResolver = null;
136 currentClassName = "";
137 typeParams.clear();
138 }
139
140 @Override
141 public final void visitToken(DetailAST ast) {
142 if (ast.getType() == TokenTypes.PACKAGE_DEF) {
143 processPackage(ast);
144 }
145 else if (ast.getType() == TokenTypes.IMPORT) {
146 processImport(ast);
147 }
148 else if (ast.getType() == TokenTypes.CLASS_DEF
149 || ast.getType() == TokenTypes.INTERFACE_DEF
150 || ast.getType() == TokenTypes.ENUM_DEF) {
151 processClass(ast);
152 }
153 else {
154 if (ast.getType() == TokenTypes.METHOD_DEF) {
155 processTypeParams(ast);
156 }
157 processAST(ast);
158 }
159 }
160
161 @Override
162 public final void leaveToken(DetailAST ast) {
163 if (ast.getType() == TokenTypes.CLASS_DEF
164 || ast.getType() == TokenTypes.INTERFACE_DEF
165 || ast.getType() == TokenTypes.ENUM_DEF) {
166
167 int dotIdx = currentClassName.lastIndexOf('$');
168 if (dotIdx == -1) {
169
170 dotIdx = currentClassName.lastIndexOf('.');
171 }
172 if (dotIdx == -1) {
173
174 currentClassName = "";
175 }
176 else {
177 currentClassName = currentClassName.substring(0, dotIdx);
178 }
179 typeParams.pop();
180 }
181 else if (ast.getType() == TokenTypes.METHOD_DEF) {
182 typeParams.pop();
183 }
184 }
185
186
187
188
189
190
191
192
193
194 protected static boolean isUnchecked(Class<?> exception) {
195 return isSubclass(exception, RuntimeException.class)
196 || isSubclass(exception, Error.class);
197 }
198
199
200
201
202
203
204
205
206
207
208
209 protected static boolean isSubclass(Class<?> child, Class<?> parent) {
210 return parent != null && child != null
211 && parent.isAssignableFrom(child);
212 }
213
214
215
216
217
218 private ClassResolver getClassResolver() {
219 if (classResolver == null) {
220 classResolver =
221 new ClassResolver(getClassLoader(),
222 packageFullIdent.getText(),
223 imports);
224 }
225 return classResolver;
226 }
227
228
229
230
231
232
233
234
235
236
237 protected final Class<?> resolveClass(String resolvableClassName,
238 String className) {
239 Class<?> clazz;
240 try {
241 clazz = getClassResolver().resolve(resolvableClassName, className);
242 }
243 catch (final ClassNotFoundException ignored) {
244 clazz = null;
245 }
246 return clazz;
247 }
248
249
250
251
252
253
254
255
256
257 protected final Class<?> tryLoadClass(Token ident, String className) {
258 final Class<?> clazz = resolveClass(ident.getText(), className);
259 if (clazz == null) {
260 logLoadError(ident);
261 }
262 return clazz;
263 }
264
265
266
267
268
269
270
271
272 protected final void logLoadErrorImpl(int lineNo, int columnNo,
273 String msgKey, Object... values) {
274 if (!logLoadErrors) {
275 final LocalizedMessage msg = new LocalizedMessage(lineNo,
276 columnNo,
277 getMessageBundle(),
278 msgKey,
279 values,
280 getSeverityLevel(),
281 getId(),
282 getClass(),
283 null);
284 throw new IllegalStateException(msg.getMessage());
285 }
286
287 if (!suppressLoadErrors) {
288 log(lineNo, columnNo, msgKey, values);
289 }
290 }
291
292
293
294
295
296 private void processPackage(DetailAST ast) {
297 final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
298 packageFullIdent = FullIdent.createFullIdent(nameAST);
299 }
300
301
302
303
304
305 private void processImport(DetailAST ast) {
306 final FullIdent name = FullIdent.createFullIdentBelow(ast);
307 imports.add(name.getText());
308 }
309
310
311
312
313
314 private void processTypeParams(DetailAST ast) {
315 final DetailAST params =
316 ast.findFirstToken(TokenTypes.TYPE_PARAMETERS);
317
318 final Map<String, AbstractClassInfo> paramsMap = new HashMap<>();
319 typeParams.push(paramsMap);
320
321 if (params != null) {
322 for (DetailAST child = params.getFirstChild();
323 child != null;
324 child = child.getNextSibling()) {
325 if (child.getType() == TokenTypes.TYPE_PARAMETER) {
326 final DetailAST bounds =
327 child.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS);
328 if (bounds != null) {
329 final FullIdent name =
330 FullIdent.createFullIdentBelow(bounds);
331 final AbstractClassInfo classInfo =
332 createClassInfo(new Token(name), currentClassName);
333 final String alias =
334 child.findFirstToken(TokenTypes.IDENT).getText();
335 paramsMap.put(alias, classInfo);
336 }
337 }
338 }
339 }
340 }
341
342
343
344
345
346 private void processClass(DetailAST ast) {
347 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
348 String innerClass = ident.getText();
349
350 if (!currentClassName.isEmpty()) {
351 innerClass = "$" + innerClass;
352 }
353 currentClassName += innerClass;
354 processTypeParams(ast);
355 }
356
357
358
359
360
361 protected final String getCurrentClassName() {
362 return currentClassName;
363 }
364
365
366
367
368
369
370
371 protected final AbstractClassInfo createClassInfo(final Token name,
372 final String surroundingClass) {
373 final AbstractClassInfo result;
374 final AbstractClassInfo classInfo = findClassAlias(name.getText());
375 if (classInfo == null) {
376 result = new RegularClass(name, surroundingClass, this);
377 }
378 else {
379 result = new ClassAlias(name, classInfo);
380 }
381 return result;
382 }
383
384
385
386
387
388
389
390 protected final AbstractClassInfo findClassAlias(final String name) {
391 AbstractClassInfo classInfo = null;
392 final Iterator<Map<String, AbstractClassInfo>> iterator = typeParams.descendingIterator();
393 while (iterator.hasNext()) {
394 final Map<String, AbstractClassInfo> paramMap = iterator.next();
395 classInfo = paramMap.get(name);
396 if (classInfo != null) {
397 break;
398 }
399 }
400 return classInfo;
401 }
402
403
404
405
406
407 protected abstract static class AbstractClassInfo {
408
409
410 private final Token name;
411
412
413
414
415
416 protected AbstractClassInfo(final Token className) {
417 if (className == null) {
418 throw new IllegalArgumentException(
419 "ClassInfo's name should be non-null");
420 }
421 name = className;
422 }
423
424
425
426
427
428
429 public abstract Class<?> getClazz();
430
431
432
433
434
435 public final Token getName() {
436 return name;
437 }
438
439 }
440
441
442 private static final class RegularClass extends AbstractClassInfo {
443
444
445 private final String surroundingClass;
446
447 private final AbstractTypeAwareCheck check;
448
449 private boolean loadable = true;
450
451 private Class<?> classObj;
452
453
454
455
456
457
458
459 RegularClass(final Token name,
460 final String surroundingClass,
461 final AbstractTypeAwareCheck check) {
462 super(name);
463 this.surroundingClass = surroundingClass;
464 this.check = check;
465 }
466
467 @Override
468 public Class<?> getClazz() {
469 if (loadable && classObj == null) {
470 setClazz(check.tryLoadClass(getName(), surroundingClass));
471 }
472 return classObj;
473 }
474
475
476
477
478
479 private void setClazz(Class<?> clazz) {
480 classObj = clazz;
481 loadable = clazz != null;
482 }
483
484 @Override
485 public String toString() {
486 return "RegularClass[name=" + getName()
487 + ", in class='" + surroundingClass + '\''
488 + ", check=" + check.hashCode()
489 + ", loadable=" + loadable
490 + ", class=" + classObj
491 + ']';
492 }
493
494 }
495
496
497 private static class ClassAlias extends AbstractClassInfo {
498
499
500 private final AbstractClassInfo classInfo;
501
502
503
504
505
506
507 ClassAlias(final Token name, AbstractClassInfo classInfo) {
508 super(name);
509 this.classInfo = classInfo;
510 }
511
512 @Override
513 public final Class<?> getClazz() {
514 return classInfo.getClazz();
515 }
516
517 @Override
518 public String toString() {
519 return "ClassAlias[alias " + getName() + " for " + classInfo.getName() + "]";
520 }
521
522 }
523
524
525
526
527
528 protected static class Token {
529
530
531 private final int columnNo;
532
533 private final int lineNo;
534
535 private final String text;
536
537
538
539
540
541
542
543 public Token(String text, int lineNo, int columnNo) {
544 this.text = text;
545 this.lineNo = lineNo;
546 this.columnNo = columnNo;
547 }
548
549
550
551
552
553 public Token(FullIdent fullIdent) {
554 text = fullIdent.getText();
555 lineNo = fullIdent.getLineNo();
556 columnNo = fullIdent.getColumnNo();
557 }
558
559
560
561
562
563 public int getLineNo() {
564 return lineNo;
565 }
566
567
568
569
570
571 public int getColumnNo() {
572 return columnNo;
573 }
574
575
576
577
578
579 public String getText() {
580 return text;
581 }
582
583 @Override
584 public String toString() {
585 return "Token[" + text + "(" + lineNo
586 + "x" + columnNo + ")]";
587 }
588
589 }
590
591 }