001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.coding; 021 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Set; 026 027import com.google.common.collect.Sets; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.FullIdent; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck; 032import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 033import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 034 035/** 036 * Checks that particular class are never used as types in variable 037 * declarations, return values or parameters. 038 * 039 * <p>Rationale: 040 * Helps reduce coupling on concrete classes. 041 * 042 * <p>Check has following properties: 043 * 044 * <p><b>format</b> - Pattern for illegal class names. 045 * 046 * <p><b>legalAbstractClassNames</b> - Abstract classes that may be used as types. 047 * 048 * <p><b>illegalClassNames</b> - Classes that should not be used as types in variable 049 declarations, return values or parameters. 050 * It is possible to set illegal class names via short or 051 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 052 * canonical</a> name. 053 * Specifying illegal type invokes analyzing imports and Check puts violations at 054 * corresponding declarations 055 * (of variables, methods or parameters). This helps to avoid ambiguous cases, e.g.: 056 * 057 * <p>{@code java.awt.List} was set as illegal class name, then, code like: 058 * 059 * <p>{@code 060 * import java.util.List;<br> 061 * ...<br> 062 * List list; //No violation here 063 * } 064 * 065 * <p>will be ok. 066 * 067 * <p><b>validateAbstractClassNames</b> - controls whether to validate abstract class names. 068 * Default value is <b>false</b> 069 * </p> 070 * 071 * <p><b>ignoredMethodNames</b> - Methods that should not be checked. 072 * 073 * <p><b>memberModifiers</b> - To check only methods and fields with only specified modifiers. 074 * 075 * <p>In most cases it's justified to put following classes to <b>illegalClassNames</b>: 076 * <ul> 077 * <li>GregorianCalendar</li> 078 * <li>Hashtable</li> 079 * <li>ArrayList</li> 080 * <li>LinkedList</li> 081 * <li>Vector</li> 082 * </ul> 083 * 084 * <p>as methods that are differ from interface methods are rear used, so in most cases user will 085 * benefit from checking for them. 086 * </p> 087 * 088 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 089 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 090 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a> 091 */ 092public final class IllegalTypeCheck extends AbstractFormatCheck { 093 094 /** 095 * A key is pointing to the warning message text in "messages.properties" 096 * file. 097 */ 098 public static final String MSG_KEY = "illegal.type"; 099 100 /** Default value of pattern for illegal class name. */ 101 private static final String DEFAULT_FORMAT = "^(.*[\\.])?Abstract.*$"; 102 /** Abstract classes legal by default. */ 103 private static final String[] DEFAULT_LEGAL_ABSTRACT_NAMES = {}; 104 /** Types illegal by default. */ 105 private static final String[] DEFAULT_ILLEGAL_TYPES = { 106 "HashSet", 107 "HashMap", 108 "LinkedHashMap", 109 "LinkedHashSet", 110 "TreeSet", 111 "TreeMap", 112 "java.util.HashSet", 113 "java.util.HashMap", 114 "java.util.LinkedHashMap", 115 "java.util.LinkedHashSet", 116 "java.util.TreeSet", 117 "java.util.TreeMap", 118 }; 119 120 /** Default ignored method names. */ 121 private static final String[] DEFAULT_IGNORED_METHOD_NAMES = { 122 "getInitialContext", 123 "getEnvironment", 124 }; 125 126 /** Illegal classes. */ 127 private final Set<String> illegalClassNames = Sets.newHashSet(); 128 /** Legal abstract classes. */ 129 private final Set<String> legalAbstractClassNames = Sets.newHashSet(); 130 /** Methods which should be ignored. */ 131 private final Set<String> ignoredMethodNames = Sets.newHashSet(); 132 /** Check methods and fields with only corresponding modifiers. */ 133 private List<Integer> memberModifiers; 134 135 /** 136 * Controls whether to validate abstract class names. 137 */ 138 private boolean validateAbstractClassNames; 139 140 /** Creates new instance of the check. */ 141 public IllegalTypeCheck() { 142 super(DEFAULT_FORMAT); 143 setIllegalClassNames(DEFAULT_ILLEGAL_TYPES); 144 setLegalAbstractClassNames(DEFAULT_LEGAL_ABSTRACT_NAMES); 145 setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES); 146 } 147 148 /** 149 * Sets whether to validate abstract class names. 150 * @param validateAbstractClassNames whether abstract class names must be ignored. 151 */ 152 public void setValidateAbstractClassNames(boolean validateAbstractClassNames) { 153 this.validateAbstractClassNames = validateAbstractClassNames; 154 } 155 156 @Override 157 public int[] getDefaultTokens() { 158 return getAcceptableTokens(); 159 } 160 161 @Override 162 public int[] getAcceptableTokens() { 163 return new int[] { 164 TokenTypes.VARIABLE_DEF, 165 TokenTypes.PARAMETER_DEF, 166 TokenTypes.METHOD_DEF, 167 TokenTypes.IMPORT, 168 }; 169 } 170 171 @Override 172 public int[] getRequiredTokens() { 173 return getAcceptableTokens(); 174 } 175 176 @Override 177 public void visitToken(DetailAST ast) { 178 switch (ast.getType()) { 179 case TokenTypes.METHOD_DEF: 180 if (isVerifiable(ast)) { 181 visitMethodDef(ast); 182 } 183 break; 184 case TokenTypes.VARIABLE_DEF: 185 if (isVerifiable(ast)) { 186 visitVariableDef(ast); 187 } 188 break; 189 case TokenTypes.PARAMETER_DEF: 190 visitParameterDef(ast); 191 break; 192 case TokenTypes.IMPORT: 193 visitImport(ast); 194 break; 195 default: 196 throw new IllegalStateException(ast.toString()); 197 } 198 } 199 200 /** 201 * Checks if current method's return type or variable's type is verifiable 202 * according to <b>memberModifiers</b> option. 203 * @param methodOrVariableDef METHOD_DEF or VARIABLE_DEF ast node. 204 * @return true if member is verifiable according to <b>memberModifiers</b> option. 205 */ 206 private boolean isVerifiable(DetailAST methodOrVariableDef) { 207 boolean result = true; 208 if (memberModifiers != null) { 209 final DetailAST modifiersAst = methodOrVariableDef 210 .findFirstToken(TokenTypes.MODIFIERS); 211 result = isContainVerifiableType(modifiersAst); 212 } 213 return result; 214 } 215 216 /** 217 * Checks is modifiers contain verifiable type. 218 * 219 * @param modifiers 220 * parent node for all modifiers 221 * @return true if method or variable can be verified 222 */ 223 private boolean isContainVerifiableType(DetailAST modifiers) { 224 boolean result = false; 225 if (modifiers.getFirstChild() != null) { 226 for (DetailAST modifier = modifiers.getFirstChild(); modifier != null; 227 modifier = modifier.getNextSibling()) { 228 if (memberModifiers.contains(modifier.getType())) { 229 result = true; 230 } 231 } 232 } 233 return result; 234 } 235 236 /** 237 * Checks return type of a given method. 238 * @param methodDef method for check. 239 */ 240 private void visitMethodDef(DetailAST methodDef) { 241 if (isCheckedMethod(methodDef)) { 242 checkClassName(methodDef); 243 } 244 } 245 246 /** 247 * Checks type of parameters. 248 * @param parameterDef parameter list for check. 249 */ 250 private void visitParameterDef(DetailAST parameterDef) { 251 final DetailAST grandParentAST = parameterDef.getParent().getParent(); 252 253 if (grandParentAST.getType() == TokenTypes.METHOD_DEF 254 && isCheckedMethod(grandParentAST)) { 255 checkClassName(parameterDef); 256 } 257 } 258 259 /** 260 * Checks type of given variable. 261 * @param variableDef variable to check. 262 */ 263 private void visitVariableDef(DetailAST variableDef) { 264 checkClassName(variableDef); 265 } 266 267 /** 268 * Checks imported type (as static and star imports are not supported by Check, 269 * only type is in the consideration).<br> 270 * If this type is illegal due to Check's options - puts violation on it. 271 * @param importAst {@link TokenTypes#IMPORT Import} 272 */ 273 private void visitImport(DetailAST importAst) { 274 if (!isStarImport(importAst)) { 275 final String canonicalName = getImportedTypeCanonicalName(importAst); 276 extendIllegalClassNamesWithShortName(canonicalName); 277 } 278 } 279 280 /** 281 * Checks if current import is star import. E.g.: 282 * <p> 283 * {@code 284 * import java.util.*; 285 * } 286 * </p> 287 * @param importAst {@link TokenTypes#IMPORT Import} 288 * @return true if it is star import 289 */ 290 private static boolean isStarImport(DetailAST importAst) { 291 boolean result = false; 292 DetailAST toVisit = importAst; 293 while (toVisit != null) { 294 toVisit = getNextSubTreeNode(toVisit, importAst); 295 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 296 result = true; 297 break; 298 } 299 } 300 return result; 301 } 302 303 /** 304 * Checks type of given method, parameter or variable. 305 * @param ast node to check. 306 */ 307 private void checkClassName(DetailAST ast) { 308 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 309 final FullIdent ident = CheckUtils.createFullType(type); 310 311 if (isMatchingClassName(ident.getText())) { 312 log(ident.getLineNo(), ident.getColumnNo(), 313 MSG_KEY, ident.getText()); 314 } 315 } 316 317 /** 318 * @param className class name to check. 319 * @return true if given class name is one of illegal classes 320 * or if it matches to abstract class names pattern. 321 */ 322 private boolean isMatchingClassName(String className) { 323 final String shortName = className.substring(className.lastIndexOf('.') + 1); 324 return illegalClassNames.contains(className) 325 || illegalClassNames.contains(shortName) 326 || validateAbstractClassNames 327 && !legalAbstractClassNames.contains(className) 328 && getRegexp().matcher(className).find(); 329 } 330 331 /** 332 * Extends illegal class names set via imported short type name. 333 * @param canonicalName 334 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 335 * Canonical</a> name of imported type. 336 */ 337 private void extendIllegalClassNamesWithShortName(String canonicalName) { 338 if (illegalClassNames.contains(canonicalName)) { 339 final String shortName = canonicalName 340 .substring(canonicalName.lastIndexOf('.') + 1); 341 illegalClassNames.add(shortName); 342 } 343 } 344 345 /** 346 * Gets imported type's 347 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 348 * canonical name</a>. 349 * @param importAst {@link TokenTypes#IMPORT Import} 350 * @return Imported canonical type's name. 351 */ 352 private static String getImportedTypeCanonicalName(DetailAST importAst) { 353 final StringBuilder canonicalNameBuilder = new StringBuilder(); 354 DetailAST toVisit = importAst; 355 while (toVisit != null) { 356 toVisit = getNextSubTreeNode(toVisit, importAst); 357 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 358 canonicalNameBuilder.append(toVisit.getText()); 359 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, importAst); 360 if (nextSubTreeNode.getType() != TokenTypes.SEMI) { 361 canonicalNameBuilder.append('.'); 362 } 363 } 364 } 365 return canonicalNameBuilder.toString(); 366 } 367 368 /** 369 * Gets the next node of a syntactical tree (child of a current node or 370 * sibling of a current node, or sibling of a parent of a current node). 371 * @param currentNodeAst Current node in considering 372 * @param subTreeRootAst SubTree root 373 * @return Current node after bypassing, if current node reached the root of a subtree 374 * method returns null 375 */ 376 private static DetailAST 377 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 378 DetailAST currentNode = currentNodeAst; 379 DetailAST toVisitAst = currentNode.getFirstChild(); 380 while (toVisitAst == null) { 381 toVisitAst = currentNode.getNextSibling(); 382 if (toVisitAst == null) { 383 if (currentNode.getParent().equals(subTreeRootAst)) { 384 break; 385 } 386 currentNode = currentNode.getParent(); 387 } 388 } 389 return toVisitAst; 390 } 391 392 /** 393 * @param ast method def to check. 394 * @return true if we should check this method. 395 */ 396 private boolean isCheckedMethod(DetailAST ast) { 397 final String methodName = 398 ast.findFirstToken(TokenTypes.IDENT).getText(); 399 return !ignoredMethodNames.contains(methodName); 400 } 401 402 /** 403 * Set the list of illegal variable types. 404 * @param classNames array of illegal variable types 405 */ 406 public void setIllegalClassNames(String... classNames) { 407 illegalClassNames.clear(); 408 Collections.addAll(illegalClassNames, classNames); 409 } 410 411 /** 412 * Set the list of ignore method names. 413 * @param methodNames array of ignored method names 414 */ 415 public void setIgnoredMethodNames(String... methodNames) { 416 ignoredMethodNames.clear(); 417 Collections.addAll(ignoredMethodNames, methodNames); 418 } 419 420 /** 421 * Set the list of legal abstract class names. 422 * @param classNames array of legal abstract class names 423 */ 424 public void setLegalAbstractClassNames(String... classNames) { 425 legalAbstractClassNames.clear(); 426 Collections.addAll(legalAbstractClassNames, classNames); 427 } 428 429 /** 430 * Set the list of member modifiers (of methods and fields) which should be checked. 431 * @param modifiers String contains modifiers. 432 */ 433 public void setMemberModifiers(String modifiers) { 434 final List<Integer> modifiersList = new ArrayList<>(); 435 for (String modifier : modifiers.split(",")) { 436 modifiersList.add(TokenUtils.getTokenId(modifier.trim())); 437 } 438 memberModifiers = modifiersList; 439 } 440}