/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.imports; import java.util.HashSet; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; import net.sourceforge.pmd.lang.java.ast.Comment; import net.sourceforge.pmd.lang.java.ast.DummyJavaNode; import net.sourceforge.pmd.lang.java.ast.FormalComment; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.rule.ImportWrapper; public class UnusedImportsRule extends AbstractJavaRule { protected Set<ImportWrapper> imports = new HashSet<>(); /* * Patterns to match the following constructs: * * @see package.class#member(param, param) label {@linkplain * package.class#member(param, param) label} {@link * package.class#member(param, param) label} {@link package.class#field} * {@value package.class#field} * * @throws package.class label */ private static final Pattern SEE_PATTERN = Pattern .compile("@see\\s+(\\p{Alpha}\\w*)(?:#\\w*(?:\\(([\\w\\s,]*)\\))?)?"); private static final Pattern LINK_PATTERNS = Pattern .compile("\\{@link(?:plain)?\\s+(\\p{Alpha}\\w*)(?:#\\w*(?:\\(([.\\w\\s,]*)\\))?)?[\\s\\}]"); private static final Pattern VALUE_PATTERN = Pattern.compile("\\{@value\\s+(\\p{Alpha}\\w*)[\\s#\\}]"); private static final Pattern THROWS_PATTERN = Pattern.compile("@throws\\s+(\\p{Alpha}\\w*)"); private static final Pattern[] PATTERNS = { SEE_PATTERN, LINK_PATTERNS, VALUE_PATTERN, THROWS_PATTERN }; @Override public Object visit(ASTCompilationUnit node, Object data) { imports.clear(); super.visit(node, data); visitComments(node); /* * special handling for Bug 2606609 : False "UnusedImports" positive in * package-info.java package annotations are processed before the import * clauses so they need to be examined again later on. */ if (node.jjtGetNumChildren() > 0 && node.jjtGetChild(0) instanceof ASTPackageDeclaration) { visit((ASTPackageDeclaration) node.jjtGetChild(0), data); } for (ImportWrapper wrapper : imports) { addViolation(data, wrapper.getNode(), wrapper.getFullName()); } return data; } private void visitComments(ASTCompilationUnit node) { if (imports.isEmpty()) { return; } for (Comment comment : node.getComments()) { if (!(comment instanceof FormalComment)) { continue; } for (Pattern p : PATTERNS) { Matcher m = p.matcher(comment.getImage()); while (m.find()) { String s = m.group(1); imports.remove(new ImportWrapper(s, s, new DummyJavaNode(-1))); if (m.groupCount() > 1) { s = m.group(2); if (s != null) { String[] params = s.split("\\s*,\\s*"); for (String param : params) { imports.remove(new ImportWrapper(param, param, new DummyJavaNode(-1))); } } } if (imports.isEmpty()) { return; } } } } } @Override public Object visit(ASTImportDeclaration node, Object data) { if (!node.isImportOnDemand()) { ASTName importedType = (ASTName) node.jjtGetChild(0); String className; if (isQualifiedName(importedType)) { int lastDot = importedType.getImage().lastIndexOf('.') + 1; className = importedType.getImage().substring(lastDot); } else { className = importedType.getImage(); } imports.add(new ImportWrapper(importedType.getImage(), className, node)); } return data; } @Override public Object visit(ASTClassOrInterfaceType node, Object data) { check(node); return super.visit(node, data); } @Override public Object visit(ASTName node, Object data) { check(node); return data; } protected void check(Node node) { if (imports.isEmpty()) { return; } ImportWrapper candidate = getImportWrapper(node); if (imports.contains(candidate)) { imports.remove(candidate); } } protected ImportWrapper getImportWrapper(Node node) { String name; if (!isQualifiedName(node)) { name = node.getImage(); } else { name = node.getImage().substring(0, node.getImage().indexOf('.')); } ImportWrapper candidate = new ImportWrapper(node.getImage(), name); return candidate; } }