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.net.URI; 023import java.util.Collections; 024import java.util.Set; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder; 032import com.puppycrawl.tools.checkstyle.api.FullIdent; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034 035/** 036 * <p> 037 * Controls what can be imported in each package and file. Useful for ensuring 038 * that application layering rules are not violated, especially on large projects. 039 * </p> 040 * <p> 041 * You can control imports based on the a package name or based on the file name. 042 * When controlling packages, all files and sub-packages in the declared package 043 * will be controlled by this check. To specify differences between a main package 044 * and a sub-package, you must define the sub-package inside the main package. 045 * When controlling file, only the file name is considered and only files processed by 046 * <a href="https://checkstyle.org/config.html#TreeWalker">TreeWalker</a>. 047 * The file's extension is ignored. 048 * </p> 049 * <p> 050 * Short description of the behaviour: 051 * </p> 052 * <ul> 053 * <li> 054 * Check starts checking from the longest matching subpackage (later 'current subpackage') or 055 * the first file name match described inside import control file to package defined in class file. 056 * <ul> 057 * <li> 058 * The longest matching subpackage is found by starting with the root package and 059 * examining if the any of the sub-packages or file definitions match the current 060 * class' package or file name. 061 * </li> 062 * <li> 063 * If a file name is matched first, that is considered the longest match and becomes 064 * the current file/subpackage. 065 * </li> 066 * <li> 067 * If another subpackage is matched, then it's subpackages and file names are examined 068 * for the next longest match and the process repeats recursively. 069 * </li> 070 * <li> 071 * If no subpackages or file names are matched, the current subpackage is then used. 072 * </li> 073 * </ul> 074 * </li> 075 * <li> 076 * Order of rules in the same subpackage/root are defined by the order of declaration 077 * in the XML file, which is from top (first) to bottom (last). 078 * </li> 079 * <li> 080 * If there is matching allow/disallow rule inside the current file/subpackage 081 * then the Check returns the first "allowed" or "disallowed" message. 082 * </li> 083 * <li> 084 * If there is no matching allow/disallow rule inside the current file/subpackage 085 * then it continues checking in the parent subpackage. 086 * </li> 087 * <li> 088 * If there is no matching allow/disallow rule in any of the files/subpackages, 089 * including the root level (import-control), then the import is disallowed by default. 090 * </li> 091 * </ul> 092 * <p> 093 * The DTD for a import control XML document is at 094 * <a href="https://checkstyle.org/dtds/import_control_1_4.dtd"> 095 * https://checkstyle.org/dtds/import_control_1_4.dtd</a>. 096 * It contains documentation on each of the elements and attributes. 097 * </p> 098 * <p> 099 * The check validates a XML document when it loads the document. To validate against 100 * the above DTD, include the following document type declaration in your XML document: 101 * </p> 102 * <pre> 103 * <!DOCTYPE import-control PUBLIC 104 * "-//Checkstyle//DTD ImportControl Configuration 1.4//EN" 105 * "https://checkstyle.org/dtds/import_control_1_4.dtd"> 106 * </pre> 107 * <ul> 108 * <li> 109 * Property {@code file} - Specify the location of the file containing the 110 * import control configuration. It can be a regular file, URL or resource path. 111 * It will try loading the path as a URL first, then as a file, and finally as a resource. 112 * Default value is {@code null}. 113 * </li> 114 * <li> 115 * Property {@code path} - Specify the regular expression of file paths to which 116 * this check should apply. Files that don't match the pattern will not be checked. 117 * The pattern will be matched against the full absolute file path. 118 * Default value is {@code ".*"}. 119 * </li> 120 * </ul> 121 * <p> 122 * To configure the check using an import control file called "config/import-control.xml", 123 * then have the following: 124 * </p> 125 * <pre> 126 * <module name="ImportControl"> 127 * <property name="file" value="config/import-control.xml"/> 128 * </module> 129 * </pre> 130 * <p> 131 * To configure the check to only check the "src/main" directory using an import 132 * control file called "config/import-control.xml", then have the following: 133 * </p> 134 * <pre> 135 * <module name="ImportControl"> 136 * <property name="file" value="config/import-control.xml"/> 137 * <property name="path" value="^.*[\\/]src[\\/]main[\\/].*$"/> 138 * </module> 139 * </pre> 140 * <p> 141 * In the example below access to package {@code com.puppycrawl.tools.checkstyle.checks} 142 * and its subpackages is allowed from anywhere in {@code com.puppycrawl.tools.checkstyle} 143 * except from the {@code filters} subpackage where access to all {@code check}'s 144 * subpackages is disallowed. Two {@code java.lang.ref} classes are allowed by virtue 145 * of one regular expression instead of listing them in two separate allow rules 146 * (as it is done with the {@code Files} and {@code ClassPath} classes). 147 * </p> 148 * <pre> 149 * <import-control pkg="com.puppycrawl.tools.checkstyle"> 150 * <disallow pkg="sun"/> 151 * <allow pkg="com.puppycrawl.tools.checkstyle.api"/> 152 * <allow pkg="com.puppycrawl.tools.checkstyle.checks"/> 153 * <allow class="com.google.common.io.Files"/> 154 * <allow class="com.google.common.reflect.ClassPath"/> 155 * <subpackage name="filters"> 156 * <allow class="java\.lang\.ref\.(Weak|Soft)Reference" 157 * regex="true"/> 158 * <disallow pkg="com\.puppycrawl\.tools\.checkstyle\.checks\.[^.]+" 159 * regex="true"/> 160 * <disallow pkg="com.puppycrawl.tools.checkstyle.ant"/> 161 * <disallow pkg="com.puppycrawl.tools.checkstyle.gui"/> 162 * </subpackage> 163 * <subpackage name="dao"> 164 * <disallow pkg="javax.swing" exact-match="true"/> 165 * </subpackage> 166 * </import-control> 167 * </pre> 168 * <p> 169 * In the next example regular expressions are used to enforce a layering rule: 170 * In all {@code dao} packages it is not allowed to access UI layer code ({@code ui}, 171 * {@code awt}, and {@code swing}). On the other hand it is not allowed to directly 172 * access {@code dao} and {@code service} layer from {@code ui} packages. 173 * The root package is also a regular expression that is used to handle old and 174 * new domain name with the same rules. 175 * </p> 176 * <pre> 177 * <import-control pkg="(de.olddomain|de.newdomain)\..*" regex="true"> 178 * <subpackage pkg="[^.]+\.dao" regex="true"> 179 * <disallow pkg=".*\.ui" regex="true"/> 180 * <disallow pkg=".*\.(awt|swing).\.*" regex="true"/> 181 * </subpackage> 182 * <subpackage pkg="[^.]+\.ui" regex="true"> 183 * <disallow pkg=".*\.(dao|service)" regex="true"/> 184 * </subpackage> 185 * </import-control> 186 * </pre> 187 * <p> 188 * In the next examples usage of {@code strategyOnMismatch} property is shown. 189 * This property defines strategy in a case when no matching allow/disallow rule was found. 190 * Property {@code strategyOnMismatch} is attribute of {@code import-control} and 191 * {@code subpackage} tags. Property can have following values for {@code import-control} tag: 192 * </p> 193 * <ul> 194 * <li> 195 * disallowed (default value) - if there is no matching allow/disallow rule in any of 196 * the subpackages, including the root level (import-control), then the import is disallowed. 197 * </li> 198 * <li> 199 * allowed - if there is no matching allow/disallow rule in any of the subpackages, 200 * including the root level, then the import is allowed. 201 * </li> 202 * </ul> 203 * <p> 204 * And following values for {@code subpackage} tags: 205 * </p> 206 * <ul> 207 * <li> 208 * delegateToParent (default value) - if there is no matching allow/disallow rule 209 * inside the current subpackage, then it continues checking in the parent subpackage. 210 * </li> 211 * <li> 212 * allowed - if there is no matching allow/disallow rule inside the current subpackage, 213 * then the import is allowed. 214 * </li> 215 * <li> 216 * disallowed - if there is no matching allow/disallow rule inside the current subpackage, 217 * then the import is disallowed. 218 * </li> 219 * </ul> 220 * <p> 221 * The following example demonstrates usage of {@code strategyOnMismatch} 222 * property for {@code import-control} tag. Here all imports are allowed except 223 * {@code java.awt.Image} and {@code java.io.File} classes. 224 * </p> 225 * <pre> 226 * <import-control pkg="com.puppycrawl.tools.checkstyle.checks" 227 * strategyOnMismatch="allowed"> 228 * <disallow class="java.awt.Image"/> 229 * <disallow class="java.io.File"/> 230 * </import-control> 231 * </pre> 232 * <p> 233 * In the example below, any import is disallowed inside 234 * {@code com.puppycrawl.tools.checkstyle.checks.imports} package except imports 235 * from package {@code javax.swing} and class {@code java.io.File}. 236 * However, any import is allowed in the classes outside of 237 * {@code com.puppycrawl.tools.checkstyle.checks.imports} package. 238 * </p> 239 * <pre> 240 * <import-control pkg="com.puppycrawl.tools.checkstyle.checks" 241 * strategyOnMismatch="allowed"> 242 * <subpackage name="imports" strategyOnMismatch="disallowed"> 243 * <allow pkg="javax.swing"/> 244 * <allow class="java.io.File"/> 245 * </subpackage> 246 * </import-control> 247 * </pre> 248 * <p> 249 * When {@code strategyOnMismatch} has {@code allowed} or {@code disallowed} 250 * value for {@code subpackage} tag, it makes {@code subpackage} isolated from 251 * parent rules. In the next example, if no matching rule was found inside 252 * {@code com.puppycrawl.tools.checkstyle.checks.filters} then it continues 253 * checking in the parent subpackage, while for 254 * {@code com.puppycrawl.tools.checkstyle.checks.imports} import will be allowed by default. 255 * </p> 256 * <pre> 257 * <import-control pkg="com.puppycrawl.tools.checkstyle.checks"> 258 * <allow class="java\.awt\.Image" regex="true"/> 259 * <allow class="java\..*\.File" local-only="true" regex="true"/> 260 * <subpackage name="imports" strategyOnMismatch="allowed"> 261 * <allow pkg="javax\.swing" regex="true"/> 262 * <allow pkg="java\.io" exact-match="true" 263 * local-only="true" regex="true"/> 264 * </subpackage> 265 * <subpackage name="filters"> 266 * <allow class="javax.util.Date"/> 267 * </subpackage> 268 * </import-control> 269 * </pre> 270 * <p> 271 * In the example below, only file names that end with "Panel", "View", or "Dialog" 272 * in the package {@code gui} are disallowed to have imports from {@code com.mycompany.dao} 273 * and any {@code jdbc} package. In addition, only the file name named "PresentationModel" 274 * in the package {@code gui} are disallowed to have imports that match {@code javax.swing.J*}. 275 * All other imports in the package are allowed. 276 * </p> 277 * <pre> 278 * <import-control pkg="com.mycompany.billing"> 279 * <subpackage name="gui" strategyOnMismatch="allowed"> 280 * <file name=".*(Panel|View|Dialog)" regex="true"> 281 * <disallow pkg="com.mycompany.dao"/> 282 * <disallow pkg=".*\.jdbc" regex="true"/> 283 * </file> 284 * <file name="PresentationModel"> 285 * <disallow pkg="javax\.swing\.J.*" regex="true"/> 286 * </file> 287 * </subpackage> 288 * </import-control> 289 * </pre> 290 * <p> 291 * For a real-life import control file look at the file called 292 * <a href="https://github.com/checkstyle/checkstyle/blob/master/config/import-control.xml"> 293 * import-control.xml</a> which is part of the Checkstyle distribution. 294 * </p> 295 * <p id="blacklist-example">Example of blacklist mode</p> 296 * <p> 297 * To have a <b>blacklist mode</b>, it is required to have disallows inside 298 * subpackage and to have allow rule inside parent of the current subpackage 299 * to catch classes and packages those are not in the blacklist. 300 * </p> 301 * <p> 302 * In the example below any import from {@code java.util}({@code java.util.List}, 303 * {@code java.util.Date}) package is allowed except {@code java.util.Map} 304 * inside subpackage {@code com.puppycrawl.tools.checkstyle.filters}. 305 * </p> 306 * <pre> 307 * <import-control pkg="com.puppycrawl.tools.checkstyle"> 308 * <allow pkg="java.util"/> 309 * <subpackage name="filters" > 310 * <disallow class="java.util.Map"/> 311 * </subpackage> 312 * </import-control> 313 * </pre> 314 * <p> 315 * In the next example imports {@code java.util.stream.Stream} and 316 * {@code java.util.stream.Collectors} are disallowed inside 317 * {@code com.puppycrawl.tools.checkstyle.checks.imports} package, but because of 318 * {@code <allow pkg="java.util.stream"/>} every import from 319 * {@code java.util.stream} is allowed except described ones. 320 * </p> 321 * <pre> 322 * <import-control pkg="com.puppycrawl.tools.checkstyle.checks"> 323 * <allow pkg="java.util.stream"/> 324 * <subpackage name="imports"> 325 * <disallow class="java.util.stream.Stream"/> 326 * <disallow class="java.util.stream.Collectors"/> 327 * </subpackage> 328 * </import-control> 329 * </pre> 330 * <pre> 331 * package com.puppycrawl.tools.checkstyle.checks.imports; 332 * 333 * import java.util.stream.Stream; // violation here 334 * import java.util.stream.Collectors; // violation here 335 * import java.util.stream.IntStream; 336 * </pre> 337 * <p> 338 * In the following example, all imports are allowed except the classes 339 * {@code java.util.Date}, {@code java.util.List} and package {@code sun}. 340 * </p> 341 * <pre> 342 * <import-control pkg="com.puppycrawl.tools.checkstyle.checks"> 343 * <allow pkg=".*" regex="true"/> 344 * <subpackage name="imports"> 345 * <disallow class="java.util.Date"/> 346 * <disallow class="java.util.List"/> 347 * <disallow pkg="sun"/> 348 * </subpackage> 349 * </import-control> 350 * </pre> 351 * <p> 352 * In the following example, all imports of the {@code java.util} package are 353 * allowed except the {@code java.util.Date} class. 354 * </p> 355 * <pre> 356 * <import-control pkg="com.puppycrawl.tools.checkstyle.checks"> 357 * <disallow class="java.util.Date"/> 358 * 359 * <allow pkg="java.util"/> 360 * </import-control> 361 * </pre> 362 * <p id="regex-notes">Notes on regular expressions</p> 363 * <p> 364 * Regular expressions in import rules have to match either Java packages or classes. 365 * The language rules for packages and class names can be described by the following 366 * complicated regular expression that takes into account that Java names may contain 367 * any unicode letter, numbers, underscores, and dollar signs (see section 3.8 in the 368 * <a href="https://docs.oracle.com/javase/specs/">Java specs</a>): 369 * </p> 370 * <ul> 371 * <li> 372 * {@code [\p{Letter}_$][\p{Letter}\p{Number}_$]*} or short {@code [\p{L}_$][\p{L}\p{N}_$]*} 373 * for a class name or package component. 374 * </li> 375 * <li> 376 * {@code ([\p{L}_$][\p{L}\p{N}_$]*\.)*[\p{L}_$][\p{L}\p{N}_$]*} for a fully qualified name. 377 * </li> 378 * </ul> 379 * <p> 380 * But it is not necessary to use these complicated expressions since no validation is required. 381 * Differentiating between package separator '.' and others is sufficient. 382 * Unfortunately '.' has a special meaning in regular expressions so one has to write {@code \.} 383 * to match an actual dot. 384 * </p> 385 * <ul> 386 * <li> 387 * Use {@code [^.]+}(one or more "not a dot" characters) for a class name or package component. 388 * </li> 389 * <li> 390 * Use {@code com\.google\.common\.[^.]+} to match any subpackage of {@code com.google.common}. 391 * </li> 392 * <li> 393 * When matching concrete packages like {@code com.google.common} omitting the backslash before 394 * the dots may improve readability and may be just exact enough: {@code com.google.common\.[^.]+} 395 * matches not only subpackages of {@code com.google.common} but e.g. also of 396 * {@code com.googleecommon} but you may not care for that. 397 * </li> 398 * <li> 399 * Do not use {@code .*} unless you really do not care for what is matched. 400 * Often you want to match only a certain package level instead. 401 * </li> 402 * </ul><p id="static-import-notes">Notes on static imports</p> 403 * <p> 404 * Static members (including methods, constants and static inner classes) 405 * have to be explicitly allowed when they are imported, they are not automatically 406 * allowed along with their enclosing class. 407 * </p> 408 * <p> 409 * For example, to allow importing both {@code java.util.Map} and {@code java.util.Map.Entry} 410 * use the following configuration: 411 * </p> 412 * <pre> 413 * <import-control pkg="com.puppycrawl.tools.checkstyle"> 414 * <allow class="java.util.Map"/> 415 * <allow class="java.util.Map.Entry"/> 416 * </import-control> 417 * </pre> 418 * <p> 419 * It is also possible to use a regex with a wildcard: 420 * </p> 421 * <pre> 422 * <import-control pkg="com.puppycrawl.tools.checkstyle"> 423 * <allow class="java.util.Map"/> 424 * <allow class="java.util.Map.*" regex="true" /> 425 * </import-control> 426 * </pre> 427 * 428 * @since 4.0 429 */ 430@FileStatefulCheck 431public class ImportControlCheck extends AbstractCheck implements ExternalResourceHolder { 432 433 /** 434 * A key is pointing to the warning message text in "messages.properties" 435 * file. 436 */ 437 public static final String MSG_MISSING_FILE = "import.control.missing.file"; 438 439 /** 440 * A key is pointing to the warning message text in "messages.properties" 441 * file. 442 */ 443 public static final String MSG_UNKNOWN_PKG = "import.control.unknown.pkg"; 444 445 /** 446 * A key is pointing to the warning message text in "messages.properties" 447 * file. 448 */ 449 public static final String MSG_DISALLOWED = "import.control.disallowed"; 450 451 /** 452 * A part of message for exception. 453 */ 454 private static final String UNABLE_TO_LOAD = "Unable to load "; 455 456 /** 457 * Specify the location of the file containing the import control configuration. 458 * It can be a regular file, URL or resource path. It will try loading the path 459 * as a URL first, then as a file, and finally as a resource. 460 */ 461 private URI file; 462 463 /** 464 * Specify the regular expression of file paths to which this check should apply. 465 * Files that don't match the pattern will not be checked. The pattern will 466 * be matched against the full absolute file path. 467 */ 468 private Pattern path = Pattern.compile(".*"); 469 /** Whether to process the current file. */ 470 private boolean processCurrentFile; 471 472 /** The root package controller. */ 473 private PkgImportControl root; 474 /** The package doing the import. */ 475 private String packageName; 476 /** The file name doing the import. */ 477 private String fileName; 478 479 /** 480 * The package controller for the current file. Used for performance 481 * optimisation. 482 */ 483 private AbstractImportControl currentImportControl; 484 485 @Override 486 public int[] getDefaultTokens() { 487 return getRequiredTokens(); 488 } 489 490 @Override 491 public int[] getAcceptableTokens() { 492 return getRequiredTokens(); 493 } 494 495 @Override 496 public int[] getRequiredTokens() { 497 return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT, }; 498 } 499 500 @Override 501 public void beginTree(DetailAST rootAST) { 502 currentImportControl = null; 503 processCurrentFile = path.matcher(getFileContents().getFileName()).find(); 504 fileName = getFileContents().getText().getFile().getName(); 505 506 final int period = fileName.lastIndexOf('.'); 507 508 if (period != -1) { 509 fileName = fileName.substring(0, period); 510 } 511 } 512 513 @Override 514 public void visitToken(DetailAST ast) { 515 if (processCurrentFile) { 516 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 517 if (root == null) { 518 log(ast, MSG_MISSING_FILE); 519 } 520 else { 521 packageName = getPackageText(ast); 522 currentImportControl = root.locateFinest(packageName, fileName); 523 if (currentImportControl == null) { 524 log(ast, MSG_UNKNOWN_PKG); 525 } 526 } 527 } 528 else if (currentImportControl != null) { 529 final String importText = getImportText(ast); 530 final AccessResult access = currentImportControl.checkAccess(packageName, fileName, 531 importText); 532 if (access != AccessResult.ALLOWED) { 533 log(ast, MSG_DISALLOWED, importText); 534 } 535 } 536 } 537 } 538 539 @Override 540 public Set<String> getExternalResourceLocations() { 541 return Collections.singleton(file.toString()); 542 } 543 544 /** 545 * Returns package text. 546 * @param ast PACKAGE_DEF ast node 547 * @return String that represents full package name 548 */ 549 private static String getPackageText(DetailAST ast) { 550 final DetailAST nameAST = ast.getLastChild().getPreviousSibling(); 551 return FullIdent.createFullIdent(nameAST).getText(); 552 } 553 554 /** 555 * Returns import text. 556 * @param ast ast node that represents import 557 * @return String that represents importing class 558 */ 559 private static String getImportText(DetailAST ast) { 560 final FullIdent imp; 561 if (ast.getType() == TokenTypes.IMPORT) { 562 imp = FullIdent.createFullIdentBelow(ast); 563 } 564 else { 565 // know it is a static import 566 imp = FullIdent.createFullIdent(ast 567 .getFirstChild().getNextSibling()); 568 } 569 return imp.getText(); 570 } 571 572 /** 573 * Setter to specify the location of the file containing the import control configuration. 574 * It can be a regular file, URL or resource path. It will try loading the path 575 * as a URL first, then as a file, and finally as a resource. 576 * 577 * @param uri the uri of the file to load. 578 * @throws IllegalArgumentException on error loading the file. 579 */ 580 public void setFile(URI uri) { 581 // Handle empty param 582 if (uri != null) { 583 try { 584 root = ImportControlLoader.load(uri); 585 file = uri; 586 } 587 catch (CheckstyleException ex) { 588 throw new IllegalArgumentException(UNABLE_TO_LOAD + uri, ex); 589 } 590 } 591 } 592 593 /** 594 * Setter to specify the regular expression of file paths to which this check should apply. 595 * Files that don't match the pattern will not be checked. The pattern will be matched 596 * against the full absolute file path. 597 * 598 * @param pattern the file path regex this check should apply to. 599 */ 600 public void setPath(Pattern pattern) { 601 path = pattern; 602 } 603 604}