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 * <module name="IllegalImport"/> 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 * <module name="IllegalImport"> 076 * <property name="illegalPkgs" value="java.io, java.sql"/> 077 * </module> 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 * <module name="IllegalImport"> 119 * <property name="illegalClasses" 120 * value="java.util.Date, java.sql.Connection"/> 121 * </module> 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 * <module name="IllegalImport"> 165 * <property name="regexp" value="true"/> 166 * <property name="illegalPkgs" value="java\.util"/> 167 * </module> 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 * <module name="IllegalImport"> 218 * <property name="regexp" value="true"/> 219 * <property name="illegalClasses" 220 * value="^java\.util\.(List|Arrays), ^java\.sql\.Connection"/> 221 * </module> 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}