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.imports;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.FullIdent;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
032
033/**
034 * <p>
035 * Checks for imports from a set of illegal packages.
036 * </p>
037 * <p>
038 * Note: By default, the check rejects all {@code sun.*} packages since programs
039 * that contain direct calls to the {@code sun.*} packages are
040 * <a href="https://www.oracle.com/java/technologies/faq-sun-packages.html">
041 * "not guaranteed to work on all Java-compatible platforms"</a>. To reject other
042 * packages, set property {@code illegalPkgs} to a list of the illegal packages.
043 * </p>
044 * <ul>
045 * <li>
046 * Property {@code illegalClasses} - Specify class names to reject, if <b>regexp</b>
047 * property is not set, checks if import equals class name. If <b>regexp</b>
048 * property is set, then list of class names will be interpreted as regular expressions.
049 * Note, all properties for match will be used.
050 * Type is {@code java.lang.String[]}.
051 * Default value is {@code ""}.
052 * </li>
053 * <li>
054 * Property {@code illegalPkgs} - Specify packages to reject, if <b>regexp</b>
055 * property is not set, checks if import is the part of package. If <b>regexp</b>
056 * property is set, then list of packages will be interpreted as regular expressions.
057 * Note, all properties for match will be used.
058 * Type is {@code java.lang.String[]}.
059 * Default value is {@code sun}.
060 * </li>
061 * <li>
062 * Property {@code regexp} - Control whether the {@code illegalPkgs} and
063 * {@code illegalClasses} should be interpreted as regular expressions.
064 * Type is {@code boolean}.
065 * Default value is {@code false}.
066 * </li>
067 * </ul>
068 * <p>
069 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
070 * </p>
071 * <p>
072 * Violation Message Keys:
073 * </p>
074 * <ul>
075 * <li>
076 * {@code import.illegal}
077 * </li>
078 * </ul>
079 *
080 * @since 3.0
081 */
082@StatelessCheck
083public class IllegalImportCheck
084    extends AbstractCheck {
085
086    /**
087     * A key is pointing to the warning message text in "messages.properties"
088     * file.
089     */
090    public static final String MSG_KEY = "import.illegal";
091
092    /** The compiled regular expressions for packages. */
093    private final List<Pattern> illegalPkgsRegexps = new ArrayList<>();
094
095    /** The compiled regular expressions for classes. */
096    private final List<Pattern> illegalClassesRegexps = new ArrayList<>();
097
098    /**
099     * Specify packages to reject, if <b>regexp</b> property is not set, checks
100     * if import is the part of package. If <b>regexp</b> property is set, then
101     * list of packages will be interpreted as regular expressions.
102     * Note, all properties for match will be used.
103     */
104    private String[] illegalPkgs;
105
106    /**
107     * Specify class names to reject, if <b>regexp</b> property is not set,
108     * checks if import equals class name. If <b>regexp</b> property is set,
109     * then list of class names will be interpreted as regular expressions.
110     * Note, all properties for match will be used.
111     */
112    private String[] illegalClasses;
113
114    /**
115     * Control whether the {@code illegalPkgs} and {@code illegalClasses}
116     * should be interpreted as regular expressions.
117     */
118    private boolean regexp;
119
120    /**
121     * Creates a new {@code IllegalImportCheck} instance.
122     */
123    public IllegalImportCheck() {
124        setIllegalPkgs("sun");
125    }
126
127    /**
128     * Setter to specify packages to reject, if <b>regexp</b> property is not set,
129     * checks if import is the part of package. If <b>regexp</b> property is set,
130     * then list of packages will be interpreted as regular expressions.
131     * Note, all properties for match will be used.
132     *
133     * @param from illegal packages
134     * @noinspection WeakerAccess
135     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
136     * @since 3.0
137     */
138    public final void setIllegalPkgs(String... from) {
139        illegalPkgs = from.clone();
140        illegalPkgsRegexps.clear();
141        for (String illegalPkg : illegalPkgs) {
142            illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
143        }
144    }
145
146    /**
147     * Setter to specify class names to reject, if <b>regexp</b> property is not
148     * set, checks if import equals class name. If <b>regexp</b> property is set,
149     * then list of class names will be interpreted as regular expressions.
150     * Note, all properties for match will be used.
151     *
152     * @param from illegal classes
153     * @since 7.8
154     */
155    public void setIllegalClasses(String... from) {
156        illegalClasses = from.clone();
157        for (String illegalClass : illegalClasses) {
158            illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
159        }
160    }
161
162    /**
163     * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
164     * should be interpreted as regular expressions.
165     *
166     * @param regexp a {@code Boolean} value
167     * @since 7.8
168     */
169    public void setRegexp(boolean regexp) {
170        this.regexp = regexp;
171    }
172
173    @Override
174    public int[] getDefaultTokens() {
175        return getRequiredTokens();
176    }
177
178    @Override
179    public int[] getAcceptableTokens() {
180        return getRequiredTokens();
181    }
182
183    @Override
184    public int[] getRequiredTokens() {
185        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
186    }
187
188    @Override
189    public void visitToken(DetailAST ast) {
190        final FullIdent imp;
191        if (ast.getType() == TokenTypes.IMPORT) {
192            imp = FullIdent.createFullIdentBelow(ast);
193        }
194        else {
195            imp = FullIdent.createFullIdent(
196                ast.getFirstChild().getNextSibling());
197        }
198        final String importText = imp.getText();
199        if (isIllegalImport(importText)) {
200            log(ast, MSG_KEY, importText);
201        }
202    }
203
204    /**
205     * Checks if an import matches one of the regular expressions
206     * for illegal packages or illegal class names.
207     *
208     * @param importText the argument of the import keyword
209     * @return if {@code importText} matches one of the regular expressions
210     *         for illegal packages or illegal class names
211     */
212    private boolean isIllegalImportByRegularExpressions(String importText) {
213        boolean result = false;
214        for (Pattern pattern : illegalPkgsRegexps) {
215            if (pattern.matcher(importText).matches()) {
216                result = true;
217                break;
218            }
219        }
220        for (Pattern pattern : illegalClassesRegexps) {
221            if (pattern.matcher(importText).matches()) {
222                result = true;
223                break;
224            }
225        }
226        return result;
227    }
228
229    /**
230     * Checks if an import is from a package or class name that must not be used.
231     *
232     * @param importText the argument of the import keyword
233     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
234     */
235    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
236        boolean result = false;
237        for (String element : illegalPkgs) {
238            if (importText.startsWith(element + ".")) {
239                result = true;
240                break;
241            }
242        }
243        if (illegalClasses != null) {
244            for (String element : illegalClasses) {
245                if (importText.equals(element)) {
246                    result = true;
247                    break;
248                }
249            }
250        }
251        return result;
252    }
253
254    /**
255     * Checks if an import is from a package or class name that must not be used.
256     *
257     * @param importText the argument of the import keyword
258     * @return if {@code importText} is illegal import
259     */
260    private boolean isIllegalImport(String importText) {
261        final boolean result;
262        if (regexp) {
263            result = isIllegalImportByRegularExpressions(importText);
264        }
265        else {
266            result = isIllegalImportByPackagesAndClassNames(importText);
267        }
268        return result;
269    }
270
271}