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