View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.imports;
21  
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FullIdent;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  
31  /**
32   * <p>
33   * Checks for imports that are redundant. An import statement is
34   * considered redundant if:
35   * </p>
36   *<ul>
37   *  <li>It is a duplicate of another import. This is, when a class is imported
38   *  more than once.</li>
39   *  <li>The class non-statically imported is from the {@code java.lang}
40   *  package. For example importing {@code java.lang.String}.</li>
41   *  <li>The class non-statically imported is from the same package as the
42   *  current package.</li>
43   *</ul>
44   * <p>
45   * An example of how to configure the check is:
46   * </p>
47   * <pre>
48   * &lt;module name="RedundantImport"/&gt;
49   * </pre>
50   * Compatible with Java 1.5 source.
51   *
52   * @author Oliver Burn
53   */
54  @FileStatefulCheck
55  public class RedundantImportCheck
56      extends AbstractCheck {
57  
58      /**
59       * A key is pointing to the warning message text in "messages.properties"
60       * file.
61       */
62      public static final String MSG_LANG = "import.lang";
63  
64      /**
65       * A key is pointing to the warning message text in "messages.properties"
66       * file.
67       */
68      public static final String MSG_SAME = "import.same";
69  
70      /**
71       * A key is pointing to the warning message text in "messages.properties"
72       * file.
73       */
74      public static final String MSG_DUPLICATE = "import.duplicate";
75  
76      /** Set of the imports. */
77      private final Set<FullIdent> imports = new HashSet<>();
78      /** Set of static imports. */
79      private final Set<FullIdent> staticImports = new HashSet<>();
80  
81      /** Name of package in file. */
82      private String pkgName;
83  
84      @Override
85      public void beginTree(DetailAST aRootAST) {
86          pkgName = null;
87          imports.clear();
88          staticImports.clear();
89      }
90  
91      @Override
92      public int[] getDefaultTokens() {
93          return getRequiredTokens();
94      }
95  
96      @Override
97      public int[] getAcceptableTokens() {
98          return getRequiredTokens();
99      }
100 
101     @Override
102     public int[] getRequiredTokens() {
103         return new int[] {
104             TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT, TokenTypes.PACKAGE_DEF,
105         };
106     }
107 
108     @Override
109     public void visitToken(DetailAST ast) {
110         if (ast.getType() == TokenTypes.PACKAGE_DEF) {
111             pkgName = FullIdent.createFullIdent(
112                     ast.getLastChild().getPreviousSibling()).getText();
113         }
114         else if (ast.getType() == TokenTypes.IMPORT) {
115             final FullIdent imp = FullIdent.createFullIdentBelow(ast);
116             if (isFromPackage(imp.getText(), "java.lang")) {
117                 log(ast.getLineNo(), ast.getColumnNo(), MSG_LANG,
118                     imp.getText());
119             }
120             // imports from unnamed package are not allowed,
121             // so we are checking SAME rule only for named packages
122             else if (pkgName != null && isFromPackage(imp.getText(), pkgName)) {
123                 log(ast.getLineNo(), ast.getColumnNo(), MSG_SAME,
124                     imp.getText());
125             }
126             // Check for a duplicate import
127             imports.stream().filter(full -> imp.getText().equals(full.getText()))
128                 .forEach(full -> log(ast.getLineNo(), ast.getColumnNo(),
129                     MSG_DUPLICATE, full.getLineNo(),
130                     imp.getText()));
131 
132             imports.add(imp);
133         }
134         else {
135             // Check for a duplicate static import
136             final FullIdent imp =
137                 FullIdent.createFullIdent(
138                     ast.getLastChild().getPreviousSibling());
139             staticImports.stream().filter(full -> imp.getText().equals(full.getText()))
140                 .forEach(full -> log(ast.getLineNo(), ast.getColumnNo(),
141                     MSG_DUPLICATE, full.getLineNo(), imp.getText()));
142 
143             staticImports.add(imp);
144         }
145     }
146 
147     /**
148      * Determines if an import statement is for types from a specified package.
149      * @param importName the import name
150      * @param pkg the package name
151      * @return whether from the package
152      */
153     private static boolean isFromPackage(String importName, String pkg) {
154         // imports from unnamed package are not allowed:
155         // https://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html#jls-7.5
156         // So '.' must be present in member name and we are not checking for it
157         final int index = importName.lastIndexOf('.');
158         final String front = importName.substring(0, index);
159         return front.equals(pkg);
160     }
161 }