View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.imports;
5   
6   import java.util.HashSet;
7   import java.util.Set;
8   import java.util.regex.Matcher;
9   import java.util.regex.Pattern;
10  
11  import net.sourceforge.pmd.lang.ast.Node;
12  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
13  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
14  import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
15  import net.sourceforge.pmd.lang.java.ast.ASTName;
16  import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration;
17  import net.sourceforge.pmd.lang.java.ast.Comment;
18  import net.sourceforge.pmd.lang.java.ast.DummyJavaNode;
19  import net.sourceforge.pmd.lang.java.ast.FormalComment;
20  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
21  import net.sourceforge.pmd.lang.rule.ImportWrapper;
22  
23  public class UnusedImportsRule extends AbstractJavaRule {
24  
25      protected Set<ImportWrapper> imports = new HashSet<>();
26  
27      @Override
28      public Object visit(ASTCompilationUnit node, Object data) {
29          imports.clear();
30          super.visit(node, data);
31          visitComments(node);
32  
33          /* special handling for Bug 2606609 : False "UnusedImports" positive in package-info.java
34           * package annotations are processed before the import clauses so they need to be examined
35           * again later on.
36           */
37          if (node.jjtGetNumChildren()>0 && node.jjtGetChild(0) instanceof ASTPackageDeclaration) {
38              visit((ASTPackageDeclaration)node.jjtGetChild(0), data);
39          }
40          for (ImportWrapper wrapper : imports) {
41              addViolation(data, wrapper.getNode(), wrapper.getFullName());
42          }
43          return data;
44      }
45  
46      /*
47       * Patterns to match the following constructs:
48       *
49       * @see  package.class#member(param, param)  label
50       * {@linkplain  package.class#member(param, param)  label}
51       * {@link  package.class#member(param, param)  label}
52       * {@value  package.class#field}
53       * @throws package.class label
54       */
55      private static final Pattern SEE_PATTERN = Pattern.compile(
56              "@see\\s+(\\p{Alpha}\\p{Alnum}*)(?:#\\p{Alnum}*\\(([\\w\\s,]*)\\))?");
57  
58      private static final Pattern LINK_PATTERNS = Pattern.compile(
59              "\\{@link(?:plain)?\\s+(\\p{Alpha}\\p{Alnum}*)(?:#\\p{Alnum}*\\(([.\\w\\s,]*)\\))?[\\s\\}]");
60  
61      private static final Pattern VALUE_PATTERN = Pattern.compile(
62              "\\{@value\\s+(\\p{Alpha}\\p{Alnum}*)[\\s#\\}]");
63  
64      private static final Pattern THROWS_PATTERN = Pattern.compile(
65              "@throws\\s+(\\p{Alpha}\\p{Alnum}*)");
66  
67      private static final Pattern[] PATTERNS = { SEE_PATTERN, LINK_PATTERNS, VALUE_PATTERN, THROWS_PATTERN };
68  
69      private void visitComments(ASTCompilationUnit node) {
70          if (imports.isEmpty()) {
71              return;
72          }
73          for (Comment comment: node.getComments()) {
74              if (!(comment instanceof FormalComment)) {
75                  continue;
76              }
77              for (Pattern p: PATTERNS) {
78                  Matcher m = p.matcher(comment.getImage());
79                  while (m.find()) {
80                      String s = m.group(1);
81                      imports.remove(new ImportWrapper(s, s, new DummyJavaNode(-1)));
82  
83                      if (m.groupCount() > 1) {
84                          s = m.group(2);
85                          if (s != null) {
86                              String[] params = s.split("\\s*,\\s*");
87                              for (String param : params) {
88                                  imports.remove(new ImportWrapper(param, param, new DummyJavaNode(-1)));
89                              }
90                          }
91                      }
92  
93                      if (imports.isEmpty()) {
94                          return;
95                      }
96                  }
97              }
98          }
99      }
100 
101     @Override
102     public Object visit(ASTImportDeclaration node, Object data) {
103         if (!node.isImportOnDemand()) {
104             ASTName importedType = (ASTName) node.jjtGetChild(0);
105             String className;
106             if (isQualifiedName(importedType)) {
107                 int lastDot = importedType.getImage().lastIndexOf('.') + 1;
108                 className = importedType.getImage().substring(lastDot);
109             } else {
110                 className = importedType.getImage();
111             }
112             imports.add(new ImportWrapper(importedType.getImage(), className, node));
113         }
114 
115         return data;
116     }
117 
118     @Override
119     public Object visit(ASTClassOrInterfaceType node, Object data) {
120         check(node);
121         return super.visit(node, data);
122     }
123 
124     @Override
125     public Object visit(ASTName node, Object data) {
126         check(node);
127         return data;
128     }
129 
130     protected void check(Node node) {
131         if (imports.isEmpty()) {
132             return;
133         }
134         ImportWrapper candidate = getImportWrapper(node);
135         if (imports.contains(candidate)) {
136             imports.remove(candidate);
137         }
138     }
139 
140     protected ImportWrapper getImportWrapper(Node node) {
141         String name;
142         if (!isQualifiedName(node)) {
143             name = node.getImage();
144         } else {
145             name = node.getImage().substring(0, node.getImage().indexOf('.'));
146         }
147         ImportWrapper candidate = new ImportWrapper(node.getImage(), name);
148         return candidate;
149     }
150 }