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()>0 && node.jjtGetChild(0) instanceof 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 = (ASTName) node.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 }
|