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