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<ImportWrapper>();
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       * {@link  package.class#field}
53       * {@value  package.class#field}
54       * @throws package.class label
55       */
56      private static final Pattern SEE_PATTERN = Pattern.compile(
57              "@see\\s+(\\p{Alpha}\\p{Alnum}*)(?:#\\p{Alnum}*(?:\\(([\\w\\s,]*)\\))?)?");
58  
59      private static final Pattern LINK_PATTERNS = Pattern.compile(
60              "\\{@link(?:plain)?\\s+(\\p{Alpha}\\p{Alnum}*)(?:#\\p{Alnum}*(?:\\(([.\\w\\s,]*)\\))?)?[\\s\\}]");
61  
62      private static final Pattern VALUE_PATTERN = Pattern.compile(
63              "\\{@value\\s+(\\p{Alpha}\\p{Alnum}*)[\\s#\\}]");
64  
65      private static final Pattern THROWS_PATTERN = Pattern.compile(
66              "@throws\\s+(\\p{Alpha}\\p{Alnum}*)");
67  
68      private static final Pattern[] PATTERNS = { SEE_PATTERN, LINK_PATTERNS, VALUE_PATTERN, THROWS_PATTERN };
69  
70      private void visitComments(ASTCompilationUnit node) {
71          if (imports.isEmpty()) {
72              return;
73          }
74          for (Comment comment: node.getComments()) {
75              if (!(comment instanceof FormalComment)) {
76                  continue;
77              }
78              for (Pattern p: PATTERNS) {
79                  Matcher m = p.matcher(comment.getImage());
80                  while (m.find()) {
81                      String s = m.group(1);
82                      imports.remove(new ImportWrapper(s, s, new DummyJavaNode(-1)));
83  
84                      if (m.groupCount() > 1) {
85                          s = m.group(2);
86                          if (s != null) {
87                              String[] params = s.split("\\s*,\\s*");
88                              for (String param : params) {
89                                  imports.remove(new ImportWrapper(param, param, new DummyJavaNode(-1)));
90                              }
91                          }
92                      }
93  
94                      if (imports.isEmpty()) {
95                          return;
96                      }
97                  }
98              }
99          }
100     }
101 
102     @Override
103     public Object visit(ASTImportDeclaration node, Object data) {
104         if (!node.isImportOnDemand()) {
105             ASTName importedType = (ASTName) node.jjtGetChild(0);
106             String className;
107             if (isQualifiedName(importedType)) {
108                 int lastDot = importedType.getImage().lastIndexOf('.') + 1;
109                 className = importedType.getImage().substring(lastDot);
110             } else {
111                 className = importedType.getImage();
112             }
113             imports.add(new ImportWrapper(importedType.getImage(), className, node));
114         }
115 
116         return data;
117     }
118 
119     @Override
120     public Object visit(ASTClassOrInterfaceType node, Object data) {
121         check(node);
122         return super.visit(node, data);
123     }
124 
125     @Override
126     public Object visit(ASTName node, Object data) {
127         check(node);
128         return data;
129     }
130 
131     protected void check(Node node) {
132         if (imports.isEmpty()) {
133             return;
134         }
135         ImportWrapper candidate = getImportWrapper(node);
136         if (imports.contains(candidate)) {
137             imports.remove(candidate);
138         }
139     }
140 
141     protected ImportWrapper getImportWrapper(Node node) {
142         String name;
143         if (!isQualifiedName(node)) {
144             name = node.getImage();
145         } else {
146             name = node.getImage().substring(0, node.getImage().indexOf('.'));
147         }
148         ImportWrapper candidate = new ImportWrapper(node.getImage(), name);
149         return candidate;
150     }
151 }