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       * @throws package.class label
54       */
55      private static final Pattern SEE_PATTERN = Pattern.compile(
56              "@see\\s+(\\p{Alpha}\\p{Alnum}*)[\\s#]");
57  
58      private static final Pattern LINK_PATTERNS = Pattern.compile(
59              "\\{@link(?:plain)?\\s+(\\p{Alpha}\\p{Alnum}*)[\\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                      ImportWrapper candidate = new ImportWrapper(s, s, new DummyJavaNode(-1));
82  
83                      if (imports.contains(candidate)) {
84                          imports.remove(candidate);
85                          if (imports.isEmpty()) {
86                              return;
87                          }
88                      }
89                  }
90              }
91          }
92      }
93  
94      @Override
95      public Object visit(ASTImportDeclaration node, Object data) {
96          if (!node.isImportOnDemand()) {
97              ASTName importedType = (ASTName) node.jjtGetChild(0);
98              String className;
99              if (isQualifiedName(importedType)) {
100                 int lastDot = importedType.getImage().lastIndexOf('.') + 1;
101                 className = importedType.getImage().substring(lastDot);
102             } else {
103                 className = importedType.getImage();
104             }
105             imports.add(new ImportWrapper(importedType.getImage(), className, node));
106         }
107 
108         return data;
109     }
110 
111     @Override
112     public Object visit(ASTClassOrInterfaceType node, Object data) {
113         check(node);
114         return super.visit(node, data);
115     }
116 
117     @Override
118     public Object visit(ASTName node, Object data) {
119         check(node);
120         return data;
121     }
122 
123     protected void check(Node node) {
124         if (imports.isEmpty()) {
125             return;
126         }
127         ImportWrapper candidate = getImportWrapper(node);
128         if (imports.contains(candidate)) {
129             imports.remove(candidate);
130         }
131     }
132 
133     protected ImportWrapper getImportWrapper(Node node) {
134         String name;
135         if (!isQualifiedName(node)) {
136             name = node.getImage();
137         } else {
138             name = node.getImage().substring(0, node.getImage().indexOf('.'));
139         }
140         ImportWrapper candidate = new ImportWrapper(node.getImage(), name, new DummyJavaNode(-1));
141         return candidate;
142     }
143 }