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