1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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   * By default, the check rejects all {@code sun.*} packages
37   * since programs that contain direct calls to the {@code sun.*} packages
38   * are <a href="https://www.oracle.com/technetwork/java/faq-sun-packages-142232.html">
39   * not 100% Pure Java</a>.
40   * </p>
41   * <p>
42   * To reject other packages, set property illegalPkgs to a comma-separated
43   * list of the illegal packages.
44   * </p>
45   * <p>
46   * An example of how to configure the check is:
47   * </p>
48   * <pre>
49   * &lt;module name="IllegalImport"/&gt;
50   * </pre>
51   * <p>
52   * An example of how to configure the check so that it rejects packages
53   * {@code java.io.*} and {@code java.sql.*} is
54   * </p>
55   * <pre>
56   * &lt;module name="IllegalImport"&gt;
57   *    &lt;property name="illegalPkgs" value="java.io, java.sql"/&gt;
58   * &lt;/module&gt;
59   *
60   * Compatible with Java 1.5 source.
61   *
62   * </pre>
63   */
64  @StatelessCheck
65  public class IllegalImportCheck
66      extends AbstractCheck {
67  
68      /**
69       * A key is pointing to the warning message text in "messages.properties"
70       * file.
71       */
72      public static final String MSG_KEY = "import.illegal";
73  
74      /** The compiled regular expressions for packages. */
75      private final List<Pattern> illegalPkgsRegexps = new ArrayList<>();
76  
77      /** The compiled regular expressions for classes. */
78      private final List<Pattern> illegalClassesRegexps = new ArrayList<>();
79  
80      /** List of illegal packages. */
81      private String[] illegalPkgs;
82  
83      /** List of illegal classes. */
84      private String[] illegalClasses;
85  
86      /**
87       * Whether the packages or class names
88       * should be interpreted as regular expressions.
89       */
90      private boolean regexp;
91  
92      /**
93       * Creates a new {@code IllegalImportCheck} instance.
94       */
95      public IllegalImportCheck() {
96          setIllegalPkgs("sun");
97      }
98  
99      /**
100      * Set the list of illegal packages.
101      * @param from array of illegal packages
102      * @noinspection WeakerAccess
103      */
104     public final void setIllegalPkgs(String... from) {
105         illegalPkgs = from.clone();
106         illegalPkgsRegexps.clear();
107         for (String illegalPkg : illegalPkgs) {
108             illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
109         }
110     }
111 
112     /**
113      * Set the list of illegal classes.
114      * @param from array of illegal classes
115      */
116     public void setIllegalClasses(String... from) {
117         illegalClasses = from.clone();
118         for (String illegalClass : illegalClasses) {
119             illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass));
120         }
121     }
122 
123     /**
124      * Controls whether the packages or class names
125      * should be interpreted as regular expressions.
126      * @param regexp a {@code Boolean} value
127      */
128     public void setRegexp(boolean regexp) {
129         this.regexp = regexp;
130     }
131 
132     @Override
133     public int[] getDefaultTokens() {
134         return getRequiredTokens();
135     }
136 
137     @Override
138     public int[] getAcceptableTokens() {
139         return getRequiredTokens();
140     }
141 
142     @Override
143     public int[] getRequiredTokens() {
144         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
145     }
146 
147     @Override
148     public void visitToken(DetailAST ast) {
149         final FullIdent imp;
150         if (ast.getType() == TokenTypes.IMPORT) {
151             imp = FullIdent.createFullIdentBelow(ast);
152         }
153         else {
154             imp = FullIdent.createFullIdent(
155                 ast.getFirstChild().getNextSibling());
156         }
157         if (isIllegalImport(imp.getText())) {
158             log(ast,
159                 MSG_KEY,
160                 imp.getText());
161         }
162     }
163 
164     /**
165      * Checks if an import matches one of the regular expressions
166      * for illegal packages or illegal class names.
167      * @param importText the argument of the import keyword
168      * @return if {@code importText} matches one of the regular expressions
169      *         for illegal packages or illegal class names
170      */
171     private boolean isIllegalImportByRegularExpressions(String importText) {
172         boolean result = false;
173         for (Pattern pattern : illegalPkgsRegexps) {
174             if (pattern.matcher(importText).matches()) {
175                 result = true;
176                 break;
177             }
178         }
179         if (!result) {
180             for (Pattern pattern : illegalClassesRegexps) {
181                 if (pattern.matcher(importText).matches()) {
182                     result = true;
183                     break;
184                 }
185             }
186         }
187         return result;
188     }
189 
190     /**
191      * Checks if an import is from a package or class name that must not be used.
192      * @param importText the argument of the import keyword
193      * @return if {@code importText} contains an illegal package prefix or equals illegal class name
194      */
195     private boolean isIllegalImportByPackagesAndClassNames(String importText) {
196         boolean result = false;
197         for (String element : illegalPkgs) {
198             if (importText.startsWith(element + ".")) {
199                 result = true;
200                 break;
201             }
202         }
203         if (!result && illegalClasses != null) {
204             for (String element : illegalClasses) {
205                 if (importText.equals(element)) {
206                     result = true;
207                     break;
208                 }
209             }
210         }
211         return result;
212     }
213 
214     /**
215      * Checks if an import is from a package or class name that must not be used.
216      * @param importText the argument of the import keyword
217      * @return if {@code importText} is illegal import
218      */
219     private boolean isIllegalImport(String importText) {
220         final boolean result;
221         if (regexp) {
222             result = isIllegalImportByRegularExpressions(importText);
223         }
224         else {
225             result = isIllegalImportByPackagesAndClassNames(importText);
226         }
227         return result;
228     }
229 
230 }