001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.imports;
021
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.FileContents;
031import com.puppycrawl.tools.checkstyle.api.FullIdent;
032import com.puppycrawl.tools.checkstyle.api.TextBlock;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
035import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
036import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
037
038/**
039 * <p>
040 * Checks for unused import statements.
041 * </p>
042 *  <p>
043 * An example of how to configure the check is:
044 * </p>
045 * <pre>
046 * &lt;module name="UnusedImports"/&gt;
047 * </pre>
048 * Compatible with Java 1.5 source.
049 *
050 * @author Oliver Burn
051 */
052public class UnusedImportsCheck extends AbstractCheck {
053
054    /**
055     * A key is pointing to the warning message text in "messages.properties"
056     * file.
057     */
058    public static final String MSG_KEY = "import.unused";
059
060    /** Regex to match class names. */
061    private static final Pattern CLASS_NAME = CommonUtils.createPattern(
062           "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)");
063    /** Regex to match the first class name. */
064    private static final Pattern FIRST_CLASS_NAME = CommonUtils.createPattern(
065           "^" + CLASS_NAME);
066    /** Regex to match argument names. */
067    private static final Pattern ARGUMENT_NAME = CommonUtils.createPattern(
068           "[(,]\\s*" + CLASS_NAME.pattern());
069
070    /** Regexp pattern to match java.lang package. */
071    private static final Pattern JAVA_LANG_PACKAGE_PATTERN =
072        CommonUtils.createPattern("^java\\.lang\\.[a-zA-Z]+$");
073
074    /** Suffix for the star import. */
075    private static final String STAR_IMPORT_SUFFIX = ".*";
076
077    /** Set of the imports. */
078    private final Set<FullIdent> imports = new HashSet<>();
079
080    /** Set of references - possibly to imports or other things. */
081    private final Set<String> referenced = new HashSet<>();
082
083    /** Flag to indicate when time to start collecting references. */
084    private boolean collect;
085    /** Flag whether to process Javadoc comments. */
086    private boolean processJavadoc = true;
087
088    /**
089     * Sets whether to process JavaDoc or not.
090     *
091     * @param value Flag for processing JavaDoc.
092     */
093    public void setProcessJavadoc(boolean value) {
094        processJavadoc = value;
095    }
096
097    @Override
098    public void beginTree(DetailAST rootAST) {
099        collect = false;
100        imports.clear();
101        referenced.clear();
102    }
103
104    @Override
105    public void finishTree(DetailAST rootAST) {
106        // loop over all the imports to see if referenced.
107        imports.stream()
108            .filter(imprt -> isUnusedImport(imprt.getText()))
109            .forEach(imprt -> log(imprt.getLineNo(),
110                imprt.getColumnNo(),
111                MSG_KEY, imprt.getText()));
112    }
113
114    @Override
115    public int[] getDefaultTokens() {
116        return new int[] {
117            TokenTypes.IDENT,
118            TokenTypes.IMPORT,
119            TokenTypes.STATIC_IMPORT,
120            // Definitions that may contain Javadoc...
121            TokenTypes.PACKAGE_DEF,
122            TokenTypes.ANNOTATION_DEF,
123            TokenTypes.ANNOTATION_FIELD_DEF,
124            TokenTypes.ENUM_DEF,
125            TokenTypes.ENUM_CONSTANT_DEF,
126            TokenTypes.CLASS_DEF,
127            TokenTypes.INTERFACE_DEF,
128            TokenTypes.METHOD_DEF,
129            TokenTypes.CTOR_DEF,
130            TokenTypes.VARIABLE_DEF,
131        };
132    }
133
134    @Override
135    public int[] getRequiredTokens() {
136        return getDefaultTokens();
137    }
138
139    @Override
140    public int[] getAcceptableTokens() {
141        return new int[] {
142            TokenTypes.IDENT,
143            TokenTypes.IMPORT,
144            TokenTypes.STATIC_IMPORT,
145            // Definitions that may contain Javadoc...
146            TokenTypes.PACKAGE_DEF,
147            TokenTypes.ANNOTATION_DEF,
148            TokenTypes.ANNOTATION_FIELD_DEF,
149            TokenTypes.ENUM_DEF,
150            TokenTypes.ENUM_CONSTANT_DEF,
151            TokenTypes.CLASS_DEF,
152            TokenTypes.INTERFACE_DEF,
153            TokenTypes.METHOD_DEF,
154            TokenTypes.CTOR_DEF,
155            TokenTypes.VARIABLE_DEF,
156        };
157    }
158
159    @Override
160    public void visitToken(DetailAST ast) {
161        if (ast.getType() == TokenTypes.IDENT) {
162            if (collect) {
163                processIdent(ast);
164            }
165        }
166        else if (ast.getType() == TokenTypes.IMPORT) {
167            processImport(ast);
168        }
169        else if (ast.getType() == TokenTypes.STATIC_IMPORT) {
170            processStaticImport(ast);
171        }
172        else {
173            collect = true;
174            if (processJavadoc) {
175                collectReferencesFromJavadoc(ast);
176            }
177        }
178    }
179
180    /**
181     * Checks whether an import is unused.
182     * @param imprt an import.
183     * @return true if an import is unused.
184     */
185    private boolean isUnusedImport(String imprt) {
186        final Matcher javaLangPackageMatcher = JAVA_LANG_PACKAGE_PATTERN.matcher(imprt);
187        return !referenced.contains(CommonUtils.baseClassName(imprt))
188            || javaLangPackageMatcher.matches();
189    }
190
191    /**
192     * Collects references made by IDENT.
193     * @param ast the IDENT node to process
194     */
195    private void processIdent(DetailAST ast) {
196        final DetailAST parent = ast.getParent();
197        final int parentType = parent.getType();
198        if (parentType != TokenTypes.DOT
199            && parentType != TokenTypes.METHOD_DEF
200            || parentType == TokenTypes.DOT
201                && ast.getNextSibling() != null) {
202            referenced.add(ast.getText());
203        }
204    }
205
206    /**
207     * Collects the details of imports.
208     * @param ast node containing the import details
209     */
210    private void processImport(DetailAST ast) {
211        final FullIdent name = FullIdent.createFullIdentBelow(ast);
212        if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
213            imports.add(name);
214        }
215    }
216
217    /**
218     * Collects the details of static imports.
219     * @param ast node containing the static import details
220     */
221    private void processStaticImport(DetailAST ast) {
222        final FullIdent name =
223            FullIdent.createFullIdent(
224                ast.getFirstChild().getNextSibling());
225        if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
226            imports.add(name);
227        }
228    }
229
230    /**
231     * Collects references made in Javadoc comments.
232     * @param ast node to inspect for Javadoc
233     */
234    private void collectReferencesFromJavadoc(DetailAST ast) {
235        final FileContents contents = getFileContents();
236        final int lineNo = ast.getLineNo();
237        final TextBlock textBlock = contents.getJavadocBefore(lineNo);
238        if (textBlock != null) {
239            referenced.addAll(collectReferencesFromJavadoc(textBlock));
240        }
241    }
242
243    /**
244     * Process a javadoc {@link TextBlock} and return the set of classes
245     * referenced within.
246     * @param textBlock The javadoc block to parse
247     * @return a set of classes referenced in the javadoc block
248     */
249    private static Set<String> collectReferencesFromJavadoc(TextBlock textBlock) {
250        final Set<String> references = new HashSet<>();
251        // process all the @link type tags
252        // INLINE tags inside BLOCKs get hidden when using ALL
253        getValidTags(textBlock, JavadocUtils.JavadocTagType.INLINE).stream()
254            .filter(JavadocTag::canReferenceImports)
255            .forEach(tag -> references.addAll(processJavadocTag(tag)));
256        // process all the @throws type tags
257        getValidTags(textBlock, JavadocUtils.JavadocTagType.BLOCK).stream()
258            .filter(JavadocTag::canReferenceImports)
259            .forEach(tag -> references.addAll(matchPattern(tag.getFirstArg(), FIRST_CLASS_NAME)));
260        return references;
261    }
262
263    /**
264     * Returns the list of valid tags found in a javadoc {@link TextBlock}.
265     * @param cmt The javadoc block to parse
266     * @param tagType The type of tags we're interested in
267     * @return the list of tags
268     */
269    private static List<JavadocTag> getValidTags(TextBlock cmt,
270            JavadocUtils.JavadocTagType tagType) {
271        return JavadocUtils.getJavadocTags(cmt, tagType).getValidTags();
272    }
273
274    /**
275     * Returns a list of references found in a javadoc {@link JavadocTag}.
276     * @param tag The javadoc tag to parse
277     * @return A list of references found in this tag
278     */
279    private static Set<String> processJavadocTag(JavadocTag tag) {
280        final Set<String> references = new HashSet<>();
281        final String identifier = tag.getFirstArg().trim();
282        for (Pattern pattern : new Pattern[]
283        {FIRST_CLASS_NAME, ARGUMENT_NAME}) {
284            references.addAll(matchPattern(identifier, pattern));
285        }
286        return references;
287    }
288
289    /**
290     * Extracts a list of texts matching a {@link Pattern} from a
291     * {@link String}.
292     * @param identifier The String to match the pattern against
293     * @param pattern The Pattern used to extract the texts
294     * @return A list of texts which matched the pattern
295     */
296    private static Set<String> matchPattern(String identifier, Pattern pattern) {
297        final Set<String> references = new HashSet<>();
298        final Matcher matcher = pattern.matcher(identifier);
299        while (matcher.find()) {
300            references.add(matcher.group(1));
301        }
302        return references;
303    }
304}