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.
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
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    /**
082     * Control whether the {@code illegalPkgs} and {@code illegalClasses}
083     * should be interpreted as regular expressions.
084     */
085    private boolean regexp;
086
087    /**
088     * Creates a new {@code IllegalImportCheck} instance.
089     */
090    public IllegalImportCheck() {
091        setIllegalPkgs("sun");
092    }
093
094    /**
095     * Setter to specify packages to reject, if <b>regexp</b> property is not set,
096     * checks if import is the part of package. If <b>regexp</b> property is set,
097     * then list of packages will be interpreted as regular expressions.
098     * Note, all properties for match will be used.
099     *
100     * @param from illegal packages
101     * @noinspection WeakerAccess
102     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
103     * @since 3.0
104     */
105    public final void setIllegalPkgs(String... from) {
106        illegalPkgs = from.clone();
107        illegalPkgsRegexps.clear();
108        for (String illegalPkg : illegalPkgs) {
109            illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
110        }
111    }
112
113    /**
114     * Setter to specify class names to reject, if <b>regexp</b> property is not
115     * set, checks if import equals class name. If <b>regexp</b> property is set,
116     * then list of class names will be interpreted as regular expressions.
117     * Note, all properties for match will be used.
118     *
119     * @param from illegal classes
120     * @since 7.8
121     */
122    public void setIllegalClasses(String... from) {
123        illegalClasses = from.clone();
124        for (String illegalClass : illegalClasses) {
125            illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
126        }
127    }
128
129    /**
130     * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
131     * should be interpreted as regular expressions.
132     *
133     * @param regexp a {@code Boolean} value
134     * @since 7.8
135     */
136    public void setRegexp(boolean regexp) {
137        this.regexp = regexp;
138    }
139
140    @Override
141    public int[] getDefaultTokens() {
142        return getRequiredTokens();
143    }
144
145    @Override
146    public int[] getAcceptableTokens() {
147        return getRequiredTokens();
148    }
149
150    @Override
151    public int[] getRequiredTokens() {
152        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
153    }
154
155    @Override
156    public void visitToken(DetailAST ast) {
157        final FullIdent imp;
158        if (ast.getType() == TokenTypes.IMPORT) {
159            imp = FullIdent.createFullIdentBelow(ast);
160        }
161        else {
162            imp = FullIdent.createFullIdent(
163                ast.getFirstChild().getNextSibling());
164        }
165        final String importText = imp.getText();
166        if (isIllegalImport(importText)) {
167            log(ast, MSG_KEY, importText);
168        }
169    }
170
171    /**
172     * Checks if an import matches one of the regular expressions
173     * for illegal packages or illegal class names.
174     *
175     * @param importText the argument of the import keyword
176     * @return if {@code importText} matches one of the regular expressions
177     *         for illegal packages or illegal class names
178     */
179    private boolean isIllegalImportByRegularExpressions(String importText) {
180        boolean result = false;
181        for (Pattern pattern : illegalPkgsRegexps) {
182            if (pattern.matcher(importText).matches()) {
183                result = true;
184                break;
185            }
186        }
187        for (Pattern pattern : illegalClassesRegexps) {
188            if (pattern.matcher(importText).matches()) {
189                result = true;
190                break;
191            }
192        }
193        return result;
194    }
195
196    /**
197     * Checks if an import is from a package or class name that must not be used.
198     *
199     * @param importText the argument of the import keyword
200     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
201     */
202    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
203        boolean result = false;
204        for (String element : illegalPkgs) {
205            if (importText.startsWith(element + ".")) {
206                result = true;
207                break;
208            }
209        }
210        if (illegalClasses != null) {
211            for (String element : illegalClasses) {
212                if (importText.equals(element)) {
213                    result = true;
214                    break;
215                }
216            }
217        }
218        return result;
219    }
220
221    /**
222     * Checks if an import is from a package or class name that must not be used.
223     *
224     * @param importText the argument of the import keyword
225     * @return if {@code importText} is illegal import
226     */
227    private boolean isIllegalImport(String importText) {
228        final boolean result;
229        if (regexp) {
230            result = isIllegalImportByRegularExpressions(importText);
231        }
232        else {
233            result = isIllegalImportByPackagesAndClassNames(importText);
234        }
235        return result;
236    }
237
238}