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 }