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