View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.BitSet;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.Set;
26  import java.util.regex.Pattern;
27  
28  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
29  import com.puppycrawl.tools.checkstyle.PropertyType;
30  import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
31  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32  import com.puppycrawl.tools.checkstyle.api.DetailAST;
33  import com.puppycrawl.tools.checkstyle.api.FullIdent;
34  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
36  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
37  
38  /**
39   * <p>
40   * Checks that particular classes or interfaces are never used.
41   * </p>
42   * <p>
43   * Rationale: Helps reduce coupling on concrete classes.
44   * </p>
45   * <p>
46   * For additional restriction of type usage see also:
47   * <a href="https://checkstyle.org/checks/coding/illegalinstantiation.html#IllegalInstantiation">
48   * IllegalInstantiation</a>,
49   * <a href="https://checkstyle.org/checks/imports/illegalimport.html#IllegalImport">
50   * IllegalImport</a>
51   * </p>
52   * <p>
53   * It is possible to set illegal class names via short or
54   * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7">canonical</a>
55   * name. Specifying illegal type invokes analyzing imports and Check puts violations at
56   * corresponding declarations (of variables, methods or parameters).
57   * This helps to avoid ambiguous cases, e.g.: {@code java.awt.List} was set as
58   * illegal class name, then, code like:
59   * </p>
60   * <pre>
61   * import java.util.List;
62   * ...
63   * List list; //No violation here
64   * </pre>
65   * <p>
66   * will be ok.
67   * </p>
68   * <p>
69   * In most cases it's justified to put following classes to <b>illegalClassNames</b>:
70   * </p>
71   * <ul>
72   * <li>GregorianCalendar</li>
73   * <li>Hashtable</li>
74   * <li>ArrayList</li>
75   * <li>LinkedList</li>
76   * <li>Vector</li>
77   * </ul>
78   * <p>
79   * as methods that are differ from interface methods are rarely used, so in most cases user will
80   * benefit from checking for them.
81   * </p>
82   * <ul>
83   * <li>
84   * Property {@code ignoredMethodNames} - Specify methods that should not be checked.
85   * Type is {@code java.lang.String[]}.
86   * Default value is {@code getEnvironment, getInitialContext}.
87   * </li>
88   * <li>
89   * Property {@code illegalAbstractClassNameFormat} - Specify RegExp for illegal abstract class
90   * names.
91   * Type is {@code java.util.regex.Pattern}.
92   * Default value is {@code "^(.*[.])?Abstract.*$"}.
93   * </li>
94   * <li>
95   * Property {@code illegalClassNames} - Specify classes that should not be used
96   * as types in variable declarations, return values or parameters.
97   * Type is {@code java.lang.String[]}.
98   * Default value is {@code HashMap, HashSet, LinkedHashMap, LinkedHashSet, TreeMap,
99   * TreeSet, java.util.HashMap, java.util.HashSet, java.util.LinkedHashMap,
100  * java.util.LinkedHashSet, java.util.TreeMap, java.util.TreeSet}.
101  * </li>
102  * <li>
103  * Property {@code legalAbstractClassNames} - Define abstract classes that may be used as types.
104  * Type is {@code java.lang.String[]}.
105  * Default value is {@code ""}.
106  * </li>
107  * <li>
108  * Property {@code memberModifiers} - Control whether to check only methods and fields with any
109  * of the specified modifiers.
110  * This property does not affect method calls nor method references nor record components.
111  * Type is {@code java.lang.String[]}.
112  * Validation type is {@code tokenTypesSet}.
113  * Default value is {@code ""}.
114  * </li>
115  * <li>
116  * Property {@code validateAbstractClassNames} - Control whether to validate abstract class names.
117  * Type is {@code boolean}.
118  * Default value is {@code false}.
119  * </li>
120  * <li>
121  * Property {@code tokens} - tokens to check
122  * Type is {@code java.lang.String[]}.
123  * Validation type is {@code tokenSet}.
124  * Default value is:
125  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
126  * ANNOTATION_FIELD_DEF</a>,
127  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
128  * CLASS_DEF</a>,
129  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
130  * INTERFACE_DEF</a>,
131  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
132  * METHOD_CALL</a>,
133  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
134  * METHOD_DEF</a>,
135  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_REF">
136  * METHOD_REF</a>,
137  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF">
138  * PARAMETER_DEF</a>,
139  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF">
140  * VARIABLE_DEF</a>,
141  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PATTERN_VARIABLE_DEF">
142  * PATTERN_VARIABLE_DEF</a>,
143  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
144  * RECORD_DEF</a>,
145  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_COMPONENT_DEF">
146  * RECORD_COMPONENT_DEF</a>,
147  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_PATTERN_DEF">
148  * RECORD_PATTERN_DEF</a>.
149  * </li>
150  * </ul>
151  * <p>
152  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
153  * </p>
154  * <p>
155  * Violation Message Keys:
156  * </p>
157  * <ul>
158  * <li>
159  * {@code illegal.type}
160  * </li>
161  * </ul>
162  *
163  * @since 3.2
164  *
165  */
166 @FileStatefulCheck
167 public final class IllegalTypeCheck extends AbstractCheck {
168 
169     /**
170      * A key is pointing to the warning message text in "messages.properties"
171      * file.
172      */
173     public static final String MSG_KEY = "illegal.type";
174 
175     /** Types illegal by default. */
176     private static final String[] DEFAULT_ILLEGAL_TYPES = {
177         "HashSet",
178         "HashMap",
179         "LinkedHashMap",
180         "LinkedHashSet",
181         "TreeSet",
182         "TreeMap",
183         "java.util.HashSet",
184         "java.util.HashMap",
185         "java.util.LinkedHashMap",
186         "java.util.LinkedHashSet",
187         "java.util.TreeSet",
188         "java.util.TreeMap",
189     };
190 
191     /** Default ignored method names. */
192     private static final String[] DEFAULT_IGNORED_METHOD_NAMES = {
193         "getInitialContext",
194         "getEnvironment",
195     };
196 
197     /**
198      * Specify classes that should not be used as types in variable declarations,
199      * return values or parameters.
200      */
201     private final Set<String> illegalClassNames = new HashSet<>();
202     /** Illegal short classes. */
203     private final Set<String> illegalShortClassNames = new HashSet<>();
204     /** Define abstract classes that may be used as types. */
205     private final Set<String> legalAbstractClassNames = new HashSet<>();
206     /** Specify methods that should not be checked. */
207     private final Set<String> ignoredMethodNames = new HashSet<>();
208     /**
209      * Control whether to check only methods and fields with any of the specified modifiers.
210      * This property does not affect method calls nor method references nor record components.
211      */
212     @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
213     private BitSet memberModifiers = new BitSet();
214 
215     /** Specify RegExp for illegal abstract class names. */
216     private Pattern illegalAbstractClassNameFormat = Pattern.compile("^(.*[.])?Abstract.*$");
217 
218     /**
219      * Control whether to validate abstract class names.
220      */
221     private boolean validateAbstractClassNames;
222 
223     /** Creates new instance of the check. */
224     public IllegalTypeCheck() {
225         setIllegalClassNames(DEFAULT_ILLEGAL_TYPES);
226         setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES);
227     }
228 
229     /**
230      * Setter to specify RegExp for illegal abstract class names.
231      *
232      * @param pattern a pattern.
233      * @since 3.2
234      */
235     public void setIllegalAbstractClassNameFormat(Pattern pattern) {
236         illegalAbstractClassNameFormat = pattern;
237     }
238 
239     /**
240      * Setter to control whether to validate abstract class names.
241      *
242      * @param validateAbstractClassNames whether abstract class names must be ignored.
243      * @since 6.10
244      */
245     public void setValidateAbstractClassNames(boolean validateAbstractClassNames) {
246         this.validateAbstractClassNames = validateAbstractClassNames;
247     }
248 
249     @Override
250     public int[] getDefaultTokens() {
251         return getAcceptableTokens();
252     }
253 
254     @Override
255     public int[] getAcceptableTokens() {
256         return new int[] {
257             TokenTypes.ANNOTATION_FIELD_DEF,
258             TokenTypes.CLASS_DEF,
259             TokenTypes.IMPORT,
260             TokenTypes.INTERFACE_DEF,
261             TokenTypes.METHOD_CALL,
262             TokenTypes.METHOD_DEF,
263             TokenTypes.METHOD_REF,
264             TokenTypes.PARAMETER_DEF,
265             TokenTypes.VARIABLE_DEF,
266             TokenTypes.PATTERN_VARIABLE_DEF,
267             TokenTypes.RECORD_DEF,
268             TokenTypes.RECORD_COMPONENT_DEF,
269             TokenTypes.RECORD_PATTERN_DEF,
270         };
271     }
272 
273     @Override
274     public void beginTree(DetailAST rootAST) {
275         illegalShortClassNames.clear();
276         illegalShortClassNames.addAll(illegalClassNames);
277     }
278 
279     @Override
280     public int[] getRequiredTokens() {
281         return new int[] {TokenTypes.IMPORT};
282     }
283 
284     @Override
285     public void visitToken(DetailAST ast) {
286         switch (ast.getType()) {
287             case TokenTypes.CLASS_DEF:
288             case TokenTypes.INTERFACE_DEF:
289             case TokenTypes.RECORD_DEF:
290                 visitTypeDef(ast);
291                 break;
292             case TokenTypes.METHOD_CALL:
293             case TokenTypes.METHOD_REF:
294                 visitMethodCallOrRef(ast);
295                 break;
296             case TokenTypes.METHOD_DEF:
297                 visitMethodDef(ast);
298                 break;
299             case TokenTypes.VARIABLE_DEF:
300             case TokenTypes.ANNOTATION_FIELD_DEF:
301             case TokenTypes.PATTERN_VARIABLE_DEF:
302                 visitVariableDef(ast);
303                 break;
304             case TokenTypes.RECORD_COMPONENT_DEF:
305             case TokenTypes.RECORD_PATTERN_DEF:
306                 checkClassName(ast);
307                 break;
308             case TokenTypes.PARAMETER_DEF:
309                 visitParameterDef(ast);
310                 break;
311             case TokenTypes.IMPORT:
312                 visitImport(ast);
313                 break;
314             default:
315                 throw new IllegalStateException(ast.toString());
316         }
317     }
318 
319     /**
320      * Checks if current method's return type or variable's type is verifiable
321      * according to <b>memberModifiers</b> option.
322      *
323      * @param methodOrVariableDef METHOD_DEF or VARIABLE_DEF ast node.
324      * @return true if member is verifiable according to <b>memberModifiers</b> option.
325      */
326     private boolean isVerifiable(DetailAST methodOrVariableDef) {
327         boolean result = true;
328         if (!memberModifiers.isEmpty()) {
329             final DetailAST modifiersAst = methodOrVariableDef
330                     .findFirstToken(TokenTypes.MODIFIERS);
331             result = isContainVerifiableType(modifiersAst);
332         }
333         return result;
334     }
335 
336     /**
337      * Checks is modifiers contain verifiable type.
338      *
339      * @param modifiers
340      *            parent node for all modifiers
341      * @return true if method or variable can be verified
342      */
343     private boolean isContainVerifiableType(DetailAST modifiers) {
344         boolean result = false;
345         for (DetailAST modifier = modifiers.getFirstChild(); modifier != null;
346                  modifier = modifier.getNextSibling()) {
347             if (memberModifiers.get(modifier.getType())) {
348                 result = true;
349                 break;
350             }
351         }
352         return result;
353     }
354 
355     /**
356      * Checks the super type and implemented interfaces of a given type.
357      *
358      * @param typeDef class or interface for check.
359      */
360     private void visitTypeDef(DetailAST typeDef) {
361         if (isVerifiable(typeDef)) {
362             checkTypeParameters(typeDef);
363             final DetailAST extendsClause = typeDef.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
364             if (extendsClause != null) {
365                 checkBaseTypes(extendsClause);
366             }
367             final DetailAST implementsClause = typeDef.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE);
368             if (implementsClause != null) {
369                 checkBaseTypes(implementsClause);
370             }
371         }
372     }
373 
374     /**
375      * Checks return type of a given method.
376      *
377      * @param methodDef method for check.
378      */
379     private void visitMethodDef(DetailAST methodDef) {
380         if (isCheckedMethod(methodDef)) {
381             checkClassName(methodDef);
382         }
383     }
384 
385     /**
386      * Checks type of parameters.
387      *
388      * @param parameterDef parameter list for check.
389      */
390     private void visitParameterDef(DetailAST parameterDef) {
391         final DetailAST grandParentAST = parameterDef.getParent().getParent();
392 
393         if (grandParentAST.getType() == TokenTypes.METHOD_DEF && isCheckedMethod(grandParentAST)) {
394             checkClassName(parameterDef);
395         }
396     }
397 
398     /**
399      * Checks type of given variable.
400      *
401      * @param variableDef variable to check.
402      */
403     private void visitVariableDef(DetailAST variableDef) {
404         if (isVerifiable(variableDef)) {
405             checkClassName(variableDef);
406         }
407     }
408 
409     /**
410      * Checks the type arguments of given method call/reference.
411      *
412      * @param methodCallOrRef method call/reference to check.
413      */
414     private void visitMethodCallOrRef(DetailAST methodCallOrRef) {
415         checkTypeArguments(methodCallOrRef);
416     }
417 
418     /**
419      * Checks imported type (as static and star imports are not supported by Check,
420      *  only type is in the consideration).<br>
421      * If this type is illegal due to Check's options - puts violation on it.
422      *
423      * @param importAst {@link TokenTypes#IMPORT Import}
424      */
425     private void visitImport(DetailAST importAst) {
426         if (!isStarImport(importAst)) {
427             final String canonicalName = getImportedTypeCanonicalName(importAst);
428             extendIllegalClassNamesWithShortName(canonicalName);
429         }
430     }
431 
432     /**
433      * Checks if current import is star import. E.g.:
434      * <p>
435      * {@code
436      * import java.util.*;
437      * }
438      * </p>
439      *
440      * @param importAst {@link TokenTypes#IMPORT Import}
441      * @return true if it is star import
442      */
443     private static boolean isStarImport(DetailAST importAst) {
444         boolean result = false;
445         DetailAST toVisit = importAst;
446         while (toVisit != null) {
447             toVisit = getNextSubTreeNode(toVisit, importAst);
448             if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
449                 result = true;
450                 break;
451             }
452         }
453         return result;
454     }
455 
456     /**
457      * Checks type and type arguments/parameters of given method, parameter, variable or
458      * method call/reference.
459      *
460      * @param ast node to check.
461      */
462     private void checkClassName(DetailAST ast) {
463         final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
464         checkType(type);
465         checkTypeParameters(ast);
466     }
467 
468     /**
469      * Checks the identifier of the given type.
470      *
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      *
483      * @param clause DetailAST for either {@link TokenTypes#EXTENDS_CLAUSE} or
484      *               {@link TokenTypes#IMPLEMENTS_CLAUSE}
485      */
486     private void checkBaseTypes(DetailAST clause) {
487         DetailAST child = clause.getFirstChild();
488         while (child != null) {
489             if (child.getType() == TokenTypes.IDENT) {
490                 checkIdent(child);
491             }
492             else {
493                 TokenUtil.forEachChild(child, TokenTypes.TYPE_ARGUMENT, this::checkType);
494             }
495             child = child.getNextSibling();
496         }
497     }
498 
499     /**
500      * Checks the given type, its arguments and parameters.
501      *
502      * @param type node to check.
503      */
504     private void checkType(DetailAST type) {
505         checkIdent(type.getFirstChild());
506         checkTypeArguments(type);
507         checkTypeBounds(type);
508     }
509 
510     /**
511      * Checks the upper and lower bounds for the given type.
512      *
513      * @param type node to check.
514      */
515     private void checkTypeBounds(DetailAST type) {
516         final DetailAST upperBounds = type.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS);
517         if (upperBounds != null) {
518             checkType(upperBounds);
519         }
520         final DetailAST lowerBounds = type.findFirstToken(TokenTypes.TYPE_LOWER_BOUNDS);
521         if (lowerBounds != null) {
522             checkType(lowerBounds);
523         }
524     }
525 
526     /**
527      * Checks the type parameters of the node.
528      *
529      * @param node node to check.
530      */
531     private void checkTypeParameters(final DetailAST node) {
532         final DetailAST typeParameters = node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
533         if (typeParameters != null) {
534             TokenUtil.forEachChild(typeParameters, TokenTypes.TYPE_PARAMETER, this::checkType);
535         }
536     }
537 
538     /**
539      * Checks the type arguments of the node.
540      *
541      * @param node node to check.
542      */
543     private void checkTypeArguments(final DetailAST node) {
544         DetailAST typeArguments = node.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
545         if (typeArguments == null) {
546             typeArguments = node.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
547         }
548 
549         if (typeArguments != null) {
550             TokenUtil.forEachChild(typeArguments, TokenTypes.TYPE_ARGUMENT, this::checkType);
551         }
552     }
553 
554     /**
555      * Returns true if given class name is one of illegal classes or else false.
556      *
557      * @param className class name to check.
558      * @return true if given class name is one of illegal classes
559      *         or if it matches to abstract class names pattern.
560      */
561     private boolean isMatchingClassName(String className) {
562         final String shortName = className.substring(className.lastIndexOf('.') + 1);
563         return illegalClassNames.contains(className)
564                 || illegalShortClassNames.contains(shortName)
565                 || validateAbstractClassNames
566                     && !legalAbstractClassNames.contains(className)
567                     && illegalAbstractClassNameFormat.matcher(className).find();
568     }
569 
570     /**
571      * Extends illegal class names set via imported short type name.
572      *
573      * @param canonicalName
574      *     <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7">
575      *     Canonical</a> name of imported type.
576      */
577     private void extendIllegalClassNamesWithShortName(String canonicalName) {
578         if (illegalClassNames.contains(canonicalName)) {
579             final String shortName = canonicalName
580                 .substring(canonicalName.lastIndexOf('.') + 1);
581             illegalShortClassNames.add(shortName);
582         }
583     }
584 
585     /**
586      * Gets imported type's
587      * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7">
588      *  canonical name</a>.
589      *
590      * @param importAst {@link TokenTypes#IMPORT Import}
591      * @return Imported canonical type's name.
592      */
593     private static String getImportedTypeCanonicalName(DetailAST importAst) {
594         final StringBuilder canonicalNameBuilder = new StringBuilder(256);
595         DetailAST toVisit = importAst;
596         while (toVisit != null) {
597             toVisit = getNextSubTreeNode(toVisit, importAst);
598             if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
599                 if (canonicalNameBuilder.length() > 0) {
600                     canonicalNameBuilder.append('.');
601                 }
602                 canonicalNameBuilder.append(toVisit.getText());
603             }
604         }
605         return canonicalNameBuilder.toString();
606     }
607 
608     /**
609      * Gets the next node of a syntactical tree (child of a current node or
610      * sibling of a current node, or sibling of a parent of a current node).
611      *
612      * @param currentNodeAst Current node in considering
613      * @param subTreeRootAst SubTree root
614      * @return Current node after bypassing, if current node reached the root of a subtree
615      *        method returns null
616      */
617     private static DetailAST
618         getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
619         DetailAST currentNode = currentNodeAst;
620         DetailAST toVisitAst = currentNode.getFirstChild();
621         while (toVisitAst == null) {
622             toVisitAst = currentNode.getNextSibling();
623             if (currentNode.getParent().equals(subTreeRootAst)) {
624                 break;
625             }
626             currentNode = currentNode.getParent();
627         }
628         return toVisitAst;
629     }
630 
631     /**
632      * Returns true if method has to be checked or false.
633      *
634      * @param ast method def to check.
635      * @return true if we should check this method.
636      */
637     private boolean isCheckedMethod(DetailAST ast) {
638         final String methodName =
639             ast.findFirstToken(TokenTypes.IDENT).getText();
640         return isVerifiable(ast) && !ignoredMethodNames.contains(methodName)
641                 && !AnnotationUtil.hasOverrideAnnotation(ast);
642     }
643 
644     /**
645      * Setter to specify classes that should not be used as types in variable declarations,
646      * return values or parameters.
647      *
648      * @param classNames array of illegal variable types
649      * @noinspection WeakerAccess
650      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
651      * @since 3.2
652      */
653     public void setIllegalClassNames(String... classNames) {
654         illegalClassNames.clear();
655         Collections.addAll(illegalClassNames, classNames);
656     }
657 
658     /**
659      * Setter to specify methods that should not be checked.
660      *
661      * @param methodNames array of ignored method names
662      * @noinspection WeakerAccess
663      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
664      * @since 3.2
665      */
666     public void setIgnoredMethodNames(String... methodNames) {
667         ignoredMethodNames.clear();
668         Collections.addAll(ignoredMethodNames, methodNames);
669     }
670 
671     /**
672      * Setter to define abstract classes that may be used as types.
673      *
674      * @param classNames array of legal abstract class names
675      * @noinspection WeakerAccess
676      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
677      * @since 4.2
678      */
679     public void setLegalAbstractClassNames(String... classNames) {
680         Collections.addAll(legalAbstractClassNames, classNames);
681     }
682 
683     /**
684      * Setter to control whether to check only methods and fields with any of
685      * the specified modifiers.
686      * This property does not affect method calls nor method references nor record components.
687      *
688      * @param modifiers String contains modifiers.
689      * @since 6.3
690      */
691     public void setMemberModifiers(String modifiers) {
692         memberModifiers = TokenUtil.asBitSet(modifiers.split(","));
693     }
694 
695 }