001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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 * Type is {@code java.lang.String[]}. 051 * Default value is {@code sun}. 052 * </li> 053 * <li> 054 * Property {@code illegalClasses} - Specify class names to reject, if <b>regexp</b> 055 * property is not set, checks if import equals class name. If <b>regexp</b> 056 * property is set, then list of class names will be interpreted as regular expressions. 057 * Note, all properties for match will be used. 058 * Type is {@code java.lang.String[]}. 059 * Default value is {@code ""}. 060 * </li> 061 * <li> 062 * Property {@code regexp} - Control whether the {@code illegalPkgs} and 063 * {@code illegalClasses} should be interpreted as regular expressions. 064 * Type is {@code boolean}. 065 * Default value is {@code false}. 066 * </li> 067 * </ul> 068 * <p> 069 * To configure the check: 070 * </p> 071 * <pre> 072 * <module name="IllegalImport"/> 073 * </pre> 074 * <p> 075 * To configure the check so that it rejects packages {@code java.io.*} and {@code java.sql.*}: 076 * </p> 077 * <pre> 078 * <module name="IllegalImport"> 079 * <property name="illegalPkgs" value="java.io, java.sql"/> 080 * </module> 081 * </pre> 082 * <p> 083 * The following example shows class with no illegal imports 084 * </p> 085 * <pre> 086 * import java.lang.ArithmeticException; 087 * import java.util.List; 088 * import java.util.Enumeration; 089 * import java.util.Arrays; 090 * import sun.applet.*; 091 * 092 * public class InputIllegalImport { } 093 * </pre> 094 * <p> 095 * The following example shows class with two illegal imports 096 * </p> 097 * <ul> 098 * <li> 099 * <b>java.io.*</b>, illegalPkgs property contains this package 100 * </li> 101 * <li> 102 * <b>java.sql.Connection</b> is inside java.sql package 103 * </li> 104 * </ul> 105 * <pre> 106 * import java.io.*; // violation 107 * import java.lang.ArithmeticException; 108 * import java.sql.Connection; // violation 109 * import java.util.List; 110 * import java.util.Enumeration; 111 * import java.util.Arrays; 112 * import sun.applet.*; 113 * 114 * public class InputIllegalImport { } 115 * </pre> 116 * <p> 117 * To configure the check so that it rejects classes {@code java.util.Date} and 118 * {@code java.sql.Connection}: 119 * </p> 120 * <pre> 121 * <module name="IllegalImport"> 122 * <property name="illegalClasses" 123 * value="java.util.Date, java.sql.Connection"/> 124 * </module> 125 * </pre> 126 * <p> 127 * The following example shows class with no illegal imports 128 * </p> 129 * <pre> 130 * import java.io.*; 131 * import java.lang.ArithmeticException; 132 * import java.util.List; 133 * import java.util.Enumeration; 134 * import java.util.Arrays; 135 * import sun.applet.*; 136 * 137 * public class InputIllegalImport { } 138 * </pre> 139 * <p> 140 * The following example shows class with two illegal imports 141 * </p> 142 * <ul> 143 * <li> 144 * <b>java.sql.Connection</b>, illegalClasses property contains this class 145 * </li> 146 * <li> 147 * <b>java.util.Date</b>, illegalClasses property contains this class 148 * </li> 149 * </ul> 150 * <pre> 151 * import java.io.*; 152 * import java.lang.ArithmeticException; 153 * import java.sql.Connection; // violation 154 * import java.util.List; 155 * import java.util.Enumeration; 156 * import java.util.Arrays; 157 * import java.util.Date; // violation 158 * import sun.applet.*; 159 * 160 * public class InputIllegalImport { } 161 * </pre> 162 * <p> 163 * To configure the check so that it rejects packages not satisfying to regular 164 * expression {@code java\.util}: 165 * </p> 166 * <pre> 167 * <module name="IllegalImport"> 168 * <property name="regexp" value="true"/> 169 * <property name="illegalPkgs" value="java\.util"/> 170 * </module> 171 * </pre> 172 * <p> 173 * The following example shows class with no illegal imports 174 * </p> 175 * <pre> 176 * import java.io.*; 177 * import java.lang.ArithmeticException; 178 * import java.sql.Connection; 179 * import sun.applet.*; 180 * 181 * public class InputIllegalImport { } 182 * </pre> 183 * <p> 184 * The following example shows class with four illegal imports 185 * </p> 186 * <ul> 187 * <li> 188 * <b>java.util.List</b> 189 * </li> 190 * <li> 191 * <b>java.util.Enumeration</b> 192 * </li> 193 * <li> 194 * <b>java.util.Arrays</b> 195 * </li> 196 * <li> 197 * <b>java.util.Date</b> 198 * </li> 199 * </ul> 200 * <p> 201 * All four imports match "java\.util" regular expression 202 * </p> 203 * <pre> 204 * import java.io.*; 205 * import java.lang.ArithmeticException; 206 * import java.sql.Connection; 207 * import java.util.List; // violation 208 * import java.util.Enumeration; // violation 209 * import java.util.Arrays; // violation 210 * import java.util.Date; // violation 211 * import sun.applet.*; 212 * 213 * public class InputIllegalImport { } 214 * </pre> 215 * <p> 216 * To configure the check so that it rejects class names not satisfying to regular 217 * expression {@code ^java\.util\.(List|Arrays)} and {@code ^java\.sql\.Connection}: 218 * </p> 219 * <pre> 220 * <module name="IllegalImport"> 221 * <property name="regexp" value="true"/> 222 * <property name="illegalClasses" 223 * value="^java\.util\.(List|Arrays), ^java\.sql\.Connection"/> 224 * </module> 225 * </pre> 226 * <p> 227 * The following example shows class with no illegal imports 228 * </p> 229 * <pre> 230 * import java.io.*; 231 * import java.lang.ArithmeticException; 232 * import java.util.Enumeration; 233 * import java.util.Date; 234 * import sun.applet.*; 235 * 236 * public class InputIllegalImport { } 237 * </pre> 238 * <p> 239 * The following example shows class with three illegal imports 240 * </p> 241 * <ul> 242 * <li> 243 * <b>java.sql.Connection</b> matches "^java\.sql\.Connection" regular expression 244 * </li> 245 * <li> 246 * <b>java.util.List</b> matches "^java\.util\.(List|Arrays)" regular expression 247 * </li> 248 * <li> 249 * <b>java.util.Arrays</b> matches "^java\.util\.(List|Arrays)" regular expression 250 * </li> 251 * </ul> 252 * <pre> 253 * import java.io.*; 254 * import java.lang.ArithmeticException; 255 * import java.sql.Connection; // violation 256 * import java.util.List; // violation 257 * import java.util.Enumeration; 258 * import java.util.Arrays; // violation 259 * import java.util.Date; 260 * import sun.applet.*; 261 * 262 * public class InputIllegalImport { } 263 * </pre> 264 * <p> 265 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 266 * </p> 267 * <p> 268 * Violation Message Keys: 269 * </p> 270 * <ul> 271 * <li> 272 * {@code import.illegal} 273 * </li> 274 * </ul> 275 * 276 * @since 3.0 277 */ 278@StatelessCheck 279public class IllegalImportCheck 280 extends AbstractCheck { 281 282 /** 283 * A key is pointing to the warning message text in "messages.properties" 284 * file. 285 */ 286 public static final String MSG_KEY = "import.illegal"; 287 288 /** The compiled regular expressions for packages. */ 289 private final List<Pattern> illegalPkgsRegexps = new ArrayList<>(); 290 291 /** The compiled regular expressions for classes. */ 292 private final List<Pattern> illegalClassesRegexps = new ArrayList<>(); 293 294 /** 295 * Specify packages to reject, if <b>regexp</b> property is not set, checks 296 * if import is the part of package. If <b>regexp</b> property is set, then 297 * list of packages will be interpreted as regular expressions. 298 * Note, all properties for match will be used. 299 */ 300 private String[] illegalPkgs; 301 302 /** 303 * Specify class names to reject, if <b>regexp</b> property is not set, 304 * checks if import equals class name. If <b>regexp</b> property is set, 305 * then list of class names will be interpreted as regular expressions. 306 * Note, all properties for match will be used. 307 */ 308 private String[] illegalClasses; 309 310 /** 311 * Control whether the {@code illegalPkgs} and {@code illegalClasses} 312 * should be interpreted as regular expressions. 313 */ 314 private boolean regexp; 315 316 /** 317 * Creates a new {@code IllegalImportCheck} instance. 318 */ 319 public IllegalImportCheck() { 320 setIllegalPkgs("sun"); 321 } 322 323 /** 324 * Setter to specify packages to reject, if <b>regexp</b> property is not set, 325 * checks if import is the part of package. If <b>regexp</b> property is set, 326 * then list of packages will be interpreted as regular expressions. 327 * Note, all properties for match will be used. 328 * 329 * @param from illegal packages 330 * @noinspection WeakerAccess 331 * @noinspectionreason WeakerAccess - we avoid 'protected' when possible 332 */ 333 public final void setIllegalPkgs(String... from) { 334 illegalPkgs = from.clone(); 335 illegalPkgsRegexps.clear(); 336 for (String illegalPkg : illegalPkgs) { 337 illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*")); 338 } 339 } 340 341 /** 342 * Setter to specify class names to reject, if <b>regexp</b> property is not 343 * set, checks if import equals class name. If <b>regexp</b> property is set, 344 * then list of class names will be interpreted as regular expressions. 345 * Note, all properties for match will be used. 346 * 347 * @param from illegal classes 348 */ 349 public void setIllegalClasses(String... from) { 350 illegalClasses = from.clone(); 351 for (String illegalClass : illegalClasses) { 352 illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass)); 353 } 354 } 355 356 /** 357 * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses} 358 * should be interpreted as regular expressions. 359 * 360 * @param regexp a {@code Boolean} value 361 */ 362 public void setRegexp(boolean regexp) { 363 this.regexp = regexp; 364 } 365 366 @Override 367 public int[] getDefaultTokens() { 368 return getRequiredTokens(); 369 } 370 371 @Override 372 public int[] getAcceptableTokens() { 373 return getRequiredTokens(); 374 } 375 376 @Override 377 public int[] getRequiredTokens() { 378 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 379 } 380 381 @Override 382 public void visitToken(DetailAST ast) { 383 final FullIdent imp; 384 if (ast.getType() == TokenTypes.IMPORT) { 385 imp = FullIdent.createFullIdentBelow(ast); 386 } 387 else { 388 imp = FullIdent.createFullIdent( 389 ast.getFirstChild().getNextSibling()); 390 } 391 final String importText = imp.getText(); 392 if (isIllegalImport(importText)) { 393 log(ast, MSG_KEY, importText); 394 } 395 } 396 397 /** 398 * Checks if an import matches one of the regular expressions 399 * for illegal packages or illegal class names. 400 * 401 * @param importText the argument of the import keyword 402 * @return if {@code importText} matches one of the regular expressions 403 * for illegal packages or illegal class names 404 */ 405 private boolean isIllegalImportByRegularExpressions(String importText) { 406 boolean result = false; 407 for (Pattern pattern : illegalPkgsRegexps) { 408 if (pattern.matcher(importText).matches()) { 409 result = true; 410 break; 411 } 412 } 413 for (Pattern pattern : illegalClassesRegexps) { 414 if (pattern.matcher(importText).matches()) { 415 result = true; 416 break; 417 } 418 } 419 return result; 420 } 421 422 /** 423 * Checks if an import is from a package or class name that must not be used. 424 * 425 * @param importText the argument of the import keyword 426 * @return if {@code importText} contains an illegal package prefix or equals illegal class name 427 */ 428 private boolean isIllegalImportByPackagesAndClassNames(String importText) { 429 boolean result = false; 430 for (String element : illegalPkgs) { 431 if (importText.startsWith(element + ".")) { 432 result = true; 433 break; 434 } 435 } 436 if (illegalClasses != null) { 437 for (String element : illegalClasses) { 438 if (importText.equals(element)) { 439 result = true; 440 break; 441 } 442 } 443 } 444 return result; 445 } 446 447 /** 448 * Checks if an import is from a package or class name that must not be used. 449 * 450 * @param importText the argument of the import keyword 451 * @return if {@code importText} is illegal import 452 */ 453 private boolean isIllegalImport(String importText) { 454 final boolean result; 455 if (regexp) { 456 result = isIllegalImportByRegularExpressions(importText); 457 } 458 else { 459 result = isIllegalImportByPackagesAndClassNames(importText); 460 } 461 return result; 462 } 463 464}