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