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