001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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/technetwork/java/faq-sun-packages-142232.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 /** Specify class names to reject, if <b>regexp</b> property is not set, 289 * checks if import equals class name. If <b>regexp</b> property is set, 290 * then list of class names will be interpreted as regular expressions. 291 * Note, all properties for match will be used. */ 292 private String[] illegalClasses; 293 294 /** 295 * Control whether the {@code illegalPkgs} and {@code illegalClasses} 296 * should be interpreted as regular expressions. 297 */ 298 private boolean regexp; 299 300 /** 301 * Creates a new {@code IllegalImportCheck} instance. 302 */ 303 public IllegalImportCheck() { 304 setIllegalPkgs("sun"); 305 } 306 307 /** 308 * Setter to specify packages to reject, if <b>regexp</b> property is not set, 309 * checks if import is the part of package. If <b>regexp</b> property is set, 310 * then list of packages will be interpreted as regular expressions. 311 * Note, all properties for match will be used. 312 * 313 * @param from array of illegal packages 314 * @noinspection WeakerAccess 315 */ 316 public final void setIllegalPkgs(String... from) { 317 illegalPkgs = from.clone(); 318 illegalPkgsRegexps.clear(); 319 for (String illegalPkg : illegalPkgs) { 320 illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*")); 321 } 322 } 323 324 /** 325 * Setter to specify class names to reject, if <b>regexp</b> property is not 326 * set, checks if import equals class name. If <b>regexp</b> property is set, 327 * then list of class names will be interpreted as regular expressions. 328 * Note, all properties for match will be used. 329 * 330 * @param from array of illegal classes 331 */ 332 public void setIllegalClasses(String... from) { 333 illegalClasses = from.clone(); 334 for (String illegalClass : illegalClasses) { 335 illegalClassesRegexps.add(CommonUtil.createPattern(illegalClass)); 336 } 337 } 338 339 /** 340 * Setter to control whether the {@code illegalPkgs} and {@code illegalClasses} 341 * should be interpreted as regular expressions. 342 * 343 * @param regexp a {@code Boolean} value 344 */ 345 public void setRegexp(boolean regexp) { 346 this.regexp = regexp; 347 } 348 349 @Override 350 public int[] getDefaultTokens() { 351 return getRequiredTokens(); 352 } 353 354 @Override 355 public int[] getAcceptableTokens() { 356 return getRequiredTokens(); 357 } 358 359 @Override 360 public int[] getRequiredTokens() { 361 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 362 } 363 364 @Override 365 public void visitToken(DetailAST ast) { 366 final FullIdent imp; 367 if (ast.getType() == TokenTypes.IMPORT) { 368 imp = FullIdent.createFullIdentBelow(ast); 369 } 370 else { 371 imp = FullIdent.createFullIdent( 372 ast.getFirstChild().getNextSibling()); 373 } 374 if (isIllegalImport(imp.getText())) { 375 log(ast, 376 MSG_KEY, 377 imp.getText()); 378 } 379 } 380 381 /** 382 * Checks if an import matches one of the regular expressions 383 * for illegal packages or illegal class names. 384 * @param importText the argument of the import keyword 385 * @return if {@code importText} matches one of the regular expressions 386 * for illegal packages or illegal class names 387 */ 388 private boolean isIllegalImportByRegularExpressions(String importText) { 389 boolean result = false; 390 for (Pattern pattern : illegalPkgsRegexps) { 391 if (pattern.matcher(importText).matches()) { 392 result = true; 393 break; 394 } 395 } 396 if (!result) { 397 for (Pattern pattern : illegalClassesRegexps) { 398 if (pattern.matcher(importText).matches()) { 399 result = true; 400 break; 401 } 402 } 403 } 404 return result; 405 } 406 407 /** 408 * Checks if an import is from a package or class name that must not be used. 409 * @param importText the argument of the import keyword 410 * @return if {@code importText} contains an illegal package prefix or equals illegal class name 411 */ 412 private boolean isIllegalImportByPackagesAndClassNames(String importText) { 413 boolean result = false; 414 for (String element : illegalPkgs) { 415 if (importText.startsWith(element + ".")) { 416 result = true; 417 break; 418 } 419 } 420 if (!result && illegalClasses != null) { 421 for (String element : illegalClasses) { 422 if (importText.equals(element)) { 423 result = true; 424 break; 425 } 426 } 427 } 428 return result; 429 } 430 431 /** 432 * Checks if an import is from a package or class name that must not be used. 433 * @param importText the argument of the import keyword 434 * @return if {@code importText} is illegal import 435 */ 436 private boolean isIllegalImport(String importText) { 437 final boolean result; 438 if (regexp) { 439 result = isIllegalImportByRegularExpressions(importText); 440 } 441 else { 442 result = isIllegalImportByPackagesAndClassNames(importText); 443 } 444 return result; 445 } 446 447}