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