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 * 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 array of illegal packages 330 * @noinspection WeakerAccess 331 */ 332 public final void setIllegalPkgs(String... from) { 333 illegalPkgs = from.clone(); 334 illegalPkgsRegexps.clear(); 335 for (String illegalPkg : illegalPkgs) { 336 illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*")); 337 } 338 } 339 340 /** 341 * Setter to specify class names to reject, if <b>regexp</b> property is not 342 * set, checks if import equals class name. If <b>regexp</b> property is set, 343 * then list of class names will be interpreted as regular expressions. 344 * Note, all properties for match will be used. 345 * 346 * @param from array of illegal classes 347 */ 348 public void setIllegalClasses(String... from) { 349 illegalClasses = from.clone(); 350 for (String illegalClass : illegalClasses) { 351 illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass)); 352 } 353 } 354 355 /** 356 * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses} 357 * should be interpreted as regular expressions. 358 * 359 * @param regexp a {@code Boolean} value 360 */ 361 public void setRegexp(boolean regexp) { 362 this.regexp = regexp; 363 } 364 365 @Override 366 public int[] getDefaultTokens() { 367 return getRequiredTokens(); 368 } 369 370 @Override 371 public int[] getAcceptableTokens() { 372 return getRequiredTokens(); 373 } 374 375 @Override 376 public int[] getRequiredTokens() { 377 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 378 } 379 380 @Override 381 public void visitToken(DetailAST ast) { 382 final FullIdent imp; 383 if (ast.getType() == TokenTypes.IMPORT) { 384 imp = FullIdent.createFullIdentBelow(ast); 385 } 386 else { 387 imp = FullIdent.createFullIdent( 388 ast.getFirstChild().getNextSibling()); 389 } 390 if (isIllegalImport(imp.getText())) { 391 log(ast, 392 MSG_KEY, 393 imp.getText()); 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 if (!result) { 414 for (Pattern pattern : illegalClassesRegexps) { 415 if (pattern.matcher(importText).matches()) { 416 result = true; 417 break; 418 } 419 } 420 } 421 return result; 422 } 423 424 /** 425 * Checks if an import is from a package or class name that must not be used. 426 * 427 * @param importText the argument of the import keyword 428 * @return if {@code importText} contains an illegal package prefix or equals illegal class name 429 */ 430 private boolean isIllegalImportByPackagesAndClassNames(String importText) { 431 boolean result = false; 432 for (String element : illegalPkgs) { 433 if (importText.startsWith(element + ".")) { 434 result = true; 435 break; 436 } 437 } 438 if (illegalClasses != null) { 439 for (String element : illegalClasses) { 440 if (importText.equals(element)) { 441 result = true; 442 break; 443 } 444 } 445 } 446 return result; 447 } 448 449 /** 450 * Checks if an import is from a package or class name that must not be used. 451 * 452 * @param importText the argument of the import keyword 453 * @return if {@code importText} is illegal import 454 */ 455 private boolean isIllegalImport(String importText) { 456 final boolean result; 457 if (regexp) { 458 result = isIllegalImportByRegularExpressions(importText); 459 } 460 else { 461 result = isIllegalImportByPackagesAndClassNames(importText); 462 } 463 return result; 464 } 465 466}