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