001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 * <p>
035 * Checks for imports from a set of illegal packages.
036 * </p>
037 * <p>
038 * Note: By default, the check rejects all {@code sun.*} packages since programs
039 * that contain direct calls to the {@code sun.*} packages are
040 * <a href="https://www.oracle.com/java/technologies/faq-sun-packages.html">
041 * "not guaranteed to work on all Java-compatible platforms"</a>. To reject other
042 * packages, set property {@code illegalPkgs} to a list of the illegal packages.
043 * </p>
044 * <ul>
045 * <li>
046 * Property {@code illegalPkgs} - Specify packages to reject, if <b>regexp</b>
047 * property is not set, checks if import is the part of package. If <b>regexp</b>
048 * property is set, then list of packages will be interpreted as regular expressions.
049 * Note, all properties for match will be used.
050 * Type is {@code java.lang.String[]}.
051 * Default value is {@code sun}.
052 * </li>
053 * <li>
054 * Property {@code illegalClasses} - Specify class names to reject, if <b>regexp</b>
055 * property is not set, checks if import equals class name. If <b>regexp</b>
056 * property is set, then list of class names will be interpreted as regular expressions.
057 * Note, all properties for match will be used.
058 * Type is {@code java.lang.String[]}.
059 * Default value is {@code ""}.
060 * </li>
061 * <li>
062 * Property {@code regexp} - Control whether the {@code illegalPkgs} and
063 * {@code illegalClasses} should be interpreted as regular expressions.
064 * Type is {@code boolean}.
065 * Default value is {@code false}.
066 * </li>
067 * </ul>
068 * <p>
069 * To configure the check:
070 * </p>
071 * <pre>
072 * &lt;module name="IllegalImport"/&gt;
073 * </pre>
074 * <p>
075 * To configure the check so that it rejects packages {@code java.io.*} and {@code java.sql.*}:
076 * </p>
077 * <pre>
078 * &lt;module name="IllegalImport"&gt;
079 *   &lt;property name="illegalPkgs" value="java.io, java.sql"/&gt;
080 * &lt;/module&gt;
081 * </pre>
082 * <p>
083 * The following example shows class with no illegal imports
084 * </p>
085 * <pre>
086 * import java.lang.ArithmeticException;
087 * import java.util.List;
088 * import java.util.Enumeration;
089 * import java.util.Arrays;
090 * import sun.applet.*;
091 *
092 * public class InputIllegalImport { }
093 * </pre>
094 * <p>
095 * The following example shows class with two illegal imports
096 * </p>
097 * <ul>
098 * <li>
099 * <b>java.io.*</b>, illegalPkgs property contains this package
100 * </li>
101 * <li>
102 * <b>java.sql.Connection</b> is inside java.sql package
103 * </li>
104 * </ul>
105 * <pre>
106 * import java.io.*;           // violation
107 * import java.lang.ArithmeticException;
108 * import java.sql.Connection; // violation
109 * import java.util.List;
110 * import java.util.Enumeration;
111 * import java.util.Arrays;
112 * import sun.applet.*;
113 *
114 * public class InputIllegalImport { }
115 * </pre>
116 * <p>
117 * To configure the check so that it rejects classes {@code java.util.Date} and
118 * {@code java.sql.Connection}:
119 * </p>
120 * <pre>
121 * &lt;module name="IllegalImport"&gt;
122 *   &lt;property name="illegalClasses"
123 *     value="java.util.Date, java.sql.Connection"/&gt;
124 * &lt;/module&gt;
125 * </pre>
126 * <p>
127 * The following example shows class with no illegal imports
128 * </p>
129 * <pre>
130 * import java.io.*;
131 * import java.lang.ArithmeticException;
132 * import java.util.List;
133 * import java.util.Enumeration;
134 * import java.util.Arrays;
135 * import sun.applet.*;
136 *
137 * public class InputIllegalImport { }
138 * </pre>
139 * <p>
140 * The following example shows class with two illegal imports
141 * </p>
142 * <ul>
143 * <li>
144 * <b>java.sql.Connection</b>, illegalClasses property contains this class
145 * </li>
146 * <li>
147 * <b>java.util.Date</b>, illegalClasses property contains this class
148 * </li>
149 * </ul>
150 * <pre>
151 * import java.io.*;
152 * import java.lang.ArithmeticException;
153 * import java.sql.Connection; // violation
154 * import java.util.List;
155 * import java.util.Enumeration;
156 * import java.util.Arrays;
157 * import java.util.Date;      // violation
158 * import sun.applet.*;
159 *
160 * public class InputIllegalImport { }
161 * </pre>
162 * <p>
163 * To configure the check so that it rejects packages not satisfying to regular
164 * expression {@code java\.util}:
165 * </p>
166 * <pre>
167 * &lt;module name="IllegalImport"&gt;
168 *   &lt;property name="regexp" value="true"/&gt;
169 *   &lt;property name="illegalPkgs" value="java\.util"/&gt;
170 * &lt;/module&gt;
171 * </pre>
172 * <p>
173 * The following example shows class with no illegal imports
174 * </p>
175 * <pre>
176 * import java.io.*;
177 * import java.lang.ArithmeticException;
178 * import java.sql.Connection;
179 * import sun.applet.*;
180 *
181 * public class InputIllegalImport { }
182 * </pre>
183 * <p>
184 * The following example shows class with four illegal imports
185 * </p>
186 * <ul>
187 * <li>
188 * <b>java.util.List</b>
189 * </li>
190 * <li>
191 * <b>java.util.Enumeration</b>
192 * </li>
193 * <li>
194 * <b>java.util.Arrays</b>
195 * </li>
196 * <li>
197 * <b>java.util.Date</b>
198 * </li>
199 * </ul>
200 * <p>
201 * All four imports match "java\.util" regular expression
202 * </p>
203 * <pre>
204 * import java.io.*;
205 * import java.lang.ArithmeticException;
206 * import java.sql.Connection;
207 * import java.util.List;          // violation
208 * import java.util.Enumeration;   // violation
209 * import java.util.Arrays;        // violation
210 * import java.util.Date;          // violation
211 * import sun.applet.*;
212 *
213 * public class InputIllegalImport { }
214 * </pre>
215 * <p>
216 * To configure the check so that it rejects class names not satisfying to regular
217 * expression {@code ^java\.util\.(List|Arrays)} and {@code ^java\.sql\.Connection}:
218 * </p>
219 * <pre>
220 * &lt;module name="IllegalImport"&gt;
221 *   &lt;property name="regexp" value="true"/&gt;
222 *   &lt;property name="illegalClasses"
223 *     value="^java\.util\.(List|Arrays), ^java\.sql\.Connection"/&gt;
224 * &lt;/module&gt;
225 * </pre>
226 * <p>
227 * The following example shows class with no illegal imports
228 * </p>
229 * <pre>
230 * import java.io.*;
231 * import java.lang.ArithmeticException;
232 * import java.util.Enumeration;
233 * import java.util.Date;
234 * import sun.applet.*;
235 *
236 * public class InputIllegalImport { }
237 * </pre>
238 * <p>
239 * The following example shows class with three illegal imports
240 * </p>
241 * <ul>
242 * <li>
243 * <b>java.sql.Connection</b> matches "^java\.sql\.Connection" regular expression
244 * </li>
245 * <li>
246 * <b>java.util.List</b> matches "^java\.util\.(List|Arrays)" regular expression
247 * </li>
248 * <li>
249 * <b>java.util.Arrays</b> matches "^java\.util\.(List|Arrays)" regular expression
250 * </li>
251 * </ul>
252 * <pre>
253 * import java.io.*;
254 * import java.lang.ArithmeticException;
255 * import java.sql.Connection;     // violation
256 * import java.util.List;          // violation
257 * import java.util.Enumeration;
258 * import java.util.Arrays;        // violation
259 * import java.util.Date;
260 * import sun.applet.*;
261 *
262 * public class InputIllegalImport { }
263 * </pre>
264 * <p>
265 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
266 * </p>
267 * <p>
268 * Violation Message Keys:
269 * </p>
270 * <ul>
271 * <li>
272 * {@code import.illegal}
273 * </li>
274 * </ul>
275 *
276 * @since 3.0
277 */
278@StatelessCheck
279public class IllegalImportCheck
280    extends AbstractCheck {
281
282    /**
283     * A key is pointing to the warning message text in "messages.properties"
284     * file.
285     */
286    public static final String MSG_KEY = "import.illegal";
287
288    /** The compiled regular expressions for packages. */
289    private final List<Pattern> illegalPkgsRegexps = new ArrayList<>();
290
291    /** The compiled regular expressions for classes. */
292    private final List<Pattern> illegalClassesRegexps = new ArrayList<>();
293
294    /**
295     * Specify packages to reject, if <b>regexp</b> property is not set, checks
296     * if import is the part of package. If <b>regexp</b> property is set, then
297     * list of packages will be interpreted as regular expressions.
298     * Note, all properties for match will be used.
299     */
300    private String[] illegalPkgs;
301
302    /**
303     * Specify class names to reject, if <b>regexp</b> property is not set,
304     * checks if import equals class name. If <b>regexp</b> property is set,
305     * then list of class names will be interpreted as regular expressions.
306     * Note, all properties for match will be used.
307     */
308    private String[] illegalClasses;
309
310    /**
311     * Control whether the {@code illegalPkgs} and {@code illegalClasses}
312     * should be interpreted as regular expressions.
313     */
314    private boolean regexp;
315
316    /**
317     * Creates a new {@code IllegalImportCheck} instance.
318     */
319    public IllegalImportCheck() {
320        setIllegalPkgs("sun");
321    }
322
323    /**
324     * Setter to specify packages to reject, if <b>regexp</b> property is not set,
325     * checks if import is the part of package. If <b>regexp</b> property is set,
326     * then list of packages will be interpreted as regular expressions.
327     * Note, all properties for match will be used.
328     *
329     * @param from illegal packages
330     * @noinspection WeakerAccess
331     * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
332     */
333    public final void setIllegalPkgs(String... from) {
334        illegalPkgs = from.clone();
335        illegalPkgsRegexps.clear();
336        for (String illegalPkg : illegalPkgs) {
337            illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
338        }
339    }
340
341    /**
342     * Setter to specify class names to reject, if <b>regexp</b> property is not
343     * set, checks if import equals class name. If <b>regexp</b> property is set,
344     * then list of class names will be interpreted as regular expressions.
345     * Note, all properties for match will be used.
346     *
347     * @param from illegal classes
348     */
349    public void setIllegalClasses(String... from) {
350        illegalClasses = from.clone();
351        for (String illegalClass : illegalClasses) {
352            illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
353        }
354    }
355
356    /**
357     * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
358     * should be interpreted as regular expressions.
359     *
360     * @param regexp a {@code Boolean} value
361     */
362    public void setRegexp(boolean regexp) {
363        this.regexp = regexp;
364    }
365
366    @Override
367    public int[] getDefaultTokens() {
368        return getRequiredTokens();
369    }
370
371    @Override
372    public int[] getAcceptableTokens() {
373        return getRequiredTokens();
374    }
375
376    @Override
377    public int[] getRequiredTokens() {
378        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
379    }
380
381    @Override
382    public void visitToken(DetailAST ast) {
383        final FullIdent imp;
384        if (ast.getType() == TokenTypes.IMPORT) {
385            imp = FullIdent.createFullIdentBelow(ast);
386        }
387        else {
388            imp = FullIdent.createFullIdent(
389                ast.getFirstChild().getNextSibling());
390        }
391        final String importText = imp.getText();
392        if (isIllegalImport(importText)) {
393            log(ast, MSG_KEY, importText);
394        }
395    }
396
397    /**
398     * Checks if an import matches one of the regular expressions
399     * for illegal packages or illegal class names.
400     *
401     * @param importText the argument of the import keyword
402     * @return if {@code importText} matches one of the regular expressions
403     *         for illegal packages or illegal class names
404     */
405    private boolean isIllegalImportByRegularExpressions(String importText) {
406        boolean result = false;
407        for (Pattern pattern : illegalPkgsRegexps) {
408            if (pattern.matcher(importText).matches()) {
409                result = true;
410                break;
411            }
412        }
413        for (Pattern pattern : illegalClassesRegexps) {
414            if (pattern.matcher(importText).matches()) {
415                result = true;
416                break;
417            }
418        }
419        return result;
420    }
421
422    /**
423     * Checks if an import is from a package or class name that must not be used.
424     *
425     * @param importText the argument of the import keyword
426     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
427     */
428    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
429        boolean result = false;
430        for (String element : illegalPkgs) {
431            if (importText.startsWith(element + ".")) {
432                result = true;
433                break;
434            }
435        }
436        if (illegalClasses != null) {
437            for (String element : illegalClasses) {
438                if (importText.equals(element)) {
439                    result = true;
440                    break;
441                }
442            }
443        }
444        return result;
445    }
446
447    /**
448     * Checks if an import is from a package or class name that must not be used.
449     *
450     * @param importText the argument of the import keyword
451     * @return if {@code importText} is illegal import
452     */
453    private boolean isIllegalImport(String importText) {
454        final boolean result;
455        if (regexp) {
456            result = isIllegalImportByRegularExpressions(importText);
457        }
458        else {
459            result = isIllegalImportByPackagesAndClassNames(importText);
460        }
461        return result;
462    }
463
464}