View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.imports;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.regex.Pattern;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.FullIdent;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32  
33  /**
34   * <p>
35   * Checks for imports from a set of illegal packages.
36   * </p>
37   * <p>
38   * Note: By default, the check rejects all {@code sun.*} packages since programs
39   * that contain direct calls to the {@code sun.*} packages are
40   * <a href="https://www.oracle.com/java/technologies/faq-sun-packages.html">
41   * "not guaranteed to work on all Java-compatible platforms"</a>. To reject other
42   * packages, set property {@code illegalPkgs} to a list of the illegal packages.
43   * </p>
44   * <ul>
45   * <li>
46   * Property {@code illegalClasses} - Specify class names to reject, if <b>regexp</b>
47   * property is not set, checks if import equals class name. If <b>regexp</b>
48   * property is set, then list of class names will be interpreted as regular expressions.
49   * Note, all properties for match will be used.
50   * Type is {@code java.lang.String[]}.
51   * Default value is {@code ""}.
52   * </li>
53   * <li>
54   * Property {@code illegalPkgs} - Specify packages to reject, if <b>regexp</b>
55   * property is not set, checks if import is the part of package. If <b>regexp</b>
56   * property is set, then list of packages will be interpreted as regular expressions.
57   * Note, all properties for match will be used.
58   * Type is {@code java.lang.String[]}.
59   * Default value is {@code sun}.
60   * </li>
61   * <li>
62   * Property {@code regexp} - Control whether the {@code illegalPkgs} and
63   * {@code illegalClasses} should be interpreted as regular expressions.
64   * Type is {@code boolean}.
65   * Default value is {@code false}.
66   * </li>
67   * </ul>
68   * <p>
69   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
70   * </p>
71   * <p>
72   * Violation Message Keys:
73   * </p>
74   * <ul>
75   * <li>
76   * {@code import.illegal}
77   * </li>
78   * </ul>
79   *
80   * @since 3.0
81   */
82  @StatelessCheck
83  public class IllegalImportCheck
84      extends AbstractCheck {
85  
86      /**
87       * A key is pointing to the warning message text in "messages.properties"
88       * file.
89       */
90      public static final String MSG_KEY = "import.illegal";
91  
92      /** The compiled regular expressions for packages. */
93      private final List<Pattern> illegalPkgsRegexps = new ArrayList<>();
94  
95      /** The compiled regular expressions for classes. */
96      private final List<Pattern> illegalClassesRegexps = new ArrayList<>();
97  
98      /**
99       * Specify packages to reject, if <b>regexp</b> property is not set, checks
100      * if import is the part of package. If <b>regexp</b> property is set, then
101      * list of packages will be interpreted as regular expressions.
102      * Note, all properties for match will be used.
103      */
104     private String[] illegalPkgs;
105 
106     /**
107      * Specify class names to reject, if <b>regexp</b> property is not set,
108      * checks if import equals class name. If <b>regexp</b> property is set,
109      * then list of class names will be interpreted as regular expressions.
110      * Note, all properties for match will be used.
111      */
112     private String[] illegalClasses;
113 
114     /**
115      * Control whether the {@code illegalPkgs} and {@code illegalClasses}
116      * should be interpreted as regular expressions.
117      */
118     private boolean regexp;
119 
120     /**
121      * Creates a new {@code IllegalImportCheck} instance.
122      */
123     public IllegalImportCheck() {
124         setIllegalPkgs("sun");
125     }
126 
127     /**
128      * Setter to specify packages to reject, if <b>regexp</b> property is not set,
129      * checks if import is the part of package. If <b>regexp</b> property is set,
130      * then list of packages will be interpreted as regular expressions.
131      * Note, all properties for match will be used.
132      *
133      * @param from illegal packages
134      * @noinspection WeakerAccess
135      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
136      * @since 3.0
137      */
138     public final void setIllegalPkgs(String... from) {
139         illegalPkgs = from.clone();
140         illegalPkgsRegexps.clear();
141         for (String illegalPkg : illegalPkgs) {
142             illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
143         }
144     }
145 
146     /**
147      * Setter to specify class names to reject, if <b>regexp</b> property is not
148      * set, checks if import equals class name. If <b>regexp</b> property is set,
149      * then list of class names will be interpreted as regular expressions.
150      * Note, all properties for match will be used.
151      *
152      * @param from illegal classes
153      * @since 7.8
154      */
155     public void setIllegalClasses(String... from) {
156         illegalClasses = from.clone();
157         for (String illegalClass : illegalClasses) {
158             illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
159         }
160     }
161 
162     /**
163      * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
164      * should be interpreted as regular expressions.
165      *
166      * @param regexp a {@code Boolean} value
167      * @since 7.8
168      */
169     public void setRegexp(boolean regexp) {
170         this.regexp = regexp;
171     }
172 
173     @Override
174     public int[] getDefaultTokens() {
175         return getRequiredTokens();
176     }
177 
178     @Override
179     public int[] getAcceptableTokens() {
180         return getRequiredTokens();
181     }
182 
183     @Override
184     public int[] getRequiredTokens() {
185         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
186     }
187 
188     @Override
189     public void visitToken(DetailAST ast) {
190         final FullIdent imp;
191         if (ast.getType() == TokenTypes.IMPORT) {
192             imp = FullIdent.createFullIdentBelow(ast);
193         }
194         else {
195             imp = FullIdent.createFullIdent(
196                 ast.getFirstChild().getNextSibling());
197         }
198         final String importText = imp.getText();
199         if (isIllegalImport(importText)) {
200             log(ast, MSG_KEY, importText);
201         }
202     }
203 
204     /**
205      * Checks if an import matches one of the regular expressions
206      * for illegal packages or illegal class names.
207      *
208      * @param importText the argument of the import keyword
209      * @return if {@code importText} matches one of the regular expressions
210      *         for illegal packages or illegal class names
211      */
212     private boolean isIllegalImportByRegularExpressions(String importText) {
213         boolean result = false;
214         for (Pattern pattern : illegalPkgsRegexps) {
215             if (pattern.matcher(importText).matches()) {
216                 result = true;
217                 break;
218             }
219         }
220         for (Pattern pattern : illegalClassesRegexps) {
221             if (pattern.matcher(importText).matches()) {
222                 result = true;
223                 break;
224             }
225         }
226         return result;
227     }
228 
229     /**
230      * Checks if an import is from a package or class name that must not be used.
231      *
232      * @param importText the argument of the import keyword
233      * @return if {@code importText} contains an illegal package prefix or equals illegal class name
234      */
235     private boolean isIllegalImportByPackagesAndClassNames(String importText) {
236         boolean result = false;
237         for (String element : illegalPkgs) {
238             if (importText.startsWith(element + ".")) {
239                 result = true;
240                 break;
241             }
242         }
243         if (illegalClasses != null) {
244             for (String element : illegalClasses) {
245                 if (importText.equals(element)) {
246                     result = true;
247                     break;
248                 }
249             }
250         }
251         return result;
252     }
253 
254     /**
255      * Checks if an import is from a package or class name that must not be used.
256      *
257      * @param importText the argument of the import keyword
258      * @return if {@code importText} is illegal import
259      */
260     private boolean isIllegalImport(String importText) {
261         final boolean result;
262         if (regexp) {
263             result = isIllegalImportByRegularExpressions(importText);
264         }
265         else {
266             result = isIllegalImportByPackagesAndClassNames(importText);
267         }
268         return result;
269     }
270 
271 }