UnusedImportsRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.rule.imports;
005 
006 import java.util.HashSet;
007 import java.util.Set;
008 import java.util.regex.Matcher;
009 import java.util.regex.Pattern;
010 
011 import net.sourceforge.pmd.lang.ast.Node;
012 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
013 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
014 import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTName;
016 import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration;
017 import net.sourceforge.pmd.lang.java.ast.Comment;
018 import net.sourceforge.pmd.lang.java.ast.DummyJavaNode;
019 import net.sourceforge.pmd.lang.java.ast.FormalComment;
020 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
021 import net.sourceforge.pmd.lang.java.rule.ImportWrapper;
022 
023 public class UnusedImportsRule extends AbstractJavaRule {
024 
025     protected Set<ImportWrapper> imports = new HashSet<ImportWrapper>();
026 
027     @Override
028     public Object visit(ASTCompilationUnit node, Object data) {
029         imports.clear();
030         super.visit(node, data);
031         visitComments(node);
032 
033         /* special handling for Bug 2606609 : False "UnusedImports" positive in package-info.java
034          * package annotations are processed before the import clauses so they need to be examined
035          * again later on.
036          */
037         if (node.jjtGetNumChildren()>&& node.jjtGetChild(0instanceof ASTPackageDeclaration) {
038             visit((ASTPackageDeclaration)node.jjtGetChild(0), data);
039         }
040         for (ImportWrapper wrapper : imports) {
041             addViolation(data, wrapper.getNode(), wrapper.getFullName());
042         }
043         return data;
044     }
045 
046     /*
047      * Patterns to match the following constructs:
048      *
049      * @see  package.class#member  label
050      * {@linkplain  package.class#member  label}
051      * {@link  package.class#member  label}
052      * {@value  package.class#field}
053      */
054     private static final Pattern SEE_PATTERN = Pattern.compile(
055             "@see\\s+(\\p{Alpha}\\p{Alnum}*)[\\s#]");
056 
057     private static final Pattern LINK_PATTERNS = Pattern.compile(
058             "\\{@link(?:plain)?\\s+(\\p{Alpha}\\p{Alnum}*)[\\s#\\}]");
059 
060     private static final Pattern VALUE_PATTERN = Pattern.compile(
061             "\\{@value\\s+(\\p{Alpha}\\p{Alnum}*)[\\s#\\}]");
062 
063     private static final Pattern[] PATTERNS = SEE_PATTERN, LINK_PATTERNS, VALUE_PATTERN };
064 
065     private void visitComments(ASTCompilationUnit node) {
066         if (imports.isEmpty()) {
067             return;
068         }
069         for (Comment comment: node.getComments()) {
070             if (!(comment instanceof FormalComment)) {
071                 continue;
072             }
073             for (Pattern p: PATTERNS) {
074                 Matcher m = p.matcher(comment.getImage());
075                 while (m.find()) {
076                     String s = m.group(1);
077                     ImportWrapper candidate = new ImportWrapper(s, s, new DummyJavaNode(-1));
078 
079                     if (imports.contains(candidate)) {
080                         imports.remove(candidate);
081                         if (imports.isEmpty()) {
082                             return;
083                         }
084                     }
085                 }
086             }
087         }
088     }
089 
090     @Override
091     public Object visit(ASTImportDeclaration node, Object data) {
092         if (!node.isImportOnDemand()) {
093             ASTName importedType = (ASTNamenode.jjtGetChild(0);
094             String className;
095             if (isQualifiedName(importedType)) {
096                 int lastDot = importedType.getImage().lastIndexOf('.'1;
097                 className = importedType.getImage().substring(lastDot);
098             else {
099                 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 }