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.imports;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
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.FileContents;
34 import com.puppycrawl.tools.checkstyle.api.FullIdent;
35 import com.puppycrawl.tools.checkstyle.api.TextBlock;
36 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
37 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
38 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
39 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
40 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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 @FileStatefulCheck
108 public class UnusedImportsCheck extends AbstractCheck {
109
110
111
112
113
114 public static final String MSG_KEY = "import.unused";
115
116
117 private static final Pattern CLASS_NAME = CommonUtil.createPattern(
118 "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)");
119
120 private static final Pattern FIRST_CLASS_NAME = CommonUtil.createPattern(
121 "^" + CLASS_NAME);
122
123 private static final Pattern ARGUMENT_NAME = CommonUtil.createPattern(
124 "[(,]\\s*" + CLASS_NAME.pattern());
125
126
127 private static final Pattern JAVA_LANG_PACKAGE_PATTERN =
128 CommonUtil.createPattern("^java\\.lang\\.[a-zA-Z]+$");
129
130
131 private static final String STAR_IMPORT_SUFFIX = ".*";
132
133
134 private final Set<FullIdent> imports = new HashSet<>();
135
136
137 private boolean collect;
138
139 private boolean processJavadoc = true;
140
141
142
143
144
145 private Frame currentFrame;
146
147
148
149
150
151
152
153 public void setProcessJavadoc(boolean value) {
154 processJavadoc = value;
155 }
156
157 @Override
158 public void beginTree(DetailAST rootAST) {
159 collect = false;
160 currentFrame = Frame.compilationUnit();
161 imports.clear();
162 }
163
164 @Override
165 public void finishTree(DetailAST rootAST) {
166 currentFrame.finish();
167
168 imports.stream()
169 .filter(imprt -> isUnusedImport(imprt.getText()))
170 .forEach(imprt -> log(imprt.getDetailAst(), MSG_KEY, imprt.getText()));
171 }
172
173 @Override
174 public int[] getDefaultTokens() {
175 return getRequiredTokens();
176 }
177
178 @Override
179 public int[] getRequiredTokens() {
180 return new int[] {
181 TokenTypes.IDENT,
182 TokenTypes.IMPORT,
183 TokenTypes.STATIC_IMPORT,
184
185 TokenTypes.PACKAGE_DEF,
186 TokenTypes.ANNOTATION_DEF,
187 TokenTypes.ANNOTATION_FIELD_DEF,
188 TokenTypes.ENUM_DEF,
189 TokenTypes.ENUM_CONSTANT_DEF,
190 TokenTypes.CLASS_DEF,
191 TokenTypes.INTERFACE_DEF,
192 TokenTypes.METHOD_DEF,
193 TokenTypes.CTOR_DEF,
194 TokenTypes.VARIABLE_DEF,
195 TokenTypes.RECORD_DEF,
196 TokenTypes.COMPACT_CTOR_DEF,
197
198 TokenTypes.OBJBLOCK,
199 TokenTypes.SLIST,
200 };
201 }
202
203 @Override
204 public int[] getAcceptableTokens() {
205 return getRequiredTokens();
206 }
207
208 @Override
209 public void visitToken(DetailAST ast) {
210 switch (ast.getType()) {
211 case TokenTypes.IDENT:
212 if (collect) {
213 processIdent(ast);
214 }
215 break;
216 case TokenTypes.IMPORT:
217 processImport(ast);
218 break;
219 case TokenTypes.STATIC_IMPORT:
220 processStaticImport(ast);
221 break;
222 case TokenTypes.OBJBLOCK:
223 case TokenTypes.SLIST:
224 currentFrame = currentFrame.push();
225 break;
226 default:
227 collect = true;
228 if (processJavadoc) {
229 collectReferencesFromJavadoc(ast);
230 }
231 break;
232 }
233 }
234
235 @Override
236 public void leaveToken(DetailAST ast) {
237 if (TokenUtil.isOfType(ast, TokenTypes.OBJBLOCK, TokenTypes.SLIST)) {
238 currentFrame = currentFrame.pop();
239 }
240 }
241
242
243
244
245
246
247
248 private boolean isUnusedImport(String imprt) {
249 final Matcher javaLangPackageMatcher = JAVA_LANG_PACKAGE_PATTERN.matcher(imprt);
250 return !currentFrame.isReferencedType(CommonUtil.baseClassName(imprt))
251 || javaLangPackageMatcher.matches();
252 }
253
254
255
256
257
258
259 private void processIdent(DetailAST ast) {
260 final DetailAST parent = ast.getParent();
261 final int parentType = parent.getType();
262
263 final boolean isClassOrMethod = parentType == TokenTypes.DOT
264 || parentType == TokenTypes.METHOD_DEF || parentType == TokenTypes.METHOD_REF;
265
266 if (TokenUtil.isTypeDeclaration(parentType)) {
267 currentFrame.addDeclaredType(ast.getText());
268 }
269 else if (!isClassOrMethod || isQualifiedIdentifier(ast)) {
270 currentFrame.addReferencedType(ast.getText());
271 }
272 }
273
274
275
276
277
278
279
280 private static boolean isQualifiedIdentifier(DetailAST ast) {
281 final DetailAST parent = ast.getParent();
282 final int parentType = parent.getType();
283
284 final boolean isQualifiedIdent = parentType == TokenTypes.DOT
285 && !TokenUtil.isOfType(ast.getPreviousSibling(), TokenTypes.DOT)
286 && ast.getNextSibling() != null;
287 final boolean isQualifiedIdentFromMethodRef = parentType == TokenTypes.METHOD_REF
288 && ast.getNextSibling() != null;
289 return isQualifiedIdent || isQualifiedIdentFromMethodRef;
290 }
291
292
293
294
295
296
297 private void processImport(DetailAST ast) {
298 final FullIdent name = FullIdent.createFullIdentBelow(ast);
299 if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
300 imports.add(name);
301 }
302 }
303
304
305
306
307
308
309 private void processStaticImport(DetailAST ast) {
310 final FullIdent name =
311 FullIdent.createFullIdent(
312 ast.getFirstChild().getNextSibling());
313 if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
314 imports.add(name);
315 }
316 }
317
318
319
320
321
322
323
324 @SuppressWarnings("deprecation")
325 private void collectReferencesFromJavadoc(DetailAST ast) {
326 final FileContents contents = getFileContents();
327 final int lineNo = ast.getLineNo();
328 final TextBlock textBlock = contents.getJavadocBefore(lineNo);
329 if (textBlock != null) {
330 currentFrame.addReferencedTypes(collectReferencesFromJavadoc(textBlock));
331 }
332 }
333
334
335
336
337
338
339
340
341 private static Set<String> collectReferencesFromJavadoc(TextBlock textBlock) {
342 final List<JavadocTag> tags = new ArrayList<>();
343
344
345 tags.addAll(getValidTags(textBlock, JavadocUtil.JavadocTagType.INLINE));
346
347 tags.addAll(getValidTags(textBlock, JavadocUtil.JavadocTagType.BLOCK));
348
349 final Set<String> references = new HashSet<>();
350
351 tags.stream()
352 .filter(JavadocTag::canReferenceImports)
353 .forEach(tag -> references.addAll(processJavadocTag(tag)));
354 return references;
355 }
356
357
358
359
360
361
362
363
364 private static List<JavadocTag> getValidTags(TextBlock cmt,
365 JavadocUtil.JavadocTagType tagType) {
366 return JavadocUtil.getJavadocTags(cmt, tagType).getValidTags();
367 }
368
369
370
371
372
373
374
375 private static Set<String> processJavadocTag(JavadocTag tag) {
376 final Set<String> references = new HashSet<>();
377 final String identifier = tag.getFirstArg();
378 for (Pattern pattern : new Pattern[]
379 {FIRST_CLASS_NAME, ARGUMENT_NAME}) {
380 references.addAll(matchPattern(identifier, pattern));
381 }
382 return references;
383 }
384
385
386
387
388
389
390
391
392
393 private static Set<String> matchPattern(String identifier, Pattern pattern) {
394 final Set<String> references = new HashSet<>();
395 final Matcher matcher = pattern.matcher(identifier);
396 while (matcher.find()) {
397 references.add(topLevelType(matcher.group(1)));
398 }
399 return references;
400 }
401
402
403
404
405
406
407
408
409
410 private static String topLevelType(String type) {
411 final String topLevelType;
412 final int dotIndex = type.indexOf('.');
413 if (dotIndex == -1) {
414 topLevelType = type;
415 }
416 else {
417 topLevelType = type.substring(0, dotIndex);
418 }
419 return topLevelType;
420 }
421
422
423
424
425 private static final class Frame {
426
427
428 private final Frame parent;
429
430
431 private final Set<String> declaredTypes;
432
433
434 private final Set<String> referencedTypes;
435
436
437
438
439
440
441 private Frame(Frame parent) {
442 this.parent = parent;
443 declaredTypes = new HashSet<>();
444 referencedTypes = new HashSet<>();
445 }
446
447
448
449
450
451
452 public void addDeclaredType(String type) {
453 declaredTypes.add(type);
454 }
455
456
457
458
459
460
461 public void addReferencedType(String type) {
462 referencedTypes.add(type);
463 }
464
465
466
467
468
469
470 public void addReferencedTypes(Collection<String> types) {
471 referencedTypes.addAll(types);
472 }
473
474
475
476
477
478 public void finish() {
479 referencedTypes.removeAll(declaredTypes);
480 }
481
482
483
484
485
486
487 public Frame push() {
488 return new Frame(this);
489 }
490
491
492
493
494
495
496 public Frame pop() {
497 finish();
498 parent.addReferencedTypes(referencedTypes);
499 return parent;
500 }
501
502
503
504
505
506
507
508 public boolean isReferencedType(String type) {
509 return referencedTypes.contains(type);
510 }
511
512
513
514
515
516
517 public static Frame compilationUnit() {
518 return new Frame(null);
519 }
520
521 }
522
523 }