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.ArrayList;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
30  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31  import com.puppycrawl.tools.checkstyle.api.DetailAST;
32  import com.puppycrawl.tools.checkstyle.api.FileContents;
33  import com.puppycrawl.tools.checkstyle.api.FullIdent;
34  import com.puppycrawl.tools.checkstyle.api.TextBlock;
35  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
37  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
38  import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
39  
40  /**
41   * <p>
42   * Checks for unused import statements.
43   * </p>
44   *  <p>
45   * An example of how to configure the check is:
46   * </p>
47   * <pre>
48   * &lt;module name="UnusedImports"/&gt;
49   * </pre>
50   * Compatible with Java 1.5 source.
51   *
52   * @author Oliver Burn
53   */
54  @FileStatefulCheck
55  public class UnusedImportsCheck extends AbstractCheck {
56  
57      /**
58       * A key is pointing to the warning message text in "messages.properties"
59       * file.
60       */
61      public static final String MSG_KEY = "import.unused";
62  
63      /** Regex to match class names. */
64      private static final Pattern CLASS_NAME = CommonUtils.createPattern(
65             "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)");
66      /** Regex to match the first class name. */
67      private static final Pattern FIRST_CLASS_NAME = CommonUtils.createPattern(
68             "^" + CLASS_NAME);
69      /** Regex to match argument names. */
70      private static final Pattern ARGUMENT_NAME = CommonUtils.createPattern(
71             "[(,]\\s*" + CLASS_NAME.pattern());
72  
73      /** Regexp pattern to match java.lang package. */
74      private static final Pattern JAVA_LANG_PACKAGE_PATTERN =
75          CommonUtils.createPattern("^java\\.lang\\.[a-zA-Z]+$");
76  
77      /** Suffix for the star import. */
78      private static final String STAR_IMPORT_SUFFIX = ".*";
79  
80      /** Set of the imports. */
81      private final Set<FullIdent> imports = new HashSet<>();
82  
83      /** Set of references - possibly to imports or other things. */
84      private final Set<String> referenced = new HashSet<>();
85  
86      /** Flag to indicate when time to start collecting references. */
87      private boolean collect;
88      /** Flag whether to process Javadoc comments. */
89      private boolean processJavadoc = true;
90  
91      /**
92       * Sets whether to process JavaDoc or not.
93       *
94       * @param value Flag for processing JavaDoc.
95       */
96      public void setProcessJavadoc(boolean value) {
97          processJavadoc = value;
98      }
99  
100     @Override
101     public void beginTree(DetailAST rootAST) {
102         collect = false;
103         imports.clear();
104         referenced.clear();
105     }
106 
107     @Override
108     public void finishTree(DetailAST rootAST) {
109         // loop over all the imports to see if referenced.
110         imports.stream()
111             .filter(imprt -> isUnusedImport(imprt.getText()))
112             .forEach(imprt -> log(imprt.getLineNo(),
113                 imprt.getColumnNo(),
114                 MSG_KEY, imprt.getText()));
115     }
116 
117     @Override
118     public int[] getDefaultTokens() {
119         return getRequiredTokens();
120     }
121 
122     @Override
123     public int[] getRequiredTokens() {
124         return new int[] {
125             TokenTypes.IDENT,
126             TokenTypes.IMPORT,
127             TokenTypes.STATIC_IMPORT,
128             // Definitions that may contain Javadoc...
129             TokenTypes.PACKAGE_DEF,
130             TokenTypes.ANNOTATION_DEF,
131             TokenTypes.ANNOTATION_FIELD_DEF,
132             TokenTypes.ENUM_DEF,
133             TokenTypes.ENUM_CONSTANT_DEF,
134             TokenTypes.CLASS_DEF,
135             TokenTypes.INTERFACE_DEF,
136             TokenTypes.METHOD_DEF,
137             TokenTypes.CTOR_DEF,
138             TokenTypes.VARIABLE_DEF,
139         };
140     }
141 
142     @Override
143     public int[] getAcceptableTokens() {
144         return getRequiredTokens();
145     }
146 
147     @Override
148     public void visitToken(DetailAST ast) {
149         if (ast.getType() == TokenTypes.IDENT) {
150             if (collect) {
151                 processIdent(ast);
152             }
153         }
154         else if (ast.getType() == TokenTypes.IMPORT) {
155             processImport(ast);
156         }
157         else if (ast.getType() == TokenTypes.STATIC_IMPORT) {
158             processStaticImport(ast);
159         }
160         else {
161             collect = true;
162             if (processJavadoc) {
163                 collectReferencesFromJavadoc(ast);
164             }
165         }
166     }
167 
168     /**
169      * Checks whether an import is unused.
170      * @param imprt an import.
171      * @return true if an import is unused.
172      */
173     private boolean isUnusedImport(String imprt) {
174         final Matcher javaLangPackageMatcher = JAVA_LANG_PACKAGE_PATTERN.matcher(imprt);
175         return !referenced.contains(CommonUtils.baseClassName(imprt))
176             || javaLangPackageMatcher.matches();
177     }
178 
179     /**
180      * Collects references made by IDENT.
181      * @param ast the IDENT node to process
182      */
183     private void processIdent(DetailAST ast) {
184         final DetailAST parent = ast.getParent();
185         final int parentType = parent.getType();
186         if (parentType != TokenTypes.DOT
187             && parentType != TokenTypes.METHOD_DEF
188             || parentType == TokenTypes.DOT
189                 && ast.getNextSibling() != null) {
190             referenced.add(ast.getText());
191         }
192     }
193 
194     /**
195      * Collects the details of imports.
196      * @param ast node containing the import details
197      */
198     private void processImport(DetailAST ast) {
199         final FullIdent name = FullIdent.createFullIdentBelow(ast);
200         if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
201             imports.add(name);
202         }
203     }
204 
205     /**
206      * Collects the details of static imports.
207      * @param ast node containing the static import details
208      */
209     private void processStaticImport(DetailAST ast) {
210         final FullIdent name =
211             FullIdent.createFullIdent(
212                 ast.getFirstChild().getNextSibling());
213         if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
214             imports.add(name);
215         }
216     }
217 
218     /**
219      * Collects references made in Javadoc comments.
220      * @param ast node to inspect for Javadoc
221      */
222     private void collectReferencesFromJavadoc(DetailAST ast) {
223         final FileContents contents = getFileContents();
224         final int lineNo = ast.getLineNo();
225         final TextBlock textBlock = contents.getJavadocBefore(lineNo);
226         if (textBlock != null) {
227             referenced.addAll(collectReferencesFromJavadoc(textBlock));
228         }
229     }
230 
231     /**
232      * Process a javadoc {@link TextBlock} and return the set of classes
233      * referenced within.
234      * @param textBlock The javadoc block to parse
235      * @return a set of classes referenced in the javadoc block
236      */
237     private static Set<String> collectReferencesFromJavadoc(TextBlock textBlock) {
238         final List<JavadocTag> tags = new ArrayList<>();
239         // gather all the inline tags, like @link
240         // INLINE tags inside BLOCKs get hidden when using ALL
241         tags.addAll(getValidTags(textBlock, JavadocUtils.JavadocTagType.INLINE));
242         // gather all the block-level tags, like @throws and @see
243         tags.addAll(getValidTags(textBlock, JavadocUtils.JavadocTagType.BLOCK));
244 
245         final Set<String> references = new HashSet<>();
246 
247         tags.stream()
248             .filter(JavadocTag::canReferenceImports)
249             .forEach(tag -> references.addAll(processJavadocTag(tag)));
250         return references;
251     }
252 
253     /**
254      * Returns the list of valid tags found in a javadoc {@link TextBlock}.
255      * @param cmt The javadoc block to parse
256      * @param tagType The type of tags we're interested in
257      * @return the list of tags
258      */
259     private static List<JavadocTag> getValidTags(TextBlock cmt,
260             JavadocUtils.JavadocTagType tagType) {
261         return JavadocUtils.getJavadocTags(cmt, tagType).getValidTags();
262     }
263 
264     /**
265      * Returns a list of references found in a javadoc {@link JavadocTag}.
266      * @param tag The javadoc tag to parse
267      * @return A list of references found in this tag
268      */
269     private static Set<String> processJavadocTag(JavadocTag tag) {
270         final Set<String> references = new HashSet<>();
271         final String identifier = tag.getFirstArg().trim();
272         for (Pattern pattern : new Pattern[]
273         {FIRST_CLASS_NAME, ARGUMENT_NAME}) {
274             references.addAll(matchPattern(identifier, pattern));
275         }
276         return references;
277     }
278 
279     /**
280      * Extracts a list of texts matching a {@link Pattern} from a
281      * {@link String}.
282      * @param identifier The String to match the pattern against
283      * @param pattern The Pattern used to extract the texts
284      * @return A list of texts which matched the pattern
285      */
286     private static Set<String> matchPattern(String identifier, Pattern pattern) {
287         final Set<String> references = new HashSet<>();
288         final Matcher matcher = pattern.matcher(identifier);
289         while (matcher.find()) {
290             references.add(topLevelType(matcher.group(1)));
291         }
292         return references;
293     }
294 
295     /**
296      * If the given type string contains "." (e.g. "Map.Entry"), returns the
297      * top level type (e.g. "Map"), as that is what must be imported for the
298      * type to resolve. Otherwise, returns the type as-is.
299      * @param type A possibly qualified type name
300      * @return The simple name of the top level type
301      */
302     private static String topLevelType(String type) {
303         final String topLevelType;
304         final int dotIndex = type.indexOf('.');
305         if (dotIndex == -1) {
306             topLevelType = type;
307         }
308         else {
309             topLevelType = type.substring(0, dotIndex);
310         }
311         return topLevelType;
312     }
313 }