001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 array of illegal packages
330     * @noinspection WeakerAccess
331     */
332    public final void setIllegalPkgs(String... from) {
333        illegalPkgs = from.clone();
334        illegalPkgsRegexps.clear();
335        for (String illegalPkg : illegalPkgs) {
336            illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
337        }
338    }
339
340    /**
341     * Setter to specify class names to reject, if <b>regexp</b> property is not
342     * set, checks if import equals class name. If <b>regexp</b> property is set,
343     * then list of class names will be interpreted as regular expressions.
344     * Note, all properties for match will be used.
345     *
346     * @param from array of illegal classes
347     */
348    public void setIllegalClasses(String... from) {
349        illegalClasses = from.clone();
350        for (String illegalClass : illegalClasses) {
351            illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
352        }
353    }
354
355    /**
356     * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
357     * should be interpreted as regular expressions.
358     *
359     * @param regexp a {@code Boolean} value
360     */
361    public void setRegexp(boolean regexp) {
362        this.regexp = regexp;
363    }
364
365    @Override
366    public int[] getDefaultTokens() {
367        return getRequiredTokens();
368    }
369
370    @Override
371    public int[] getAcceptableTokens() {
372        return getRequiredTokens();
373    }
374
375    @Override
376    public int[] getRequiredTokens() {
377        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
378    }
379
380    @Override
381    public void visitToken(DetailAST ast) {
382        final FullIdent imp;
383        if (ast.getType() == TokenTypes.IMPORT) {
384            imp = FullIdent.createFullIdentBelow(ast);
385        }
386        else {
387            imp = FullIdent.createFullIdent(
388                ast.getFirstChild().getNextSibling());
389        }
390        if (isIllegalImport(imp.getText())) {
391            log(ast,
392                MSG_KEY,
393                imp.getText());
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        if (!result) {
414            for (Pattern pattern : illegalClassesRegexps) {
415                if (pattern.matcher(importText).matches()) {
416                    result = true;
417                    break;
418                }
419            }
420        }
421        return result;
422    }
423
424    /**
425     * Checks if an import is from a package or class name that must not be used.
426     *
427     * @param importText the argument of the import keyword
428     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
429     */
430    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
431        boolean result = false;
432        for (String element : illegalPkgs) {
433            if (importText.startsWith(element + ".")) {
434                result = true;
435                break;
436            }
437        }
438        if (illegalClasses != null) {
439            for (String element : illegalClasses) {
440                if (importText.equals(element)) {
441                    result = true;
442                    break;
443                }
444            }
445        }
446        return result;
447    }
448
449    /**
450     * Checks if an import is from a package or class name that must not be used.
451     *
452     * @param importText the argument of the import keyword
453     * @return if {@code importText} is illegal import
454     */
455    private boolean isIllegalImport(String importText) {
456        final boolean result;
457        if (regexp) {
458            result = isIllegalImportByRegularExpressions(importText);
459        }
460        else {
461            result = isIllegalImportByPackagesAndClassNames(importText);
462        }
463        return result;
464    }
465
466}