001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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/technetwork/java/faq-sun-packages-142232.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    /** Specify class names to reject, if <b>regexp</b> property is not set,
289     *  checks if import equals class name. If <b>regexp</b> property is set,
290     *  then list of class names will be interpreted as regular expressions.
291     *  Note, all properties for match will be used. */
292    private String[] illegalClasses;
293
294    /**
295     * Control whether the {@code illegalPkgs} and {@code illegalClasses}
296     * should be interpreted as regular expressions.
297     */
298    private boolean regexp;
299
300    /**
301     * Creates a new {@code IllegalImportCheck} instance.
302     */
303    public IllegalImportCheck() {
304        setIllegalPkgs("sun");
305    }
306
307    /**
308     * Setter to specify packages to reject, if <b>regexp</b> property is not set,
309     * checks if import is the part of package. If <b>regexp</b> property is set,
310     * then list of packages will be interpreted as regular expressions.
311     * Note, all properties for match will be used.
312     *
313     * @param from array of illegal packages
314     * @noinspection WeakerAccess
315     */
316    public final void setIllegalPkgs(String... from) {
317        illegalPkgs = from.clone();
318        illegalPkgsRegexps.clear();
319        for (String illegalPkg : illegalPkgs) {
320            illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
321        }
322    }
323
324    /**
325     * Setter to specify class names to reject, if <b>regexp</b> property is not
326     * set, checks if import equals class name. If <b>regexp</b> property is set,
327     * then list of class names will be interpreted as regular expressions.
328     * Note, all properties for match will be used.
329     *
330     * @param from array of illegal classes
331     */
332    public void setIllegalClasses(String... from) {
333        illegalClasses = from.clone();
334        for (String illegalClass : illegalClasses) {
335            illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
336        }
337    }
338
339    /**
340     * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
341     * should be interpreted as regular expressions.
342     *
343     * @param regexp a {@code Boolean} value
344     */
345    public void setRegexp(boolean regexp) {
346        this.regexp = regexp;
347    }
348
349    @Override
350    public int[] getDefaultTokens() {
351        return getRequiredTokens();
352    }
353
354    @Override
355    public int[] getAcceptableTokens() {
356        return getRequiredTokens();
357    }
358
359    @Override
360    public int[] getRequiredTokens() {
361        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
362    }
363
364    @Override
365    public void visitToken(DetailAST ast) {
366        final FullIdent imp;
367        if (ast.getType() == TokenTypes.IMPORT) {
368            imp = FullIdent.createFullIdentBelow(ast);
369        }
370        else {
371            imp = FullIdent.createFullIdent(
372                ast.getFirstChild().getNextSibling());
373        }
374        if (isIllegalImport(imp.getText())) {
375            log(ast,
376                MSG_KEY,
377                imp.getText());
378        }
379    }
380
381    /**
382     * Checks if an import matches one of the regular expressions
383     * for illegal packages or illegal class names.
384     * @param importText the argument of the import keyword
385     * @return if {@code importText} matches one of the regular expressions
386     *         for illegal packages or illegal class names
387     */
388    private boolean isIllegalImportByRegularExpressions(String importText) {
389        boolean result = false;
390        for (Pattern pattern : illegalPkgsRegexps) {
391            if (pattern.matcher(importText).matches()) {
392                result = true;
393                break;
394            }
395        }
396        if (!result) {
397            for (Pattern pattern : illegalClassesRegexps) {
398                if (pattern.matcher(importText).matches()) {
399                    result = true;
400                    break;
401                }
402            }
403        }
404        return result;
405    }
406
407    /**
408     * Checks if an import is from a package or class name that must not be used.
409     * @param importText the argument of the import keyword
410     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
411     */
412    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
413        boolean result = false;
414        for (String element : illegalPkgs) {
415            if (importText.startsWith(element + ".")) {
416                result = true;
417                break;
418            }
419        }
420        if (!result && illegalClasses != null) {
421            for (String element : illegalClasses) {
422                if (importText.equals(element)) {
423                    result = true;
424                    break;
425                }
426            }
427        }
428        return result;
429    }
430
431    /**
432     * Checks if an import is from a package or class name that must not be used.
433     * @param importText the argument of the import keyword
434     * @return if {@code importText} is illegal import
435     */
436    private boolean isIllegalImport(String importText) {
437        final boolean result;
438        if (regexp) {
439            result = isIllegalImportByRegularExpressions(importText);
440        }
441        else {
442            result = isIllegalImportByPackagesAndClassNames(importText);
443        }
444        return result;
445    }
446
447}