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