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 * <module name="UnusedImports"/> 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}