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.Locale; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 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 the ordering/grouping of imports. Features are: 036 * </p> 037 * <ul> 038 * <li> 039 * groups type/static imports: ensures that groups of imports come in a specific order 040 * (e.g., java. comes first, javax. comes second, then everything else) 041 * </li> 042 * <li> 043 * adds a separation between type import groups : ensures that a blank line sit between each group 044 * </li> 045 * <li> 046 * type/static import groups aren't separated internally: ensures that each group aren't separated 047 * internally by blank line or comment 048 * </li> 049 * <li> 050 * sorts type/static imports inside each group: ensures that imports within each group are in 051 * lexicographic order 052 * </li> 053 * <li> 054 * sorts according to case: ensures that the comparison between imports is case sensitive, in 055 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a> 056 * </li> 057 * <li> 058 * arrange static imports: ensures the relative order between type imports and static imports 059 * (see 060 * <a href="https://checkstyle.org/property_types.html#ImportOrderOption">ImportOrderOption</a>) 061 * </li> 062 * </ul> 063 * <ul> 064 * <li> 065 * Property {@code option} - specify policy on the relative order between type imports and static 066 * imports. 067 * Type is {@code com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderOption}. 068 * Default value is {@code under}. 069 * </li> 070 * <li> 071 * Property {@code groups} - specify list of <b>type import</b> groups (every group identified 072 * either by a common prefix string, or by a regular expression enclosed in forward slashes 073 * (e.g. {@code /regexp/}). All type imports, which does not match any group, falls into an 074 * additional group, located at the end. 075 * Thus, the empty list of type groups (the default value) means one group for all type imports. 076 * Type is {@code java.lang.String[]}. 077 * Default value is {@code {}}. 078 * </li> 079 * <li> 080 * Property {@code ordered} - control whether type imports within each group should be 081 * sorted. 082 * It doesn't affect sorting for static imports. 083 * Type is {@code boolean}. 084 * Default value is true. 085 * </li> 086 * <li> 087 * Property {@code separated} - control whether type import groups should be separated 088 * by, at least, one blank line or comment and aren't separated internally. 089 * It doesn't affect separations for static imports. 090 * Type is {@code boolean}. 091 * Default value is false. 092 * </li> 093 * <li> 094 * Property {@code separatedStaticGroups} - control whether static import groups should 095 * be separated by, at least, one blank line or comment and aren't separated internally. 096 * This property has effect only when the property {@code option} is is set to {@code top} 097 * or {@code bottom}. 098 * Type is {@code boolean}. 099 * Default value is false. 100 * </li> 101 * <li> 102 * Property {@code caseSensitive} - control whether string comparison should be case 103 * sensitive or not. Case sensitive sorting is in 104 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 105 * It affects both type imports and static imports. 106 * Type is {@code boolean}. 107 * Default value is true. 108 * </li> 109 * <li> 110 * Property {@code staticGroups} - specify list of <b>static</b> import groups (every group 111 * identified either by a common prefix string, or by a regular expression enclosed in forward 112 * slashes (e.g. {@code /regexp/}). All static imports, which does not match any group, falls into 113 * an additional group, located at the end. Thus, the empty list of static groups (the default 114 * value) means one group for all static imports. This property has effect only when the property 115 * {@code option} is set to {@code top} or {@code bottom}. 116 * Type is {@code java.lang.String[]}. 117 * Default value is {@code {}}. 118 * </li> 119 * <li> 120 * Property {@code sortStaticImportsAlphabetically} - control whether 121 * <b>static imports</b> located at <b>top</b> or <b>bottom</b> are sorted within the group. 122 * Type is {@code boolean}. 123 * Default value is false. 124 * </li> 125 * <li> 126 * Property {@code useContainerOrderingForStatic} - control whether to use container 127 * ordering (Eclipse IDE term) for static imports or not. 128 * Type is {@code boolean}. 129 * Default value is false. 130 * </li> 131 * <li> 132 * Property {@code tokens} - tokens to check 133 * Type is {@code int[]}. 134 * Default value is: 135 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_IMPORT"> 136 * STATIC_IMPORT</a>. 137 * </li> 138 * </ul> 139 * <p> 140 * To configure the check so that it matches default Eclipse formatter configuration 141 * (tested on Kepler and Luna releases): 142 * </p> 143 * <ul> 144 * <li> 145 * group of static imports is on the top 146 * </li> 147 * <li> 148 * groups of type imports: "java" and "javax" packages first, then "org" and then all other imports 149 * </li> 150 * <li> 151 * imports will be sorted in the groups 152 * </li> 153 * <li> 154 * groups are separated by, at least, one blank line and aren't separated internally 155 * </li> 156 * </ul> 157 * <p> 158 * Notes: 159 * </p> 160 * <ul> 161 * <li> 162 * "com" package is not mentioned on configuration, because it is ignored by Eclipse Kepler and Luna 163 * (looks like Eclipse defect) 164 * </li> 165 * <li> 166 * configuration below doesn't work in all 100% cases due to inconsistent behavior prior to 167 * Mars release, but covers most scenarios 168 * </li> 169 * </ul> 170 * <pre> 171 * <module name="ImportOrder"> 172 * <property name="groups" value="/^java\./,javax,org"/> 173 * <property name="ordered" value="true"/> 174 * <property name="separated" value="true"/> 175 * <property name="option" value="above"/> 176 * <property name="sortStaticImportsAlphabetically" value="true"/> 177 * </module> 178 * </pre> 179 * <p> 180 * To configure the check so that it matches default Eclipse formatter configuration 181 * (tested on Mars release): 182 * </p> 183 * <ul> 184 * <li> 185 * group of static imports is on the top 186 * </li> 187 * <li> 188 * groups of type imports: "java" and "javax" packages first, then "org" and "com", 189 * then all other imports as one group 190 * </li> 191 * <li> 192 * imports will be sorted in the groups 193 * </li> 194 * <li> 195 * groups are separated by, at least, one blank line and aren't separated internally 196 * </li> 197 * </ul> 198 * <pre> 199 * <module name="ImportOrder"> 200 * <property name="groups" value="/^java\./,javax,org,com"/> 201 * <property name="ordered" value="true"/> 202 * <property name="separated" value="true"/> 203 * <property name="option" value="above"/> 204 * <property name="sortStaticImportsAlphabetically" value="true"/> 205 * </module> 206 * </pre> 207 * <p> 208 * To configure the check so that it matches default IntelliJ IDEA formatter configuration 209 * (tested on v2018.2): 210 * </p> 211 * <ul> 212 * <li> 213 * group of static imports is on the bottom 214 * </li> 215 * <li> 216 * groups of type imports: all imports except of "javax" and "java", then "javax" and "java" 217 * </li> 218 * <li> 219 * imports will be sorted in the groups 220 * </li> 221 * <li> 222 * groups are separated by, at least, one blank line and aren't separated internally 223 * </li> 224 * </ul> 225 * <p> 226 * Note: a <a href="https://checkstyle.org/config_filters.html#SuppressionXpathSingleFilter"> 227 * suppression xpath single filter</a> is needed because 228 * IDEA has no blank line between "javax" and "java". 229 * ImportOrder has a limitation by design to enforce an empty line between groups ("java", "javax"). 230 * There is no flexibility to enforce empty lines between some groups and no empty lines between 231 * other groups. 232 * </p> 233 * <p> 234 * Note: "separated" option is disabled because IDEA default has blank line between "java" and 235 * static imports, and no blank line between "javax" and "java". 236 * </p> 237 * <pre> 238 * <module name="ImportOrder"> 239 * <property name="groups" value="*,javax,java"/> 240 * <property name="ordered" value="true"/> 241 * <property name="separated" value="false"/> 242 * <property name="option" value="bottom"/> 243 * <property name="sortStaticImportsAlphabetically" value="true"/> 244 * </module> 245 * <module name="SuppressionXpathSingleFilter"> 246 * <property name="checks" value="ImportOrder"/> 247 * <property name="message" value="^'java\..*'.*"/> 248 * </module> 249 * </pre> 250 * <p> 251 * To configure the check so that it matches default NetBeans formatter configuration 252 * (tested on v8): 253 * </p> 254 * <ul> 255 * <li> 256 * groups of type imports are not defined, all imports will be sorted as a one group 257 * </li> 258 * <li> 259 * static imports are not separated, they will be sorted along with other imports 260 * </li> 261 * </ul> 262 * <pre> 263 * <module name="ImportOrder"> 264 * <property name="option" value="inflow"/> 265 * </module> 266 * </pre> 267 * <p> 268 * Group descriptions enclosed in slashes are interpreted as regular expressions. 269 * If multiple groups match, the one matching a longer substring of the imported name 270 * will take precedence, with ties broken first in favor of earlier matches and finally 271 * in favor of the first matching group. 272 * </p> 273 * <p> 274 * There is always a wildcard group to which everything not in a named group belongs. 275 * If an import does not match a named group, the group belongs to this wildcard group. 276 * The wildcard group position can be specified using the {@code *} character. 277 * </p> 278 * <p> 279 * Check also has on option making it more flexible: <b>sortStaticImportsAlphabetically</b> 280 * - sets whether static imports grouped by <b>top</b> or <b>bottom</b> option should be sorted 281 * alphabetically or not, default value is <b>false</b>. It is applied to static imports grouped 282 * with <b>top</b> or <b>bottom</b> options. This option is helping in reconciling of this 283 * Check and other tools like Eclipse's Organize Imports feature. 284 * </p> 285 * <p> 286 * To configure the Check allows static imports grouped to the <b>top</b> being sorted 287 * alphabetically: 288 * </p> 289 * <pre> 290 * <module name="ImportOrder"> 291 * <property name="sortStaticImportsAlphabetically" value="true"/> 292 * <property name="option" value="top"/> 293 * </module> 294 * </pre> 295 * <pre> 296 * import static java.lang.Math.PI; 297 * import static java.lang.Math.abs; // OK, alphabetical case sensitive ASCII order, 'P' < 'a' 298 * import static org.abego.treelayout.Configuration.AlignmentInLevel; // OK, alphabetical order 299 * 300 * import org.abego.*; 301 * 302 * import java.util.Set; // Wrong order for 'java.util.Set' import. 303 * 304 * public class SomeClass { ... } 305 * </pre> 306 * <p> 307 * To configure the Check with groups of static imports: 308 * </p> 309 * <pre> 310 * <module name="ImportOrder"> 311 * <property name="staticGroups" value="org,java"/> 312 * <property name="sortStaticImportsAlphabetically" value="true"/> 313 * </module> 314 * </pre> 315 * <pre> 316 * import static org.abego.treelayout.Configuration.AlignmentInLevel; // Group 1 317 * import static java.lang.Math.abs; // Group 2 318 * import static java.lang.String.format; // Group 2 319 * import static com.google.common.primitives.Doubles.BYTES; // Group "everything else" 320 * 321 * public class SomeClass { ... } 322 * </pre> 323 * <p> 324 * The following example shows the idea of 'useContainerOrderingForStatic' option that is 325 * useful for Eclipse IDE users to match ordering validation. 326 * This is how the import comparison works for static imports: we first compare 327 * the container of the static import, container is the type enclosing the static element 328 * being imported. When the result of the comparison is 0 (containers are equal), 329 * we compare the fully qualified import names. 330 * For e.g. this is what is considered to be container names for the given example: 331 * 332 * import static HttpConstants.COLON => HttpConstants 333 * import static HttpHeaders.addHeader => HttpHeaders 334 * import static HttpHeaders.setHeader => HttpHeaders 335 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 336 * 337 * According to this logic, HttpHeaders.Names should come after HttpHeaders. 338 * </p> 339 * <p> 340 * Example for {@code useContainerOrderingForStatic=true} 341 * </p> 342 * <pre> 343 * <module name="ImportOrder"> 344 * <property name="useContainerOrderingForStatic" value="true"/> 345 * <property name="ordered" value="true"/> 346 * <property name="option" value="top"/> 347 * <property name="caseSensitive" value="false"/> 348 * <property name="sortStaticImportsAlphabetically" value="true"/> 349 * </module> 350 * </pre> 351 * <pre> 352 * import static io.netty.handler.codec.http.HttpConstants.COLON; 353 * import static io.netty.handler.codec.http.HttpHeaders.addHeader; 354 * import static io.netty.handler.codec.http.HttpHeaders.setHeader; 355 * import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; 356 * 357 * public class InputEclipseStaticImportsOrder { } 358 * </pre> 359 * <p> 360 * Example for {@code useContainerOrderingForStatic=false} 361 * </p> 362 * <pre> 363 * <module name="ImportOrder"> 364 * <property name="useContainerOrderingForStatic" value="false"/> 365 * <property name="ordered" value="true"/> 366 * <property name="option" value="top"/> 367 * <property name="caseSensitive" value="false"/> 368 * <property name="sortStaticImportsAlphabetically" value="true"/> 369 * </module> 370 * </pre> 371 * <pre> 372 * import static io.netty.handler.codec.http.HttpConstants.COLON; 373 * import static io.netty.handler.codec.http.HttpHeaders.addHeader; 374 * import static io.netty.handler.codec.http.HttpHeaders.setHeader; 375 * import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; // violation 376 * 377 * public class InputEclipseStaticImportsOrder { } 378 * </pre> 379 * <p> 380 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 381 * </p> 382 * <p> 383 * Violation Message Keys: 384 * </p> 385 * <ul> 386 * <li> 387 * {@code import.groups.separated.internally} 388 * </li> 389 * <li> 390 * {@code import.ordering} 391 * </li> 392 * <li> 393 * {@code import.separation} 394 * </li> 395 * </ul> 396 * 397 * @since 3.2 398 */ 399@FileStatefulCheck 400public class ImportOrderCheck 401 extends AbstractCheck { 402 403 /** 404 * A key is pointing to the warning message text in "messages.properties" 405 * file. 406 */ 407 public static final String MSG_SEPARATION = "import.separation"; 408 409 /** 410 * A key is pointing to the warning message text in "messages.properties" 411 * file. 412 */ 413 public static final String MSG_ORDERING = "import.ordering"; 414 415 /** 416 * A key is pointing to the warning message text in "messages.properties" 417 * file. 418 */ 419 public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally"; 420 421 /** The special wildcard that catches all remaining groups. */ 422 private static final String WILDCARD_GROUP_NAME = "*"; 423 424 /** Empty array of pattern type needed to initialize check. */ 425 private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0]; 426 427 /** 428 * Specify list of <b>type import</b> groups (every group identified either by a common prefix 429 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 430 * All type imports, which does not match any group, falls into an additional group, 431 * located at the end. Thus, the empty list of type groups (the default value) means one group 432 * for all type imports. 433 */ 434 private Pattern[] groups = EMPTY_PATTERN_ARRAY; 435 436 /** 437 * Specify list of <b>static</b> import groups (every group identified either by a common prefix 438 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 439 * All static imports, which does not match any group, falls into an additional group, located 440 * at the end. Thus, the empty list of static groups (the default value) means one group for all 441 * static imports. This property has effect only when the property {@code option} is set to 442 * {@code top} or {@code bottom}. 443 */ 444 private Pattern[] staticGroups = EMPTY_PATTERN_ARRAY; 445 446 /** 447 * Control whether type import groups should be separated by, at least, one blank 448 * line or comment and aren't separated internally. It doesn't affect separations for static 449 * imports. 450 */ 451 private boolean separated; 452 453 /** 454 * Control whether static import groups should be separated by, at least, one blank 455 * line or comment and aren't separated internally. This property has effect only when the 456 * property {@code option} is is set to {@code top} or {@code bottom}. 457 */ 458 private boolean separatedStaticGroups; 459 460 /** 461 * Control whether type imports within each group should be sorted. 462 * It doesn't affect sorting for static imports. 463 */ 464 private boolean ordered = true; 465 466 /** 467 * Control whether string comparison should be case sensitive or not. Case sensitive 468 * sorting is in <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 469 * It affects both type imports and static imports. 470 */ 471 private boolean caseSensitive = true; 472 473 /** Last imported group. */ 474 private int lastGroup; 475 /** Line number of last import. */ 476 private int lastImportLine; 477 /** Name of last import. */ 478 private String lastImport; 479 /** If last import was static. */ 480 private boolean lastImportStatic; 481 /** Whether there was any imports. */ 482 private boolean beforeFirstImport; 483 /** 484 * Whether static and type import groups should be split apart. 485 * When the {@code option} property is set to {@code INFLOW}, {@code BELOW} or {@code UNDER}, 486 * both the type and static imports use the properties {@code groups} and {@code separated}. 487 * When the {@code option} property is set to {@code TOP} or {@code BOTTOM}, static imports 488 * uses the properties {@code staticGroups} and {@code separatedStaticGroups}. 489 **/ 490 private boolean staticImportsApart; 491 492 /** 493 * Control whether <b>static imports</b> located at <b>top</b> or <b>bottom</b> are 494 * sorted within the group. 495 */ 496 private boolean sortStaticImportsAlphabetically; 497 498 /** 499 * Control whether to use container ordering (Eclipse IDE term) for static imports 500 * or not. 501 */ 502 private boolean useContainerOrderingForStatic; 503 504 /** 505 * Specify policy on the relative order between type imports and static imports. 506 */ 507 private ImportOrderOption option = ImportOrderOption.UNDER; 508 509 /** 510 * Setter to specify policy on the relative order between type imports and static imports. 511 * 512 * @param optionStr string to decode option from 513 * @throws IllegalArgumentException if unable to decode 514 */ 515 public void setOption(String optionStr) { 516 option = ImportOrderOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 517 } 518 519 /** 520 * Setter to specify list of <b>type import</b> groups (every group identified either by a 521 * common prefix string, or by a regular expression enclosed in forward slashes 522 * (e.g. {@code /regexp/}). All type imports, which does not match any group, falls into an 523 * additional group, located at the end. Thus, the empty list of type groups (the default value) 524 * means one group for all type imports. 525 * 526 * @param packageGroups a comma-separated list of package names/prefixes. 527 */ 528 public void setGroups(String... packageGroups) { 529 groups = compilePatterns(packageGroups); 530 } 531 532 /** 533 * Setter to specify list of <b>static</b> import groups (every group identified either by a 534 * common prefix string, or by a regular expression enclosed in forward slashes 535 * (e.g. {@code /regexp/}). All static imports, which does not match any group, falls into an 536 * additional group, located at the end. Thus, the empty list of static groups (the default 537 * value) means one group for all static imports. This property has effect only when 538 * the property {@code option} is set to {@code top} or {@code bottom}. 539 * 540 * @param packageGroups a comma-separated list of package names/prefixes. 541 */ 542 public void setStaticGroups(String... packageGroups) { 543 staticGroups = compilePatterns(packageGroups); 544 } 545 546 /** 547 * Setter to control whether type imports within each group should be sorted. 548 * It doesn't affect sorting for static imports. 549 * 550 * @param ordered 551 * whether lexicographic ordering of imports within a group 552 * required or not. 553 */ 554 public void setOrdered(boolean ordered) { 555 this.ordered = ordered; 556 } 557 558 /** 559 * Setter to control whether type import groups should be separated by, at least, 560 * one blank line or comment and aren't separated internally. 561 * It doesn't affect separations for static imports. 562 * 563 * @param separated 564 * whether groups should be separated by one blank line or comment. 565 */ 566 public void setSeparated(boolean separated) { 567 this.separated = separated; 568 } 569 570 /** 571 * Setter to control whether static import groups should be separated by, at least, 572 * one blank line or comment and aren't separated internally. 573 * This property has effect only when the property 574 * {@code option} is is set to {@code top} or {@code bottom}. 575 * 576 * @param separatedStaticGroups 577 * whether groups should be separated by one blank line or comment. 578 */ 579 public void setSeparatedStaticGroups(boolean separatedStaticGroups) { 580 this.separatedStaticGroups = separatedStaticGroups; 581 } 582 583 /** 584 * Setter to control whether string comparison should be case sensitive or not. 585 * Case sensitive sorting is in 586 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 587 * It affects both type imports and static imports. 588 * 589 * @param caseSensitive 590 * whether string comparison should be case sensitive. 591 */ 592 public void setCaseSensitive(boolean caseSensitive) { 593 this.caseSensitive = caseSensitive; 594 } 595 596 /** 597 * Setter to control whether <b>static imports</b> located at <b>top</b> or 598 * <b>bottom</b> are sorted within the group. 599 * 600 * @param sortAlphabetically true or false. 601 */ 602 public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) { 603 sortStaticImportsAlphabetically = sortAlphabetically; 604 } 605 606 /** 607 * Setter to control whether to use container ordering (Eclipse IDE term) for static 608 * imports or not. 609 * 610 * @param useContainerOrdering whether to use container ordering for static imports or not. 611 */ 612 public void setUseContainerOrderingForStatic(boolean useContainerOrdering) { 613 useContainerOrderingForStatic = useContainerOrdering; 614 } 615 616 @Override 617 public int[] getDefaultTokens() { 618 return getAcceptableTokens(); 619 } 620 621 @Override 622 public int[] getAcceptableTokens() { 623 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 624 } 625 626 @Override 627 public int[] getRequiredTokens() { 628 return new int[] {TokenTypes.IMPORT}; 629 } 630 631 @Override 632 public void beginTree(DetailAST rootAST) { 633 lastGroup = Integer.MIN_VALUE; 634 lastImportLine = Integer.MIN_VALUE; 635 lastImport = ""; 636 lastImportStatic = false; 637 beforeFirstImport = true; 638 staticImportsApart = 639 option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM; 640 } 641 642 // -@cs[CyclomaticComplexity] SWITCH was transformed into IF-ELSE. 643 @Override 644 public void visitToken(DetailAST ast) { 645 final FullIdent ident; 646 final boolean isStatic; 647 648 if (ast.getType() == TokenTypes.IMPORT) { 649 ident = FullIdent.createFullIdentBelow(ast); 650 isStatic = false; 651 } 652 else { 653 ident = FullIdent.createFullIdent(ast.getFirstChild() 654 .getNextSibling()); 655 isStatic = true; 656 } 657 658 // using set of IF instead of SWITCH to analyze Enum options to satisfy coverage. 659 // https://github.com/checkstyle/checkstyle/issues/1387 660 if (option == ImportOrderOption.TOP || option == ImportOrderOption.ABOVE) { 661 final boolean isStaticAndNotLastImport = isStatic && !lastImportStatic; 662 doVisitToken(ident, isStatic, isStaticAndNotLastImport, ast); 663 } 664 else if (option == ImportOrderOption.BOTTOM || option == ImportOrderOption.UNDER) { 665 final boolean isLastImportAndNonStatic = lastImportStatic && !isStatic; 666 doVisitToken(ident, isStatic, isLastImportAndNonStatic, ast); 667 } 668 else if (option == ImportOrderOption.INFLOW) { 669 // "previous" argument is useless here 670 doVisitToken(ident, isStatic, true, ast); 671 } 672 else { 673 throw new IllegalStateException( 674 "Unexpected option for static imports: " + option); 675 } 676 677 lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo(); 678 lastImportStatic = isStatic; 679 beforeFirstImport = false; 680 } 681 682 /** 683 * Shares processing... 684 * 685 * @param ident the import to process. 686 * @param isStatic whether the token is static or not. 687 * @param previous previous non-static but current is static (above), or 688 * previous static but current is non-static (under). 689 * @param ast node of the AST. 690 */ 691 private void doVisitToken(FullIdent ident, boolean isStatic, boolean previous, DetailAST ast) { 692 final String name = ident.getText(); 693 final int groupIdx = getGroupNumber(isStatic && staticImportsApart, name); 694 695 if (groupIdx > lastGroup) { 696 if (!beforeFirstImport 697 && ast.getLineNo() - lastImportLine < 2 698 && needSeparator(isStatic)) { 699 log(ast, MSG_SEPARATION, name); 700 } 701 } 702 else if (groupIdx == lastGroup) { 703 doVisitTokenInSameGroup(isStatic, previous, name, ast); 704 } 705 else { 706 log(ast, MSG_ORDERING, name); 707 } 708 if (isSeparatorInGroup(groupIdx, isStatic, ast.getLineNo())) { 709 log(ast, MSG_SEPARATED_IN_GROUP, name); 710 } 711 712 lastGroup = groupIdx; 713 lastImport = name; 714 } 715 716 /** 717 * Checks whether import groups should be separated. 718 * 719 * @param isStatic whether the token is static or not. 720 * @return true if imports groups should be separated. 721 */ 722 private boolean needSeparator(boolean isStatic) { 723 final boolean typeImportSeparator = !isStatic && separated; 724 final boolean staticImportSeparator; 725 if (staticImportsApart) { 726 staticImportSeparator = isStatic && separatedStaticGroups; 727 } 728 else { 729 staticImportSeparator = isStatic && separated; 730 } 731 final boolean separatorBetween = isStatic != lastImportStatic 732 && (separated || separatedStaticGroups); 733 734 return typeImportSeparator || staticImportSeparator || separatorBetween; 735 } 736 737 /** 738 * Checks whether imports group separated internally. 739 * 740 * @param groupIdx group number. 741 * @param isStatic whether the token is static or not. 742 * @param line the line of the current import. 743 * @return true if imports group are separated internally. 744 */ 745 private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) { 746 final boolean inSameGroup = groupIdx == lastGroup; 747 return (inSameGroup || !needSeparator(isStatic)) && isSeparatorBeforeImport(line); 748 } 749 750 /** 751 * Checks whether there is any separator before current import. 752 * 753 * @param line the line of the current import. 754 * @return true if there is separator before current import which isn't the first import. 755 */ 756 private boolean isSeparatorBeforeImport(int line) { 757 return !beforeFirstImport && line - lastImportLine > 1; 758 } 759 760 /** 761 * Shares processing... 762 * 763 * @param isStatic whether the token is static or not. 764 * @param previous previous non-static but current is static (above), or 765 * previous static but current is non-static (under). 766 * @param name the name of the current import. 767 * @param ast node of the AST. 768 */ 769 private void doVisitTokenInSameGroup(boolean isStatic, 770 boolean previous, String name, DetailAST ast) { 771 if (ordered) { 772 if (option == ImportOrderOption.INFLOW) { 773 if (isWrongOrder(name, isStatic)) { 774 log(ast, MSG_ORDERING, name); 775 } 776 } 777 else { 778 final boolean shouldFireError = 779 // previous non-static but current is static (above) 780 // or 781 // previous static but current is non-static (under) 782 previous 783 || 784 // current and previous static or current and 785 // previous non-static 786 lastImportStatic == isStatic 787 && isWrongOrder(name, isStatic); 788 789 if (shouldFireError) { 790 log(ast, MSG_ORDERING, name); 791 } 792 } 793 } 794 } 795 796 /** 797 * Checks whether import name is in wrong order. 798 * 799 * @param name import name. 800 * @param isStatic whether it is a static import name. 801 * @return true if import name is in wrong order. 802 */ 803 private boolean isWrongOrder(String name, boolean isStatic) { 804 final boolean result; 805 if (isStatic) { 806 if (useContainerOrderingForStatic) { 807 result = compareContainerOrder(lastImport, name, caseSensitive) > 0; 808 } 809 else if (staticImportsApart) { 810 result = sortStaticImportsAlphabetically 811 && compare(lastImport, name, caseSensitive) > 0; 812 } 813 else { 814 result = compare(lastImport, name, caseSensitive) > 0; 815 } 816 } 817 else { 818 // out of lexicographic order 819 result = compare(lastImport, name, caseSensitive) > 0; 820 } 821 return result; 822 } 823 824 /** 825 * Compares two import strings. 826 * We first compare the container of the static import, container being the type enclosing 827 * the static element being imported. When this returns 0, we compare the qualified 828 * import name. For e.g. this is what is considered to be container names: 829 * <p> 830 * import static HttpConstants.COLON => HttpConstants 831 * import static HttpHeaders.addHeader => HttpHeaders 832 * import static HttpHeaders.setHeader => HttpHeaders 833 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 834 * </p> 835 * <p> 836 * According to this logic, HttpHeaders.Names would come after HttpHeaders. 837 * 838 * For more details, see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=473629#c3"> 839 * static imports comparison method</a> in Eclipse. 840 * </p> 841 * 842 * @param importName1 first import name. 843 * @param importName2 second import name. 844 * @param caseSensitive whether the comparison of fully qualified import names is case 845 * sensitive. 846 * @return the value {@code 0} if str1 is equal to str2; a value 847 * less than {@code 0} if str is less than the str2 (container order 848 * or lexicographical); and a value greater than {@code 0} if str1 is greater than str2 849 * (container order or lexicographically). 850 */ 851 private static int compareContainerOrder(String importName1, String importName2, 852 boolean caseSensitive) { 853 final String container1 = getImportContainer(importName1); 854 final String container2 = getImportContainer(importName2); 855 final int compareContainersOrderResult; 856 if (caseSensitive) { 857 compareContainersOrderResult = container1.compareTo(container2); 858 } 859 else { 860 compareContainersOrderResult = container1.compareToIgnoreCase(container2); 861 } 862 final int result; 863 if (compareContainersOrderResult == 0) { 864 result = compare(importName1, importName2, caseSensitive); 865 } 866 else { 867 result = compareContainersOrderResult; 868 } 869 return result; 870 } 871 872 /** 873 * Extracts import container name from fully qualified import name. 874 * An import container name is the type which encloses the static element being imported. 875 * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names: 876 * <p> 877 * import static HttpConstants.COLON => HttpConstants 878 * import static HttpHeaders.addHeader => HttpHeaders 879 * import static HttpHeaders.setHeader => HttpHeaders 880 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 881 * </p> 882 * 883 * @param qualifiedImportName fully qualified import name. 884 * @return import container name. 885 */ 886 private static String getImportContainer(String qualifiedImportName) { 887 final int lastDotIndex = qualifiedImportName.lastIndexOf('.'); 888 return qualifiedImportName.substring(0, lastDotIndex); 889 } 890 891 /** 892 * Finds out what group the specified import belongs to. 893 * 894 * @param isStatic whether the token is static or not. 895 * @param name the import name to find. 896 * @return group number for given import name. 897 */ 898 private int getGroupNumber(boolean isStatic, String name) { 899 final Pattern[] patterns; 900 if (isStatic) { 901 patterns = staticGroups; 902 } 903 else { 904 patterns = groups; 905 } 906 907 int number = getGroupNumber(patterns, name); 908 909 if (isStatic && option == ImportOrderOption.BOTTOM) { 910 number += groups.length + 1; 911 } 912 else if (!isStatic && option == ImportOrderOption.TOP) { 913 number += staticGroups.length + 1; 914 } 915 return number; 916 } 917 918 /** 919 * Finds out what group the specified import belongs to. 920 * 921 * @param patterns groups to check. 922 * @param name the import name to find. 923 * @return group number for given import name. 924 */ 925 private static int getGroupNumber(Pattern[] patterns, String name) { 926 int bestIndex = patterns.length; 927 int bestEnd = -1; 928 int bestPos = Integer.MAX_VALUE; 929 930 // find out what group this belongs in 931 // loop over patterns and get index 932 for (int i = 0; i < patterns.length; i++) { 933 final Matcher matcher = patterns[i].matcher(name); 934 if (matcher.find()) { 935 if (matcher.start() < bestPos) { 936 bestIndex = i; 937 bestEnd = matcher.end(); 938 bestPos = matcher.start(); 939 } 940 else if (matcher.start() == bestPos && matcher.end() > bestEnd) { 941 bestIndex = i; 942 bestEnd = matcher.end(); 943 } 944 } 945 } 946 return bestIndex; 947 } 948 949 /** 950 * Compares two strings. 951 * 952 * @param string1 953 * the first string. 954 * @param string2 955 * the second string. 956 * @param caseSensitive 957 * whether the comparison is case sensitive. 958 * @return the value {@code 0} if string1 is equal to string2; a value 959 * less than {@code 0} if string1 is lexicographically less 960 * than the string2; and a value greater than {@code 0} if 961 * string1 is lexicographically greater than string2. 962 */ 963 private static int compare(String string1, String string2, 964 boolean caseSensitive) { 965 final int result; 966 if (caseSensitive) { 967 result = string1.compareTo(string2); 968 } 969 else { 970 result = string1.compareToIgnoreCase(string2); 971 } 972 973 return result; 974 } 975 976 /** 977 * Compiles the list of package groups and the order they should occur in the file. 978 * 979 * @param packageGroups a comma-separated list of package names/prefixes. 980 * @return array of compiled patterns. 981 */ 982 private static Pattern[] compilePatterns(String... packageGroups) { 983 final Pattern[] patterns = new Pattern[packageGroups.length]; 984 985 for (int i = 0; i < packageGroups.length; i++) { 986 String pkg = packageGroups[i]; 987 final Pattern grp; 988 989 // if the pkg name is the wildcard, make it match zero chars 990 // from any name, so it will always be used as last resort. 991 if (WILDCARD_GROUP_NAME.equals(pkg)) { 992 // matches any package 993 grp = Pattern.compile(""); 994 } 995 else if (CommonUtil.startsWithChar(pkg, '/')) { 996 if (!CommonUtil.endsWithChar(pkg, '/')) { 997 throw new IllegalArgumentException("Invalid group: " + pkg); 998 } 999 pkg = pkg.substring(1, pkg.length() - 1); 1000 grp = Pattern.compile(pkg); 1001 } 1002 else { 1003 final StringBuilder pkgBuilder = new StringBuilder(pkg); 1004 if (!CommonUtil.endsWithChar(pkg, '.')) { 1005 pkgBuilder.append('.'); 1006 } 1007 grp = Pattern.compile("^" + Pattern.quote(pkgBuilder.toString())); 1008 } 1009 1010 patterns[i] = grp; 1011 } 1012 return patterns; 1013 } 1014 1015}