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