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