001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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 * &lt;module name=&quot;IllegalType&quot;&gt;
137 *   &lt;property name=&quot;ignoredMethodNames&quot; value=&quot;getInstance&quot;/&gt;
138 * &lt;/module&gt;
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 * &lt;module name=&quot;IllegalType&quot;&gt;
145 *   &lt;property name=&quot;memberModifiers&quot; value=&quot;LITERAL_PUBLIC,
146 *    LITERAL_PROTECTED, LITERAL_STATIC&quot;/&gt;
147 * &lt;/module&gt;
148 * </pre>
149 * <p>
150 * To configure the check so that it verifies usage of types Boolean and Foo:
151 * </p>
152 * <pre>
153 * &lt;module name=&quot;IllegalType&quot;&gt;
154 *           &lt;property name=&quot;illegalClassNames&quot; value=&quot;Boolean, Foo&quot;/&gt;
155 * &lt;/module&gt;
156 * </pre>
157 * <pre>
158 * public class Test {
159 *
160 *   public Set&lt;Boolean&gt; set; // violation
161 *   public java.util.List&lt;Map&lt;Boolean, Foo&gt;&gt; list; // violation
162 *
163 *   private void method(List&lt;Foo&gt; list, Boolean value) { // violation
164 *     SomeType.&lt;Boolean&gt;foo(); // violation
165 *     final Consumer&lt;Foo&gt; consumer = Foo&lt;Boolean&gt;::foo; // violation
166 *   }
167 *
168 *   public &lt;T extends Boolean, U extends Serializable&gt; void typeParam(T a) {} // violation
169 *
170 *   public void fullName(java.util.ArrayList&lt;? super Boolean&gt; a) {} // violation
171 *
172 *   public abstract Set&lt;Boolean&gt; shortName(Set&lt;? super Boolean&gt; a); // violation
173 *
174 *   public Set&lt;? extends Foo&gt; typeArgument() { // violation
175 *     return new TreeSet&lt;Foo&lt;Boolean&gt;&gt;();
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}