001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.Collections; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Set; 026import java.util.regex.Pattern; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.PropertyType; 032import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 033import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 034import com.puppycrawl.tools.checkstyle.api.DetailAST; 035import com.puppycrawl.tools.checkstyle.api.FullIdent; 036import com.puppycrawl.tools.checkstyle.api.TokenTypes; 037import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 038import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 039 040/** 041 * <p> 042 * Checks that particular classes or interfaces are never used. 043 * </p> 044 * <p> 045 * Rationale: Helps reduce coupling on concrete classes. 046 * </p> 047 * <p> 048 * For additional restriction of type usage see also: 049 * <a href="https://checkstyle.org/config_coding.html#IllegalInstantiation"> 050 * IllegalInstantiation</a>, 051 * <a href="https://checkstyle.org/config_imports.html#IllegalImport">IllegalImport</a> 052 * </p> 053 * <p> 054 * It is possible to set illegal class names via short or 055 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7">canonical</a> 056 * name. Specifying illegal type invokes analyzing imports and Check puts violations at 057 * corresponding declarations (of variables, methods or parameters). 058 * This helps to avoid ambiguous cases, e.g.: {@code java.awt.List} was set as 059 * illegal class name, then, code like: 060 * </p> 061 * <pre> 062 * import java.util.List; 063 * ... 064 * List list; //No violation here 065 * </pre> 066 * <p> 067 * will be ok. 068 * </p> 069 * <p> 070 * In most cases it's justified to put following classes to <b>illegalClassNames</b>: 071 * </p> 072 * <ul> 073 * <li>GregorianCalendar</li> 074 * <li>Hashtable</li> 075 * <li>ArrayList</li> 076 * <li>LinkedList</li> 077 * <li>Vector</li> 078 * </ul> 079 * <p> 080 * as methods that are differ from interface methods are rarely used, so in most cases user will 081 * benefit from checking for them. 082 * </p> 083 * <ul> 084 * <li> 085 * Property {@code validateAbstractClassNames} - Control whether to validate abstract class names. 086 * Type is {@code boolean}. 087 * Default value is {@code false}. 088 * </li> 089 * <li> 090 * Property {@code illegalClassNames} - Specify classes that should not be used 091 * as types in variable declarations, return values or parameters. 092 * Type is {@code java.lang.String[]}. 093 * Default value is {@code HashMap, HashSet, LinkedHashMap, LinkedHashSet, TreeMap, 094 * TreeSet, java.util.HashMap, java.util.HashSet, java.util.LinkedHashMap, 095 * java.util.LinkedHashSet, java.util.TreeMap, java.util.TreeSet}. 096 * </li> 097 * <li> 098 * Property {@code legalAbstractClassNames} - Define abstract classes that may be used as types. 099 * Type is {@code java.lang.String[]}. 100 * Default value is {@code ""}. 101 * </li> 102 * <li> 103 * Property {@code ignoredMethodNames} - Specify methods that should not be checked. 104 * Type is {@code java.lang.String[]}. 105 * Default value is {@code getEnvironment, getInitialContext}. 106 * </li> 107 * <li> 108 * Property {@code illegalAbstractClassNameFormat} - Specify RegExp for illegal abstract class 109 * names. 110 * Type is {@code java.util.regex.Pattern}. 111 * Default value is {@code "^(.*[.])?Abstract.*$"}. 112 * </li> 113 * <li> 114 * Property {@code memberModifiers} - Control whether to check only methods and fields with any 115 * of the specified modifiers. 116 * This property does not affect method calls nor method references nor record components. 117 * Type is {@code java.lang.String[]}. 118 * Validation type is {@code tokenTypesSet}. 119 * Default value is {@code ""}. 120 * </li> 121 * <li> 122 * Property {@code tokens} - tokens to check 123 * Type is {@code java.lang.String[]}. 124 * Validation type is {@code tokenSet}. 125 * Default value is: 126 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 127 * ANNOTATION_FIELD_DEF</a>, 128 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 129 * CLASS_DEF</a>, 130 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 131 * INTERFACE_DEF</a>, 132 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 133 * METHOD_CALL</a>, 134 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 135 * METHOD_DEF</a>, 136 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_REF"> 137 * METHOD_REF</a>, 138 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF"> 139 * PARAMETER_DEF</a>, 140 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 141 * VARIABLE_DEF</a>, 142 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PATTERN_VARIABLE_DEF"> 143 * PATTERN_VARIABLE_DEF</a>, 144 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 145 * RECORD_DEF</a>, 146 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_COMPONENT_DEF"> 147 * RECORD_COMPONENT_DEF</a>. 148 * </li> 149 * </ul> 150 * <p> 151 * To configure the default check: 152 * </p> 153 * <pre> 154 * <module name="IllegalType"/> 155 * </pre> 156 * <pre> 157 * public class Test extends TreeSet { // violation 158 * public <T extends java.util.HashSet> void method() { // violation 159 * 160 * LinkedHashMap<Integer, String> lhmap = 161 * new LinkedHashMap<Integer, String>(); // violation 162 * TreeMap<Integer, String> treemap = 163 * new TreeMap<Integer, String>(); // violation 164 * Test t; // OK 165 * HashMap<String, String> hmap; // violation 166 * Queue<Integer> intqueue; // OK 167 * 168 * java.lang.IllegalArgumentException illegalex; // OK 169 * java.util.TreeSet treeset; // violation 170 * } 171 * 172 * } 173 * </pre> 174 * <p> 175 * To configure the Check so that particular tokens are checked: 176 * </p> 177 * <pre> 178 * <module name="IllegalType"> 179 * <property name="tokens" value="METHOD_DEF"/> 180 * </module> 181 * </pre> 182 * <pre> 183 * public class Test extends TreeSet { // OK 184 * public <T extends java.util.HashSet> void method() { // violation 185 * LinkedHashMap<Integer, String> lhmap = 186 * new LinkedHashMap<Integer, String>(); // OK 187 * 188 * java.lang.IllegalArgumentException illegalex; // OK 189 * java.util.TreeSet treeset; // Ok 190 * } 191 * 192 * public <T extends java.util.HashSet> void typeParam(T t) {} // violation 193 * 194 * public void fullName(TreeSet a) {} // OK 195 * 196 * } 197 * </pre> 198 * <p> 199 * To configure the Check so that it ignores function() methods: 200 * </p> 201 * <pre> 202 * <module name="IllegalType"> 203 * <property name="ignoredMethodNames" value="function"/> 204 * </module> 205 * </pre> 206 * <pre> 207 * public class Test { 208 * public HashMap<String, String> function() { // OK 209 * // code 210 * } 211 * 212 * public HashMap<String, String> function1() { // violation 213 * // code 214 * } 215 * } 216 * </pre> 217 * <p> 218 * To configure the Check so that it validates abstract class names: 219 * </p> 220 * <pre> 221 * <module name="IllegalType"> 222 * <property name="validateAbstractClassNames" value="true"/> 223 * <property name="illegalAbstractClassNameFormat" value="Gitt"/> 224 * </module> 225 * </pre> 226 * <pre> 227 * class Test extends Gitter { // violation 228 * } 229 * 230 * class Test1 extends Github { // OK 231 * } 232 * </pre> 233 * <p> 234 * To configure the Check so that it verifies only public, protected or static methods and fields: 235 * </p> 236 * <pre> 237 * <module name="IllegalType"> 238 * <property name="memberModifiers" value="LITERAL_PUBLIC, 239 * LITERAL_PROTECTED, LITERAL_STATIC"/> 240 * </module> 241 * </pre> 242 * <pre> 243 * public class Test { 244 * public HashMap<String, String> function1() { // violation 245 * // code 246 * } 247 * 248 * private HashMap<String, String> function2() { // OK 249 * // code 250 * } 251 * 252 * protected HashMap<Integer, String> function3() { // violation 253 * // code 254 * } 255 * 256 * public static TreeMap<Integer, String> function4() { // violation 257 * // code 258 * } 259 * 260 * } 261 * </pre> 262 * <p> 263 * To configure the check so that it verifies usage of types Boolean and Foo: 264 * </p> 265 * <pre> 266 * <module name="IllegalType"> 267 * <property name="illegalClassNames" value="Boolean, Foo"/> 268 * </module> 269 * </pre> 270 * <pre> 271 * public class Test { 272 * 273 * public Set<Boolean> set; // violation 274 * public java.util.List<Map<Boolean, Foo>> list; // violation 275 * 276 * private void method(List<Foo> list, Boolean value) { // violation 277 * SomeType.<Boolean>foo(); // violation 278 * final Consumer<Foo> consumer = Foo<Boolean>::foo; // violation 279 * } 280 * 281 * public <T extends Boolean, U extends Serializable> void typeParam(T a) {} // violation 282 * 283 * public void fullName(java.util.ArrayList<? super Boolean> a) {} // violation 284 * 285 * public abstract Set<Boolean> shortName(Set<? super Boolean> a); // violation 286 * 287 * public Set<? extends Foo> typeArgument() { // violation 288 * return new TreeSet<Foo<Boolean>>(); 289 * } 290 * 291 * } 292 * </pre> 293 * <p> 294 * To configure the check to target fields types only: 295 * </p> 296 * <pre> 297 * <module name="IllegalType"> 298 * <property name="illegalClassNames" value="java.util.Optional"/> 299 * <property name="tokens" value="VARIABLE_DEF"/> 300 * <property name="id" value="IllegalTypeOptionalAsField"/> 301 * </module> 302 * <module name="SuppressionXpathSingleFilter"> 303 * <property name="query" value="//METHOD_DEF//*"/> 304 * <property name="id" value="IllegalTypeOptionalAsField"/> 305 * </module> 306 * </pre> 307 * <pre> 308 * import java.util.Optional; 309 * 310 * public class Main { 311 * 312 * static int field1 = 4; // OK 313 * public Optional<String> field2; // violation, usage of type 'Optional' is not allowed 314 * protected String field3; // OK 315 * Optional<String> field4; // violation, usage of type 'Optional' is not allowed 316 * private Optional<String> field5; // violation, usage of type 'Optional' is not allowed 317 * 318 * void foo() { 319 * Optional<String> i; // OK 320 * } 321 * public <T extends java.util.Optional> void method(T t) { // OK 322 * Optional<T> i; // OK 323 * } 324 * } 325 * </pre> 326 * <p> 327 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 328 * </p> 329 * <p> 330 * Violation Message Keys: 331 * </p> 332 * <ul> 333 * <li> 334 * {@code illegal.type} 335 * </li> 336 * </ul> 337 * 338 * @since 3.2 339 * 340 */ 341@FileStatefulCheck 342public final class IllegalTypeCheck extends AbstractCheck { 343 344 /** 345 * A key is pointing to the warning message text in "messages.properties" 346 * file. 347 */ 348 public static final String MSG_KEY = "illegal.type"; 349 350 /** Types illegal by default. */ 351 private static final String[] DEFAULT_ILLEGAL_TYPES = { 352 "HashSet", 353 "HashMap", 354 "LinkedHashMap", 355 "LinkedHashSet", 356 "TreeSet", 357 "TreeMap", 358 "java.util.HashSet", 359 "java.util.HashMap", 360 "java.util.LinkedHashMap", 361 "java.util.LinkedHashSet", 362 "java.util.TreeSet", 363 "java.util.TreeMap", 364 }; 365 366 /** Default ignored method names. */ 367 private static final String[] DEFAULT_IGNORED_METHOD_NAMES = { 368 "getInitialContext", 369 "getEnvironment", 370 }; 371 372 /** 373 * Specify classes that should not be used as types in variable declarations, 374 * return values or parameters. 375 */ 376 private final Set<String> illegalClassNames = new HashSet<>(); 377 /** Illegal short classes. */ 378 private final Set<String> illegalShortClassNames = new HashSet<>(); 379 /** Define abstract classes that may be used as types. */ 380 private final Set<String> legalAbstractClassNames = new HashSet<>(); 381 /** Specify methods that should not be checked. */ 382 private final Set<String> ignoredMethodNames = new HashSet<>(); 383 /** 384 * Control whether to check only methods and fields with any of the specified modifiers. 385 * This property does not affect method calls nor method references nor record components. 386 */ 387 @XdocsPropertyType(PropertyType.TOKEN_ARRAY) 388 private List<Integer> memberModifiers = Collections.emptyList(); 389 390 /** Specify RegExp for illegal abstract class names. */ 391 private Pattern illegalAbstractClassNameFormat = Pattern.compile("^(.*[.])?Abstract.*$"); 392 393 /** 394 * Control whether to validate abstract class names. 395 */ 396 private boolean validateAbstractClassNames; 397 398 /** Creates new instance of the check. */ 399 public IllegalTypeCheck() { 400 setIllegalClassNames(DEFAULT_ILLEGAL_TYPES); 401 setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES); 402 } 403 404 /** 405 * Setter to specify RegExp for illegal abstract class names. 406 * 407 * @param pattern a pattern. 408 */ 409 public void setIllegalAbstractClassNameFormat(Pattern pattern) { 410 illegalAbstractClassNameFormat = pattern; 411 } 412 413 /** 414 * Setter to control whether to validate abstract class names. 415 * 416 * @param validateAbstractClassNames whether abstract class names must be ignored. 417 */ 418 public void setValidateAbstractClassNames(boolean validateAbstractClassNames) { 419 this.validateAbstractClassNames = validateAbstractClassNames; 420 } 421 422 @Override 423 public int[] getDefaultTokens() { 424 return getAcceptableTokens(); 425 } 426 427 @Override 428 public int[] getAcceptableTokens() { 429 return new int[] { 430 TokenTypes.ANNOTATION_FIELD_DEF, 431 TokenTypes.CLASS_DEF, 432 TokenTypes.IMPORT, 433 TokenTypes.INTERFACE_DEF, 434 TokenTypes.METHOD_CALL, 435 TokenTypes.METHOD_DEF, 436 TokenTypes.METHOD_REF, 437 TokenTypes.PARAMETER_DEF, 438 TokenTypes.VARIABLE_DEF, 439 TokenTypes.PATTERN_VARIABLE_DEF, 440 TokenTypes.RECORD_DEF, 441 TokenTypes.RECORD_COMPONENT_DEF, 442 }; 443 } 444 445 @Override 446 public void beginTree(DetailAST rootAST) { 447 illegalShortClassNames.clear(); 448 illegalShortClassNames.addAll(illegalClassNames); 449 } 450 451 @Override 452 public int[] getRequiredTokens() { 453 return new int[] {TokenTypes.IMPORT}; 454 } 455 456 @Override 457 public void visitToken(DetailAST ast) { 458 switch (ast.getType()) { 459 case TokenTypes.CLASS_DEF: 460 case TokenTypes.INTERFACE_DEF: 461 case TokenTypes.RECORD_DEF: 462 visitTypeDef(ast); 463 break; 464 case TokenTypes.METHOD_CALL: 465 case TokenTypes.METHOD_REF: 466 visitMethodCallOrRef(ast); 467 break; 468 case TokenTypes.METHOD_DEF: 469 visitMethodDef(ast); 470 break; 471 case TokenTypes.VARIABLE_DEF: 472 case TokenTypes.ANNOTATION_FIELD_DEF: 473 case TokenTypes.PATTERN_VARIABLE_DEF: 474 visitVariableDef(ast); 475 break; 476 case TokenTypes.RECORD_COMPONENT_DEF: 477 checkClassName(ast); 478 break; 479 case TokenTypes.PARAMETER_DEF: 480 visitParameterDef(ast); 481 break; 482 case TokenTypes.IMPORT: 483 visitImport(ast); 484 break; 485 default: 486 throw new IllegalStateException(ast.toString()); 487 } 488 } 489 490 /** 491 * Checks if current method's return type or variable's type is verifiable 492 * according to <b>memberModifiers</b> option. 493 * 494 * @param methodOrVariableDef METHOD_DEF or VARIABLE_DEF ast node. 495 * @return true if member is verifiable according to <b>memberModifiers</b> option. 496 */ 497 private boolean isVerifiable(DetailAST methodOrVariableDef) { 498 boolean result = true; 499 if (!memberModifiers.isEmpty()) { 500 final DetailAST modifiersAst = methodOrVariableDef 501 .findFirstToken(TokenTypes.MODIFIERS); 502 result = isContainVerifiableType(modifiersAst); 503 } 504 return result; 505 } 506 507 /** 508 * Checks is modifiers contain verifiable type. 509 * 510 * @param modifiers 511 * parent node for all modifiers 512 * @return true if method or variable can be verified 513 */ 514 private boolean isContainVerifiableType(DetailAST modifiers) { 515 boolean result = false; 516 for (DetailAST modifier = modifiers.getFirstChild(); modifier != null; 517 modifier = modifier.getNextSibling()) { 518 if (memberModifiers.contains(modifier.getType())) { 519 result = true; 520 break; 521 } 522 } 523 return result; 524 } 525 526 /** 527 * Checks the super type and implemented interfaces of a given type. 528 * 529 * @param typeDef class or interface for check. 530 */ 531 private void visitTypeDef(DetailAST typeDef) { 532 if (isVerifiable(typeDef)) { 533 checkTypeParameters(typeDef); 534 final DetailAST extendsClause = typeDef.findFirstToken(TokenTypes.EXTENDS_CLAUSE); 535 if (extendsClause != null) { 536 checkBaseTypes(extendsClause); 537 } 538 final DetailAST implementsClause = typeDef.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE); 539 if (implementsClause != null) { 540 checkBaseTypes(implementsClause); 541 } 542 } 543 } 544 545 /** 546 * Checks return type of a given method. 547 * 548 * @param methodDef method for check. 549 */ 550 private void visitMethodDef(DetailAST methodDef) { 551 if (isCheckedMethod(methodDef)) { 552 checkClassName(methodDef); 553 } 554 } 555 556 /** 557 * Checks type of parameters. 558 * 559 * @param parameterDef parameter list for check. 560 */ 561 private void visitParameterDef(DetailAST parameterDef) { 562 final DetailAST grandParentAST = parameterDef.getParent().getParent(); 563 564 if (grandParentAST.getType() == TokenTypes.METHOD_DEF && isCheckedMethod(grandParentAST)) { 565 checkClassName(parameterDef); 566 } 567 } 568 569 /** 570 * Checks type of given variable. 571 * 572 * @param variableDef variable to check. 573 */ 574 private void visitVariableDef(DetailAST variableDef) { 575 if (isVerifiable(variableDef)) { 576 checkClassName(variableDef); 577 } 578 } 579 580 /** 581 * Checks the type arguments of given method call/reference. 582 * 583 * @param methodCallOrRef method call/reference to check. 584 */ 585 private void visitMethodCallOrRef(DetailAST methodCallOrRef) { 586 checkTypeArguments(methodCallOrRef); 587 } 588 589 /** 590 * Checks imported type (as static and star imports are not supported by Check, 591 * only type is in the consideration).<br> 592 * If this type is illegal due to Check's options - puts violation on it. 593 * 594 * @param importAst {@link TokenTypes#IMPORT Import} 595 */ 596 private void visitImport(DetailAST importAst) { 597 if (!isStarImport(importAst)) { 598 final String canonicalName = getImportedTypeCanonicalName(importAst); 599 extendIllegalClassNamesWithShortName(canonicalName); 600 } 601 } 602 603 /** 604 * Checks if current import is star import. E.g.: 605 * <p> 606 * {@code 607 * import java.util.*; 608 * } 609 * </p> 610 * 611 * @param importAst {@link TokenTypes#IMPORT Import} 612 * @return true if it is star import 613 */ 614 private static boolean isStarImport(DetailAST importAst) { 615 boolean result = false; 616 DetailAST toVisit = importAst; 617 while (toVisit != null) { 618 toVisit = getNextSubTreeNode(toVisit, importAst); 619 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 620 result = true; 621 break; 622 } 623 } 624 return result; 625 } 626 627 /** 628 * Checks type and type arguments/parameters of given method, parameter, variable or 629 * method call/reference. 630 * 631 * @param ast node to check. 632 */ 633 private void checkClassName(DetailAST ast) { 634 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 635 checkType(type); 636 checkTypeParameters(ast); 637 } 638 639 /** 640 * Checks the identifier of the given type. 641 * 642 * @param type node to check. 643 */ 644 private void checkIdent(DetailAST type) { 645 final FullIdent ident = FullIdent.createFullIdent(type); 646 if (isMatchingClassName(ident.getText())) { 647 log(ident.getDetailAst(), MSG_KEY, ident.getText()); 648 } 649 } 650 651 /** 652 * Checks the {@code extends} or {@code implements} statement. 653 * 654 * @param clause DetailAST for either {@link TokenTypes#EXTENDS_CLAUSE} or 655 * {@link TokenTypes#IMPLEMENTS_CLAUSE} 656 */ 657 private void checkBaseTypes(DetailAST clause) { 658 DetailAST child = clause.getFirstChild(); 659 while (child != null) { 660 if (child.getType() == TokenTypes.IDENT) { 661 checkIdent(child); 662 } 663 else { 664 TokenUtil.forEachChild(child, TokenTypes.TYPE_ARGUMENT, this::checkType); 665 } 666 child = child.getNextSibling(); 667 } 668 } 669 670 /** 671 * Checks the given type, its arguments and parameters. 672 * 673 * @param type node to check. 674 */ 675 private void checkType(DetailAST type) { 676 checkIdent(type.getFirstChild()); 677 checkTypeArguments(type); 678 checkTypeBounds(type); 679 } 680 681 /** 682 * Checks the upper and lower bounds for the given type. 683 * 684 * @param type node to check. 685 */ 686 private void checkTypeBounds(DetailAST type) { 687 final DetailAST upperBounds = type.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS); 688 if (upperBounds != null) { 689 checkType(upperBounds); 690 } 691 final DetailAST lowerBounds = type.findFirstToken(TokenTypes.TYPE_LOWER_BOUNDS); 692 if (lowerBounds != null) { 693 checkType(lowerBounds); 694 } 695 } 696 697 /** 698 * Checks the type parameters of the node. 699 * 700 * @param node node to check. 701 */ 702 private void checkTypeParameters(final DetailAST node) { 703 final DetailAST typeParameters = node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 704 if (typeParameters != null) { 705 TokenUtil.forEachChild(typeParameters, TokenTypes.TYPE_PARAMETER, this::checkType); 706 } 707 } 708 709 /** 710 * Checks the type arguments of the node. 711 * 712 * @param node node to check. 713 */ 714 private void checkTypeArguments(final DetailAST node) { 715 DetailAST typeArguments = node.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 716 if (typeArguments == null) { 717 typeArguments = node.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 718 } 719 720 if (typeArguments != null) { 721 TokenUtil.forEachChild(typeArguments, TokenTypes.TYPE_ARGUMENT, this::checkType); 722 } 723 } 724 725 /** 726 * Returns true if given class name is one of illegal classes or else false. 727 * 728 * @param className class name to check. 729 * @return true if given class name is one of illegal classes 730 * or if it matches to abstract class names pattern. 731 */ 732 private boolean isMatchingClassName(String className) { 733 final String shortName = className.substring(className.lastIndexOf('.') + 1); 734 return illegalClassNames.contains(className) 735 || illegalShortClassNames.contains(shortName) 736 || validateAbstractClassNames 737 && !legalAbstractClassNames.contains(className) 738 && illegalAbstractClassNameFormat.matcher(className).find(); 739 } 740 741 /** 742 * Extends illegal class names set via imported short type name. 743 * 744 * @param canonicalName 745 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 746 * Canonical</a> name of imported type. 747 */ 748 private void extendIllegalClassNamesWithShortName(String canonicalName) { 749 if (illegalClassNames.contains(canonicalName)) { 750 final String shortName = canonicalName 751 .substring(canonicalName.lastIndexOf('.') + 1); 752 illegalShortClassNames.add(shortName); 753 } 754 } 755 756 /** 757 * Gets imported type's 758 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 759 * canonical name</a>. 760 * 761 * @param importAst {@link TokenTypes#IMPORT Import} 762 * @return Imported canonical type's name. 763 */ 764 private static String getImportedTypeCanonicalName(DetailAST importAst) { 765 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 766 DetailAST toVisit = importAst; 767 while (toVisit != null) { 768 toVisit = getNextSubTreeNode(toVisit, importAst); 769 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 770 if (canonicalNameBuilder.length() > 0) { 771 canonicalNameBuilder.append('.'); 772 } 773 canonicalNameBuilder.append(toVisit.getText()); 774 } 775 } 776 return canonicalNameBuilder.toString(); 777 } 778 779 /** 780 * Gets the next node of a syntactical tree (child of a current node or 781 * sibling of a current node, or sibling of a parent of a current node). 782 * 783 * @param currentNodeAst Current node in considering 784 * @param subTreeRootAst SubTree root 785 * @return Current node after bypassing, if current node reached the root of a subtree 786 * method returns null 787 */ 788 private static DetailAST 789 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 790 DetailAST currentNode = currentNodeAst; 791 DetailAST toVisitAst = currentNode.getFirstChild(); 792 while (toVisitAst == null) { 793 toVisitAst = currentNode.getNextSibling(); 794 if (currentNode.getParent().equals(subTreeRootAst)) { 795 break; 796 } 797 currentNode = currentNode.getParent(); 798 } 799 return toVisitAst; 800 } 801 802 /** 803 * Returns true if method has to be checked or false. 804 * 805 * @param ast method def to check. 806 * @return true if we should check this method. 807 */ 808 private boolean isCheckedMethod(DetailAST ast) { 809 final String methodName = 810 ast.findFirstToken(TokenTypes.IDENT).getText(); 811 return isVerifiable(ast) && !ignoredMethodNames.contains(methodName) 812 && !AnnotationUtil.containsAnnotation(ast, "Override"); 813 } 814 815 /** 816 * Setter to specify classes that should not be used as types in variable declarations, 817 * return values or parameters. 818 * 819 * @param classNames array of illegal variable types 820 * @noinspection WeakerAccess 821 */ 822 public void setIllegalClassNames(String... classNames) { 823 illegalClassNames.clear(); 824 Collections.addAll(illegalClassNames, classNames); 825 } 826 827 /** 828 * Setter to specify methods that should not be checked. 829 * 830 * @param methodNames array of ignored method names 831 * @noinspection WeakerAccess 832 */ 833 public void setIgnoredMethodNames(String... methodNames) { 834 ignoredMethodNames.clear(); 835 Collections.addAll(ignoredMethodNames, methodNames); 836 } 837 838 /** 839 * Setter to define abstract classes that may be used as types. 840 * 841 * @param classNames array of legal abstract class names 842 * @noinspection WeakerAccess 843 */ 844 public void setLegalAbstractClassNames(String... classNames) { 845 Collections.addAll(legalAbstractClassNames, classNames); 846 } 847 848 /** 849 * Setter to control whether to check only methods and fields with any of 850 * the specified modifiers. 851 * This property does not affect method calls nor method references nor record components. 852 * 853 * @param modifiers String contains modifiers. 854 */ 855 public void setMemberModifiers(String modifiers) { 856 memberModifiers = Stream.of(modifiers.split(",")) 857 .map(String::trim) 858 .filter(token -> !token.isEmpty()) 859 .map(TokenUtil::getTokenId) 860 .collect(Collectors.toList()); 861 } 862 863}