001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.imports;
020
021import com.google.common.collect.Sets;
022import com.puppycrawl.tools.checkstyle.api.Check;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.FileContents;
025import com.puppycrawl.tools.checkstyle.api.FullIdent;
026import com.puppycrawl.tools.checkstyle.api.TextBlock;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.api.Utils;
029import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
030import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocUtils;
031
032import java.util.HashSet;
033import java.util.List;
034import java.util.Set;
035import java.util.regex.Matcher;
036import java.util.regex.Pattern;
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 *
049 * Compatible with Java 1.5 source.
050 *
051 * @author Oliver Burn
052 */
053public class UnusedImportsCheck extends Check
054{
055    /** regex to match class names. */
056    private static final Pattern CLASS_NAME = Pattern.compile(
057           "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)");
058    /** regex to match the first class name. */
059    private static final Pattern FIRST_CLASS_NAME = Pattern.compile(
060           "^" + CLASS_NAME);
061    /** regex to match argument names. */
062    private static final Pattern ARGUMENT_NAME = Pattern.compile(
063           "[(,]\\s*" + CLASS_NAME.pattern());
064
065    /** flag to indicate when time to start collecting references. */
066    private boolean collect;
067    /** flag whether to process Javdoc comments. */
068    private boolean processJavadoc;
069
070    /** set of the imports. */
071    private final Set<FullIdent> imports = Sets.newHashSet();
072
073    /** set of references - possibly to imports or other things. */
074    private final Set<String> referenced = Sets.newHashSet();
075
076    /** Default constructor. */
077    public UnusedImportsCheck()
078    {
079    }
080
081    public void setProcessJavadoc(boolean value)
082    {
083        processJavadoc = value;
084    }
085
086    @Override
087    public void beginTree(DetailAST rootAST)
088    {
089        collect = false;
090        imports.clear();
091        referenced.clear();
092    }
093
094    @Override
095    public void finishTree(DetailAST rootAST)
096    {
097        // loop over all the imports to see if referenced.
098        for (final FullIdent imp : imports) {
099            if (!referenced.contains(Utils.baseClassname(imp.getText()))) {
100                log(imp.getLineNo(),
101                    imp.getColumnNo(),
102                    "import.unused", imp.getText());
103            }
104        }
105    }
106
107    @Override
108    public int[] getDefaultTokens()
109    {
110        return new int[] {
111            TokenTypes.IDENT,
112            TokenTypes.IMPORT,
113            TokenTypes.STATIC_IMPORT,
114            // Definitions that may contain Javdoc...
115            TokenTypes.PACKAGE_DEF,
116            TokenTypes.ANNOTATION_DEF,
117            TokenTypes.ANNOTATION_FIELD_DEF,
118            TokenTypes.ENUM_DEF,
119            TokenTypes.ENUM_CONSTANT_DEF,
120            TokenTypes.CLASS_DEF,
121            TokenTypes.INTERFACE_DEF,
122            TokenTypes.METHOD_DEF,
123            TokenTypes.CTOR_DEF,
124            TokenTypes.VARIABLE_DEF,
125        };
126    }
127
128    @Override
129    public int[] getRequiredTokens()
130    {
131        return getDefaultTokens();
132    }
133
134    @Override
135    public void visitToken(DetailAST ast)
136    {
137        if (ast.getType() == TokenTypes.IDENT) {
138            if (collect) {
139                processIdent(ast);
140            }
141        }
142        else if (ast.getType() == TokenTypes.IMPORT) {
143            processImport(ast);
144        }
145        else if (ast.getType() == TokenTypes.STATIC_IMPORT) {
146            processStaticImport(ast);
147        }
148        else {
149            collect = true;
150            if (processJavadoc) {
151                processJavadoc(ast);
152            }
153        }
154    }
155
156    /**
157     * Collects references made by IDENT.
158     * @param ast the IDENT node to process
159     */
160    private void processIdent(DetailAST ast)
161    {
162        final DetailAST parent = ast.getParent();
163        final int parentType = parent.getType();
164        if (((parentType != TokenTypes.DOT)
165            && (parentType != TokenTypes.METHOD_DEF))
166            || ((parentType == TokenTypes.DOT)
167                && (ast.getNextSibling() != null)))
168        {
169            referenced.add(ast.getText());
170        }
171    }
172
173    /**
174     * Collects the details of imports.
175     * @param ast node containing the import details
176     */
177    private void processImport(DetailAST ast)
178    {
179        final FullIdent name = FullIdent.createFullIdentBelow(ast);
180        if ((name != null) && !name.getText().endsWith(".*")) {
181            imports.add(name);
182        }
183    }
184
185    /**
186     * Collects the details of static imports.
187     * @param ast node containing the static import details
188     */
189    private void processStaticImport(DetailAST ast)
190    {
191        final FullIdent name =
192            FullIdent.createFullIdent(
193                ast.getFirstChild().getNextSibling());
194        if ((name != null) && !name.getText().endsWith(".*")) {
195            imports.add(name);
196        }
197    }
198
199    /**
200     * Collects references made in Javadoc comments.
201     * @param ast node to inspect for Javadoc
202     */
203    private void processJavadoc(DetailAST ast)
204    {
205        final FileContents contents = getFileContents();
206        final int lineNo = ast.getLineNo();
207        final TextBlock cmt = contents.getJavadocBefore(lineNo);
208        if (cmt != null) {
209            referenced.addAll(processJavadoc(cmt));
210        }
211    }
212
213    /**
214     * Process a javadoc {@link TextBlock} and return the set of classes
215     * referenced within.
216     * @param cmt The javadoc block to parse
217     * @return a set of classes referenced in the javadoc block
218     */
219    private Set<String> processJavadoc(TextBlock cmt)
220    {
221        final Set<String> references = new HashSet<String>();
222        // process all the @link type tags
223        // INLINEs inside BLOCKs get hidden when using ALL
224        for (final JavadocTag tag
225                : getValidTags(cmt, JavadocUtils.JavadocTagType.INLINE))
226        {
227            if (tag.canReferenceImports()) {
228                references.addAll(processJavadocTag(tag));
229            }
230        }
231        // process all the @throws type tags
232        for (final JavadocTag tag
233                : getValidTags(cmt, JavadocUtils.JavadocTagType.BLOCK))
234        {
235            if (tag.canReferenceImports()) {
236                references.addAll(
237                        matchPattern(tag.getArg1(), FIRST_CLASS_NAME));
238            }
239        }
240        return references;
241    }
242
243    /**
244     * Returns the list of valid tags found in a javadoc {@link TextBlock}
245     * @param cmt The javadoc block to parse
246     * @param tagType The type of tags we're interested in
247     * @return the list of tags
248     */
249    private List<JavadocTag> getValidTags(TextBlock cmt,
250            JavadocUtils.JavadocTagType tagType)
251    {
252        return JavadocUtils.getJavadocTags(cmt, tagType).getValidTags();
253    }
254
255    /**
256     * Returns a list of references found in a javadoc {@link JavadocTag}
257     * @param tag The javadoc tag to parse
258     * @return A list of references found in this tag
259     */
260    private Set<String> processJavadocTag(JavadocTag tag)
261    {
262        final Set<String> references = new HashSet<String>();
263        final String identifier = tag.getArg1().trim();
264        for (Pattern pattern : new Pattern[]
265        {FIRST_CLASS_NAME, ARGUMENT_NAME})
266        {
267            references.addAll(matchPattern(identifier, pattern));
268        }
269        return references;
270    }
271
272    /**
273     * Extracts a list of texts matching a {@link Pattern} from a
274     * {@link String}.
275     * @param identifier The String to match the pattern against
276     * @param pattern The Pattern used to extract the texts
277     * @return A list of texts which matched the pattern
278     */
279    private Set<String> matchPattern(String identifier, Pattern pattern)
280    {
281        final Set<String> references = new HashSet<String>();
282        final Matcher matcher = pattern.matcher(identifier);
283        while (matcher.find()) {
284            references.add(matcher.group(1));
285        }
286        return references;
287    }
288}