001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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.google.common.collect.Sets; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.FileContents; 032import com.puppycrawl.tools.checkstyle.api.FullIdent; 033import com.puppycrawl.tools.checkstyle.api.TextBlock; 034import com.puppycrawl.tools.checkstyle.api.TokenTypes; 035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag; 036import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 037import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 038 039/** 040 * <p> 041 * Checks for unused import statements. 042 * </p> 043 * <p> 044 * An example of how to configure the check is: 045 * </p> 046 * <pre> 047 * <module name="UnusedImports"/> 048 * </pre> 049 * Compatible with Java 1.5 source. 050 * 051 * @author Oliver Burn 052 */ 053public class UnusedImportsCheck extends AbstractCheck { 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" 057 * file. 058 */ 059 public static final String MSG_KEY = "import.unused"; 060 061 /** Regex to match class names. */ 062 private static final Pattern CLASS_NAME = Pattern.compile( 063 "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)"); 064 /** Regex to match the first class name. */ 065 private static final Pattern FIRST_CLASS_NAME = Pattern.compile( 066 "^" + CLASS_NAME); 067 /** Regex to match argument names. */ 068 private static final Pattern ARGUMENT_NAME = Pattern.compile( 069 "[(,]\\s*" + CLASS_NAME.pattern()); 070 071 /** Suffix for the star import. */ 072 private static final String STAR_IMPORT_SUFFIX = ".*"; 073 074 /** Set of the imports. */ 075 private final Set<FullIdent> imports = Sets.newHashSet(); 076 077 /** Set of references - possibly to imports or other things. */ 078 private final Set<String> referenced = Sets.newHashSet(); 079 080 /** Flag to indicate when time to start collecting references. */ 081 private boolean collect; 082 /** Flag whether to process Javadoc comments. */ 083 private boolean processJavadoc = true; 084 085 /** 086 * Sets whether to process JavaDoc or not. 087 * 088 * @param value Flag for processing JavaDoc. 089 */ 090 public void setProcessJavadoc(boolean value) { 091 processJavadoc = value; 092 } 093 094 @Override 095 public void beginTree(DetailAST rootAST) { 096 collect = false; 097 imports.clear(); 098 referenced.clear(); 099 } 100 101 @Override 102 public void finishTree(DetailAST rootAST) { 103 // loop over all the imports to see if referenced. 104 for (final FullIdent imp : imports) { 105 if (!referenced.contains(CommonUtils.baseClassName(imp.getText()))) { 106 log(imp.getLineNo(), 107 imp.getColumnNo(), 108 MSG_KEY, imp.getText()); 109 } 110 } 111 } 112 113 @Override 114 public int[] getDefaultTokens() { 115 return new int[] { 116 TokenTypes.IDENT, 117 TokenTypes.IMPORT, 118 TokenTypes.STATIC_IMPORT, 119 // Definitions that may contain Javadoc... 120 TokenTypes.PACKAGE_DEF, 121 TokenTypes.ANNOTATION_DEF, 122 TokenTypes.ANNOTATION_FIELD_DEF, 123 TokenTypes.ENUM_DEF, 124 TokenTypes.ENUM_CONSTANT_DEF, 125 TokenTypes.CLASS_DEF, 126 TokenTypes.INTERFACE_DEF, 127 TokenTypes.METHOD_DEF, 128 TokenTypes.CTOR_DEF, 129 TokenTypes.VARIABLE_DEF, 130 }; 131 } 132 133 @Override 134 public int[] getRequiredTokens() { 135 return getDefaultTokens(); 136 } 137 138 @Override 139 public int[] getAcceptableTokens() { 140 return new int[] { 141 TokenTypes.IDENT, 142 TokenTypes.IMPORT, 143 TokenTypes.STATIC_IMPORT, 144 // Definitions that may contain Javadoc... 145 TokenTypes.PACKAGE_DEF, 146 TokenTypes.ANNOTATION_DEF, 147 TokenTypes.ANNOTATION_FIELD_DEF, 148 TokenTypes.ENUM_DEF, 149 TokenTypes.ENUM_CONSTANT_DEF, 150 TokenTypes.CLASS_DEF, 151 TokenTypes.INTERFACE_DEF, 152 TokenTypes.METHOD_DEF, 153 TokenTypes.CTOR_DEF, 154 TokenTypes.VARIABLE_DEF, 155 }; 156 } 157 158 @Override 159 public void visitToken(DetailAST ast) { 160 if (ast.getType() == TokenTypes.IDENT) { 161 if (collect) { 162 processIdent(ast); 163 } 164 } 165 else if (ast.getType() == TokenTypes.IMPORT) { 166 processImport(ast); 167 } 168 else if (ast.getType() == TokenTypes.STATIC_IMPORT) { 169 processStaticImport(ast); 170 } 171 else { 172 collect = true; 173 if (processJavadoc) { 174 collectReferencesFromJavadoc(ast); 175 } 176 } 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 Set<String> references = new HashSet<>(); 239 // process all the @link type tags 240 // INLINE tags inside BLOCKs get hidden when using ALL 241 for (final JavadocTag tag 242 : getValidTags(textBlock, JavadocUtils.JavadocTagType.INLINE)) { 243 if (tag.canReferenceImports()) { 244 references.addAll(processJavadocTag(tag)); 245 } 246 } 247 // process all the @throws type tags 248 for (final JavadocTag tag 249 : getValidTags(textBlock, JavadocUtils.JavadocTagType.BLOCK)) { 250 if (tag.canReferenceImports()) { 251 references.addAll( 252 matchPattern(tag.getFirstArg(), FIRST_CLASS_NAME)); 253 } 254 } 255 return references; 256 } 257 258 /** 259 * Returns the list of valid tags found in a javadoc {@link TextBlock}. 260 * @param cmt The javadoc block to parse 261 * @param tagType The type of tags we're interested in 262 * @return the list of tags 263 */ 264 private static List<JavadocTag> getValidTags(TextBlock cmt, 265 JavadocUtils.JavadocTagType tagType) { 266 return JavadocUtils.getJavadocTags(cmt, tagType).getValidTags(); 267 } 268 269 /** 270 * Returns a list of references found in a javadoc {@link JavadocTag}. 271 * @param tag The javadoc tag to parse 272 * @return A list of references found in this tag 273 */ 274 private static Set<String> processJavadocTag(JavadocTag tag) { 275 final Set<String> references = new HashSet<>(); 276 final String identifier = tag.getFirstArg().trim(); 277 for (Pattern pattern : new Pattern[] 278 {FIRST_CLASS_NAME, ARGUMENT_NAME}) { 279 references.addAll(matchPattern(identifier, pattern)); 280 } 281 return references; 282 } 283 284 /** 285 * Extracts a list of texts matching a {@link Pattern} from a 286 * {@link String}. 287 * @param identifier The String to match the pattern against 288 * @param pattern The Pattern used to extract the texts 289 * @return A list of texts which matched the pattern 290 */ 291 private static Set<String> matchPattern(String identifier, Pattern pattern) { 292 final Set<String> references = new HashSet<>(); 293 final Matcher matcher = pattern.matcher(identifier); 294 while (matcher.find()) { 295 references.add(matcher.group(1)); 296 } 297 return references; 298 } 299}