001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.HashSet; 025import java.util.List; 026import java.util.Set; 027import java.util.regex.Pattern; 028 029import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 030import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 031import com.puppycrawl.tools.checkstyle.api.DetailAST; 032import com.puppycrawl.tools.checkstyle.api.FullIdent; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 035 036/** 037 * Checks that particular classes or interfaces are never used. 038 * 039 * <p>Rationale: 040 * Helps reduce coupling on concrete classes. 041 * 042 * <p>Check has following properties: 043 * 044 * <p><b>illegalAbstractClassNameFormat</b> - Pattern for illegal abstract 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="https://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 any of the specified modifiers. 074 * This property does not affect method calls nor method references. 075 * 076 * <p>In most cases it's justified to put following classes to <b>illegalClassNames</b>: 077 * <ul> 078 * <li>GregorianCalendar</li> 079 * <li>Hashtable</li> 080 * <li>ArrayList</li> 081 * <li>LinkedList</li> 082 * <li>Vector</li> 083 * </ul> 084 * 085 * <p>as methods that are differ from interface methods are rear used, so in most cases user will 086 * benefit from checking for them. 087 * </p> 088 * 089 */ 090@FileStatefulCheck 091public final class IllegalTypeCheck extends AbstractCheck { 092 093 /** 094 * A key is pointing to the warning message text in "messages.properties" 095 * file. 096 */ 097 public static final String MSG_KEY = "illegal.type"; 098 099 /** Types illegal by default. */ 100 private static final String[] DEFAULT_ILLEGAL_TYPES = { 101 "HashSet", 102 "HashMap", 103 "LinkedHashMap", 104 "LinkedHashSet", 105 "TreeSet", 106 "TreeMap", 107 "java.util.HashSet", 108 "java.util.HashMap", 109 "java.util.LinkedHashMap", 110 "java.util.LinkedHashSet", 111 "java.util.TreeSet", 112 "java.util.TreeMap", 113 }; 114 115 /** Default ignored method names. */ 116 private static final String[] DEFAULT_IGNORED_METHOD_NAMES = { 117 "getInitialContext", 118 "getEnvironment", 119 }; 120 121 /** Illegal classes. */ 122 private final Set<String> illegalClassNames = new HashSet<>(); 123 /** Illegal short classes. */ 124 private final Set<String> illegalShortClassNames = new HashSet<>(); 125 /** Legal abstract classes. */ 126 private final Set<String> legalAbstractClassNames = new HashSet<>(); 127 /** Methods which should be ignored. */ 128 private final Set<String> ignoredMethodNames = new HashSet<>(); 129 /** Check methods and fields with only corresponding modifiers. */ 130 private List<Integer> memberModifiers; 131 132 /** The regexp to match against. */ 133 private Pattern illegalAbstractClassNameFormat = Pattern.compile("^(.*[.])?Abstract.*$"); 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 setIllegalClassNames(DEFAULT_ILLEGAL_TYPES); 143 setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES); 144 } 145 146 /** 147 * Set the format for the specified regular expression. 148 * @param pattern a pattern. 149 */ 150 public void setIllegalAbstractClassNameFormat(Pattern pattern) { 151 illegalAbstractClassNameFormat = pattern; 152 } 153 154 /** 155 * Sets whether to validate abstract class names. 156 * @param validateAbstractClassNames whether abstract class names must be ignored. 157 */ 158 public void setValidateAbstractClassNames(boolean validateAbstractClassNames) { 159 this.validateAbstractClassNames = validateAbstractClassNames; 160 } 161 162 @Override 163 public int[] getDefaultTokens() { 164 return getAcceptableTokens(); 165 } 166 167 @Override 168 public int[] getAcceptableTokens() { 169 return new int[] { 170 TokenTypes.ANNOTATION_FIELD_DEF, 171 TokenTypes.CLASS_DEF, 172 TokenTypes.IMPORT, 173 TokenTypes.INTERFACE_DEF, 174 TokenTypes.METHOD_CALL, 175 TokenTypes.METHOD_DEF, 176 TokenTypes.METHOD_REF, 177 TokenTypes.PARAMETER_DEF, 178 TokenTypes.VARIABLE_DEF, 179 }; 180 } 181 182 @Override 183 public void beginTree(DetailAST rootAST) { 184 illegalShortClassNames.clear(); 185 186 for (String s : illegalClassNames) { 187 if (s.indexOf('.') == -1) { 188 illegalShortClassNames.add(s); 189 } 190 } 191 } 192 193 @Override 194 public int[] getRequiredTokens() { 195 return new int[] {TokenTypes.IMPORT}; 196 } 197 198 @Override 199 public void visitToken(DetailAST ast) { 200 switch (ast.getType()) { 201 case TokenTypes.CLASS_DEF: 202 case TokenTypes.INTERFACE_DEF: 203 visitTypeDef(ast); 204 break; 205 case TokenTypes.METHOD_CALL: 206 case TokenTypes.METHOD_REF: 207 visitMethodCallOrRef(ast); 208 break; 209 case TokenTypes.METHOD_DEF: 210 visitMethodDef(ast); 211 break; 212 case TokenTypes.VARIABLE_DEF: 213 case TokenTypes.ANNOTATION_FIELD_DEF: 214 visitVariableDef(ast); 215 break; 216 case TokenTypes.PARAMETER_DEF: 217 visitParameterDef(ast); 218 break; 219 case TokenTypes.IMPORT: 220 visitImport(ast); 221 break; 222 default: 223 throw new IllegalStateException(ast.toString()); 224 } 225 } 226 227 /** 228 * Checks if current method's return type or variable's type is verifiable 229 * according to <b>memberModifiers</b> option. 230 * @param methodOrVariableDef METHOD_DEF or VARIABLE_DEF ast node. 231 * @return true if member is verifiable according to <b>memberModifiers</b> option. 232 */ 233 private boolean isVerifiable(DetailAST methodOrVariableDef) { 234 boolean result = true; 235 if (memberModifiers != null) { 236 final DetailAST modifiersAst = methodOrVariableDef 237 .findFirstToken(TokenTypes.MODIFIERS); 238 result = isContainVerifiableType(modifiersAst); 239 } 240 return result; 241 } 242 243 /** 244 * Checks is modifiers contain verifiable type. 245 * 246 * @param modifiers 247 * parent node for all modifiers 248 * @return true if method or variable can be verified 249 */ 250 private boolean isContainVerifiableType(DetailAST modifiers) { 251 boolean result = false; 252 if (modifiers.getFirstChild() != null) { 253 for (DetailAST modifier = modifiers.getFirstChild(); modifier != null; 254 modifier = modifier.getNextSibling()) { 255 if (memberModifiers.contains(modifier.getType())) { 256 result = true; 257 break; 258 } 259 } 260 } 261 return result; 262 } 263 264 /** 265 * Checks the super type and implemented interfaces of a given type. 266 * @param typeDef class or interface for check. 267 */ 268 private void visitTypeDef(DetailAST typeDef) { 269 if (isVerifiable(typeDef)) { 270 checkTypeParameters(typeDef); 271 final DetailAST extendsClause = typeDef.findFirstToken(TokenTypes.EXTENDS_CLAUSE); 272 if (extendsClause != null) { 273 checkBaseTypes(extendsClause); 274 } 275 final DetailAST implementsClause = typeDef.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE); 276 if (implementsClause != null) { 277 checkBaseTypes(implementsClause); 278 } 279 } 280 } 281 282 /** 283 * Checks return type of a given method. 284 * @param methodDef method for check. 285 */ 286 private void visitMethodDef(DetailAST methodDef) { 287 if (isVerifiable(methodDef) && isCheckedMethod(methodDef)) { 288 checkClassName(methodDef); 289 } 290 } 291 292 /** 293 * Checks type of parameters. 294 * @param parameterDef parameter list for check. 295 */ 296 private void visitParameterDef(DetailAST parameterDef) { 297 final DetailAST grandParentAST = parameterDef.getParent().getParent(); 298 299 if (grandParentAST.getType() == TokenTypes.METHOD_DEF 300 && isCheckedMethod(grandParentAST) 301 && isVerifiable(grandParentAST)) { 302 checkClassName(parameterDef); 303 } 304 } 305 306 /** 307 * Checks type of given variable. 308 * @param variableDef variable to check. 309 */ 310 private void visitVariableDef(DetailAST variableDef) { 311 if (isVerifiable(variableDef)) { 312 checkClassName(variableDef); 313 } 314 } 315 316 /** 317 * Checks the type arguments of given method call/reference. 318 * @param methodCallOrRef method call/reference to check. 319 */ 320 private void visitMethodCallOrRef(DetailAST methodCallOrRef) { 321 checkTypeArguments(methodCallOrRef); 322 } 323 324 /** 325 * Checks imported type (as static and star imports are not supported by Check, 326 * only type is in the consideration).<br> 327 * If this type is illegal due to Check's options - puts violation on it. 328 * @param importAst {@link TokenTypes#IMPORT Import} 329 */ 330 private void visitImport(DetailAST importAst) { 331 if (!isStarImport(importAst)) { 332 final String canonicalName = getImportedTypeCanonicalName(importAst); 333 extendIllegalClassNamesWithShortName(canonicalName); 334 } 335 } 336 337 /** 338 * Checks if current import is star import. E.g.: 339 * <p> 340 * {@code 341 * import java.util.*; 342 * } 343 * </p> 344 * @param importAst {@link TokenTypes#IMPORT Import} 345 * @return true if it is star import 346 */ 347 private static boolean isStarImport(DetailAST importAst) { 348 boolean result = false; 349 DetailAST toVisit = importAst; 350 while (toVisit != null) { 351 toVisit = getNextSubTreeNode(toVisit, importAst); 352 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 353 result = true; 354 break; 355 } 356 } 357 return result; 358 } 359 360 /** 361 * Checks type and type arguments/parameters of given method, parameter, variable or 362 * method call/reference. 363 * @param ast node to check. 364 */ 365 private void checkClassName(DetailAST ast) { 366 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 367 checkType(type); 368 checkTypeParameters(ast); 369 } 370 371 /** 372 * Checks the identifier of the given type. 373 * @param type node to check. 374 */ 375 private void checkIdent(DetailAST type) { 376 final FullIdent ident = FullIdent.createFullIdent(type); 377 if (isMatchingClassName(ident.getText())) { 378 log(ident.getDetailAst(), MSG_KEY, ident.getText()); 379 } 380 } 381 382 /** 383 * Checks the {@code extends} or {@code implements} statement. 384 * @param clause DetailAST for either {@link TokenTypes#EXTENDS_CLAUSE} or 385 * {@link TokenTypes#IMPLEMENTS_CLAUSE} 386 */ 387 private void checkBaseTypes(DetailAST clause) { 388 DetailAST child = clause.getFirstChild(); 389 while (child != null) { 390 if (child.getType() == TokenTypes.IDENT) { 391 checkIdent(child); 392 } 393 else if (child.getType() == TokenTypes.TYPE_ARGUMENTS) { 394 TokenUtil.forEachChild(child, TokenTypes.TYPE_ARGUMENT, this::checkType); 395 } 396 child = child.getNextSibling(); 397 } 398 } 399 400 /** 401 * Checks the given type, its arguments and parameters. 402 * @param type node to check. 403 */ 404 private void checkType(DetailAST type) { 405 checkIdent(type.getFirstChild()); 406 checkTypeArguments(type); 407 checkTypeBounds(type); 408 } 409 410 /** 411 * Checks the upper and lower bounds for the given type. 412 * @param type node to check. 413 */ 414 private void checkTypeBounds(DetailAST type) { 415 final DetailAST upperBounds = type.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS); 416 if (upperBounds != null) { 417 checkType(upperBounds); 418 } 419 final DetailAST lowerBounds = type.findFirstToken(TokenTypes.TYPE_LOWER_BOUNDS); 420 if (lowerBounds != null) { 421 checkType(lowerBounds); 422 } 423 } 424 425 /** 426 * Checks the type parameters of the node. 427 * @param node node to check. 428 */ 429 private void checkTypeParameters(final DetailAST node) { 430 final DetailAST typeParameters = node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 431 if (typeParameters != null) { 432 TokenUtil.forEachChild(typeParameters, TokenTypes.TYPE_PARAMETER, this::checkType); 433 } 434 } 435 436 /** 437 * Checks the type arguments of the node. 438 * @param node node to check. 439 */ 440 private void checkTypeArguments(final DetailAST node) { 441 DetailAST typeArguments = node.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 442 if (typeArguments == null) { 443 typeArguments = node.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 444 } 445 446 if (typeArguments != null) { 447 TokenUtil.forEachChild(typeArguments, TokenTypes.TYPE_ARGUMENT, this::checkType); 448 } 449 } 450 451 /** 452 * Returns true if given class name is one of illegal classes or else false. 453 * @param className class name to check. 454 * @return true if given class name is one of illegal classes 455 * or if it matches to abstract class names pattern. 456 */ 457 private boolean isMatchingClassName(String className) { 458 final String shortName = className.substring(className.lastIndexOf('.') + 1); 459 return illegalClassNames.contains(className) 460 || illegalShortClassNames.contains(shortName) 461 || validateAbstractClassNames 462 && !legalAbstractClassNames.contains(className) 463 && illegalAbstractClassNameFormat.matcher(className).find(); 464 } 465 466 /** 467 * Extends illegal class names set via imported short type name. 468 * @param canonicalName 469 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 470 * Canonical</a> name of imported type. 471 */ 472 private void extendIllegalClassNamesWithShortName(String canonicalName) { 473 if (illegalClassNames.contains(canonicalName)) { 474 final String shortName = canonicalName 475 .substring(canonicalName.lastIndexOf('.') + 1); 476 illegalShortClassNames.add(shortName); 477 } 478 } 479 480 /** 481 * Gets imported type's 482 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 483 * canonical name</a>. 484 * @param importAst {@link TokenTypes#IMPORT Import} 485 * @return Imported canonical type's name. 486 */ 487 private static String getImportedTypeCanonicalName(DetailAST importAst) { 488 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 489 DetailAST toVisit = importAst; 490 while (toVisit != null) { 491 toVisit = getNextSubTreeNode(toVisit, importAst); 492 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 493 if (canonicalNameBuilder.length() > 0) { 494 canonicalNameBuilder.append('.'); 495 } 496 canonicalNameBuilder.append(toVisit.getText()); 497 } 498 } 499 return canonicalNameBuilder.toString(); 500 } 501 502 /** 503 * Gets the next node of a syntactical tree (child of a current node or 504 * sibling of a current node, or sibling of a parent of a current node). 505 * @param currentNodeAst Current node in considering 506 * @param subTreeRootAst SubTree root 507 * @return Current node after bypassing, if current node reached the root of a subtree 508 * method returns null 509 */ 510 private static DetailAST 511 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 512 DetailAST currentNode = currentNodeAst; 513 DetailAST toVisitAst = currentNode.getFirstChild(); 514 while (toVisitAst == null) { 515 toVisitAst = currentNode.getNextSibling(); 516 if (toVisitAst == null) { 517 if (currentNode.getParent().equals(subTreeRootAst)) { 518 break; 519 } 520 currentNode = currentNode.getParent(); 521 } 522 } 523 return toVisitAst; 524 } 525 526 /** 527 * Returns true if method has to be checked or false. 528 * @param ast method def to check. 529 * @return true if we should check this method. 530 */ 531 private boolean isCheckedMethod(DetailAST ast) { 532 final String methodName = 533 ast.findFirstToken(TokenTypes.IDENT).getText(); 534 return !ignoredMethodNames.contains(methodName); 535 } 536 537 /** 538 * Set the list of illegal variable types. 539 * @param classNames array of illegal variable types 540 * @noinspection WeakerAccess 541 */ 542 public void setIllegalClassNames(String... classNames) { 543 illegalClassNames.clear(); 544 Collections.addAll(illegalClassNames, classNames); 545 } 546 547 /** 548 * Set the list of ignore method names. 549 * @param methodNames array of ignored method names 550 * @noinspection WeakerAccess 551 */ 552 public void setIgnoredMethodNames(String... methodNames) { 553 ignoredMethodNames.clear(); 554 Collections.addAll(ignoredMethodNames, methodNames); 555 } 556 557 /** 558 * Set the list of legal abstract class names. 559 * @param classNames array of legal abstract class names 560 * @noinspection WeakerAccess 561 */ 562 public void setLegalAbstractClassNames(String... classNames) { 563 Collections.addAll(legalAbstractClassNames, classNames); 564 } 565 566 /** 567 * Set the list of member modifiers (of methods and fields) which should be checked. 568 * @param modifiers String contains modifiers. 569 */ 570 public void setMemberModifiers(String modifiers) { 571 final List<Integer> modifiersList = new ArrayList<>(); 572 for (String modifier : modifiers.split(",")) { 573 modifiersList.add(TokenUtil.getTokenId(modifier.trim())); 574 } 575 memberModifiers = modifiersList; 576 } 577 578}