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   * <div>
35   * Checks for imports from a set of illegal packages.
36   * </div>
37   *
38   * <p>
39   * Note: By default, the check rejects all {@code sun.*} packages since programs
40   * that contain direct calls to the {@code sun.*} packages are
41   * <a href="https://www.oracle.com/java/technologies/faq-sun-packages.html">
42   * "not guaranteed to work on all Java-compatible platforms"</a>. To reject other
43   * packages, set property {@code illegalPkgs} to a list of the illegal packages.
44   * </p>
45   * <ul>
46   * <li>
47   * Property {@code illegalClasses} - Specify class names to reject, if <b>regexp</b>
48   * property is not set, checks if import equals class name. If <b>regexp</b>
49   * property is set, then list of class names will be interpreted as regular expressions.
50   * Note, all properties for match will be used.
51   * Type is {@code java.lang.String[]}.
52   * Default value is {@code ""}.
53   * </li>
54   * <li>
55   * Property {@code illegalPkgs} - Specify packages to reject, if <b>regexp</b>
56   * property is not set, checks if import is the part of package. If <b>regexp</b>
57   * property is set, then list of packages will be interpreted as regular expressions.
58   * Note, all properties for match will be used.
59   * Type is {@code java.lang.String[]}.
60   * Default value is {@code sun}.
61   * </li>
62   * <li>
63   * Property {@code regexp} - Control whether the {@code illegalPkgs} and
64   * {@code illegalClasses} should be interpreted as regular expressions.
65   * Type is {@code boolean}.
66   * Default value is {@code false}.
67   * </li>
68   * </ul>
69   *
70   * <p>
71   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
72   * </p>
73   *
74   * <p>
75   * Violation Message Keys:
76   * </p>
77   * <ul>
78   * <li>
79   * {@code import.illegal}
80   * </li>
81   * </ul>
82   *
83   * @since 3.0
84   */
85  @StatelessCheck
86  public class IllegalImportCheck
87      extends AbstractCheck {
88  
89      /**
90       * A key is pointing to the warning message text in "messages.properties"
91       * file.
92       */
93      public static final String MSG_KEY = "import.illegal";
94  
95      /** The compiled regular expressions for packages. */
96      private final List<Pattern> illegalPkgsRegexps = new ArrayList<>();
97  
98      /** The compiled regular expressions for classes. */
99      private final List<Pattern> illegalClassesRegexps = new ArrayList<>();
100 
101     /**
102      * Specify packages to reject, if <b>regexp</b> property is not set, checks
103      * if import is the part of package. If <b>regexp</b> property is set, then
104      * list of packages will be interpreted as regular expressions.
105      * Note, all properties for match will be used.
106      */
107     private String[] illegalPkgs;
108 
109     /**
110      * Specify class names to reject, if <b>regexp</b> property is not set,
111      * checks if import equals class name. If <b>regexp</b> property is set,
112      * then list of class names will be interpreted as regular expressions.
113      * Note, all properties for match will be used.
114      */
115     private String[] illegalClasses;
116 
117     /**
118      * Control whether the {@code illegalPkgs} and {@code illegalClasses}
119      * should be interpreted as regular expressions.
120      */
121     private boolean regexp;
122 
123     /**
124      * Creates a new {@code IllegalImportCheck} instance.
125      */
126     public IllegalImportCheck() {
127         setIllegalPkgs("sun");
128     }
129 
130     /**
131      * Setter to specify packages to reject, if <b>regexp</b> property is not set,
132      * checks if import is the part of package. If <b>regexp</b> property is set,
133      * then list of packages will be interpreted as regular expressions.
134      * Note, all properties for match will be used.
135      *
136      * @param from illegal packages
137      * @noinspection WeakerAccess
138      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
139      * @since 3.0
140      */
141     public final void setIllegalPkgs(String... from) {
142         illegalPkgs = from.clone();
143         illegalPkgsRegexps.clear();
144         for (String illegalPkg : illegalPkgs) {
145             illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
146         }
147     }
148 
149     /**
150      * Setter to specify class names to reject, if <b>regexp</b> property is not
151      * set, checks if import equals class name. If <b>regexp</b> property is set,
152      * then list of class names will be interpreted as regular expressions.
153      * Note, all properties for match will be used.
154      *
155      * @param from illegal classes
156      * @since 7.8
157      */
158     public void setIllegalClasses(String... from) {
159         illegalClasses = from.clone();
160         for (String illegalClass : illegalClasses) {
161             illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
162         }
163     }
164 
165     /**
166      * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses}
167      * should be interpreted as regular expressions.
168      *
169      * @param regexp a {@code Boolean} value
170      * @since 7.8
171      */
172     public void setRegexp(boolean regexp) {
173         this.regexp = regexp;
174     }
175 
176     @Override
177     public int[] getDefaultTokens() {
178         return getRequiredTokens();
179     }
180 
181     @Override
182     public int[] getAcceptableTokens() {
183         return getRequiredTokens();
184     }
185 
186     @Override
187     public int[] getRequiredTokens() {
188         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
189     }
190 
191     @Override
192     public void visitToken(DetailAST ast) {
193         final FullIdent imp;
194         if (ast.getType() == TokenTypes.IMPORT) {
195             imp = FullIdent.createFullIdentBelow(ast);
196         }
197         else {
198             imp = FullIdent.createFullIdent(
199                 ast.getFirstChild().getNextSibling());
200         }
201         final String importText = imp.getText();
202         if (isIllegalImport(importText)) {
203             log(ast, MSG_KEY, importText);
204         }
205     }
206 
207     /**
208      * Checks if an import matches one of the regular expressions
209      * for illegal packages or illegal class names.
210      *
211      * @param importText the argument of the import keyword
212      * @return if {@code importText} matches one of the regular expressions
213      *         for illegal packages or illegal class names
214      */
215     private boolean isIllegalImportByRegularExpressions(String importText) {
216         boolean result = false;
217         for (Pattern pattern : illegalPkgsRegexps) {
218             if (pattern.matcher(importText).matches()) {
219                 result = true;
220                 break;
221             }
222         }
223         for (Pattern pattern : illegalClassesRegexps) {
224             if (pattern.matcher(importText).matches()) {
225                 result = true;
226                 break;
227             }
228         }
229         return result;
230     }
231 
232     /**
233      * Checks if an import is from a package or class name that must not be used.
234      *
235      * @param importText the argument of the import keyword
236      * @return if {@code importText} contains an illegal package prefix or equals illegal class name
237      */
238     private boolean isIllegalImportByPackagesAndClassNames(String importText) {
239         boolean result = false;
240         for (String element : illegalPkgs) {
241             if (importText.startsWith(element + ".")) {
242                 result = true;
243                 break;
244             }
245         }
246         if (illegalClasses != null) {
247             for (String element : illegalClasses) {
248                 if (importText.equals(element)) {
249                     result = true;
250                     break;
251                 }
252             }
253         }
254         return result;
255     }
256 
257     /**
258      * Checks if an import is from a package or class name that must not be used.
259      *
260      * @param importText the argument of the import keyword
261      * @return if {@code importText} is illegal import
262      */
263     private boolean isIllegalImport(String importText) {
264         final boolean result;
265         if (regexp) {
266             result = isIllegalImportByRegularExpressions(importText);
267         }
268         else {
269             result = isIllegalImportByPackagesAndClassNames(importText);
270         }
271         return result;
272     }
273 
274 }