1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.imports; 21 22 import java.util.Locale; 23 import java.util.regex.Matcher; 24 import java.util.regex.Pattern; 25 26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 28 import com.puppycrawl.tools.checkstyle.api.DetailAST; 29 import com.puppycrawl.tools.checkstyle.api.FullIdent; 30 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 31 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 32 import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil; 33 34 /** 35 * <p> 36 * Checks the ordering/grouping of imports. Features are: 37 * </p> 38 * <ul> 39 * <li> 40 * groups type/static imports: ensures that groups of imports come in a specific order 41 * (e.g., java. comes first, javax. comes second, then everything else) 42 * </li> 43 * <li> 44 * adds a separation between type import groups : ensures that a blank line sit between each group 45 * </li> 46 * <li> 47 * type/static import groups aren't separated internally: ensures that each group aren't separated 48 * internally by blank line or comment 49 * </li> 50 * <li> 51 * sorts type/static imports inside each group: ensures that imports within each group are in 52 * lexicographic order 53 * </li> 54 * <li> 55 * sorts according to case: ensures that the comparison between imports is case-sensitive, in 56 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a> 57 * </li> 58 * <li> 59 * arrange static imports: ensures the relative order between type imports and static imports 60 * (see 61 * <a href="https://checkstyle.org/property_types.html#ImportOrderOption">ImportOrderOption</a>) 62 * </li> 63 * </ul> 64 * <ul> 65 * <li> 66 * Property {@code caseSensitive} - Control whether string comparison should be 67 * case-sensitive or not. Case-sensitive sorting is in 68 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 69 * It affects both type imports and static imports. 70 * Type is {@code boolean}. 71 * Default value is {@code true}. 72 * </li> 73 * <li> 74 * Property {@code groups} - Specify list of <b>type import</b> groups. Every group identified 75 * either by a common prefix string, or by a regular expression enclosed in forward slashes 76 * (e.g. {@code /regexp/}). All type imports, which does not match any group, falls into an 77 * additional group, located at the end. 78 * Thus, the empty list of type groups (the default value) means one group for all type imports. 79 * Type is {@code java.lang.String[]}. 80 * Default value is {@code ""}. 81 * </li> 82 * <li> 83 * Property {@code option} - Specify policy on the relative order between type imports and static 84 * imports. 85 * Type is {@code com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderOption}. 86 * Default value is {@code under}. 87 * </li> 88 * <li> 89 * Property {@code ordered} - Control whether type imports within each group should be 90 * sorted. 91 * It doesn't affect sorting for static imports. 92 * Type is {@code boolean}. 93 * Default value is {@code true}. 94 * </li> 95 * <li> 96 * Property {@code separated} - Control whether type import groups should be separated 97 * by, at least, one blank line or comment and aren't separated internally. 98 * It doesn't affect separations for static imports. 99 * Type is {@code boolean}. 100 * Default value is {@code false}. 101 * </li> 102 * <li> 103 * Property {@code separatedStaticGroups} - Control whether static import groups should 104 * be separated by, at least, one blank line or comment and aren't separated internally. 105 * This property has effect only when the property {@code option} is set to {@code top} 106 * or {@code bottom} and when property {@code staticGroups} is enabled. 107 * Type is {@code boolean}. 108 * Default value is {@code false}. 109 * </li> 110 * <li> 111 * Property {@code sortStaticImportsAlphabetically} - Control whether 112 * <b>static imports</b> located at <b>top</b> or <b>bottom</b> are sorted within the group. 113 * Type is {@code boolean}. 114 * Default value is {@code false}. 115 * </li> 116 * <li> 117 * Property {@code staticGroups} - Specify list of <b>static</b> import groups. Every group 118 * identified either by a common prefix string, or by a regular expression enclosed in forward 119 * slashes (e.g. {@code /regexp/}). All static imports, which does not match any group, fall into 120 * an additional group, located at the end. Thus, the empty list of static groups (the default 121 * value) means one group for all static imports. This property has effect only when the property 122 * {@code option} is set to {@code top} or {@code bottom}. 123 * Type is {@code java.lang.String[]}. 124 * Default value is {@code ""}. 125 * </li> 126 * <li> 127 * Property {@code useContainerOrderingForStatic} - Control whether to use container 128 * ordering (Eclipse IDE term) for static imports or not. 129 * Type is {@code boolean}. 130 * Default value is {@code false}. 131 * </li> 132 * </ul> 133 * <p> 134 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 135 * </p> 136 * <p> 137 * Violation Message Keys: 138 * </p> 139 * <ul> 140 * <li> 141 * {@code import.groups.separated.internally} 142 * </li> 143 * <li> 144 * {@code import.ordering} 145 * </li> 146 * <li> 147 * {@code import.separation} 148 * </li> 149 * </ul> 150 * 151 * @since 3.2 152 */ 153 @FileStatefulCheck 154 public class ImportOrderCheck 155 extends AbstractCheck { 156 157 /** 158 * A key is pointing to the warning message text in "messages.properties" 159 * file. 160 */ 161 public static final String MSG_SEPARATION = "import.separation"; 162 163 /** 164 * A key is pointing to the warning message text in "messages.properties" 165 * file. 166 */ 167 public static final String MSG_ORDERING = "import.ordering"; 168 169 /** 170 * A key is pointing to the warning message text in "messages.properties" 171 * file. 172 */ 173 public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally"; 174 175 /** The special wildcard that catches all remaining groups. */ 176 private static final String WILDCARD_GROUP_NAME = "*"; 177 178 /** The Forward slash. */ 179 private static final String FORWARD_SLASH = "/"; 180 181 /** Empty array of pattern type needed to initialize check. */ 182 private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0]; 183 184 /** 185 * Specify list of <b>type import</b> groups. Every group identified either by a common prefix 186 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 187 * All type imports, which does not match any group, falls into an additional group, 188 * located at the end. Thus, the empty list of type groups (the default value) means one group 189 * for all type imports. 190 */ 191 private String[] groups = CommonUtil.EMPTY_STRING_ARRAY; 192 193 /** 194 * Specify list of <b>static</b> import groups. Every group identified either by a common prefix 195 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 196 * All static imports, which does not match any group, fall into an additional group, located 197 * at the end. Thus, the empty list of static groups (the default value) means one group for all 198 * static imports. This property has effect only when the property {@code option} is set to 199 * {@code top} or {@code bottom}. 200 */ 201 private String[] staticGroups = CommonUtil.EMPTY_STRING_ARRAY; 202 203 /** 204 * Control whether type import groups should be separated by, at least, one blank 205 * line or comment and aren't separated internally. It doesn't affect separations for static 206 * imports. 207 */ 208 private boolean separated; 209 210 /** 211 * Control whether static import groups should be separated by, at least, one blank 212 * line or comment and aren't separated internally. This property has effect only when the 213 * property {@code option} is set to {@code top} or {@code bottom} and when property 214 * {@code staticGroups} is enabled. 215 */ 216 private boolean separatedStaticGroups; 217 218 /** 219 * Control whether type imports within each group should be sorted. 220 * It doesn't affect sorting for static imports. 221 */ 222 private boolean ordered = true; 223 224 /** 225 * Control whether string comparison should be case-sensitive or not. Case-sensitive 226 * sorting is in <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 227 * It affects both type imports and static imports. 228 */ 229 private boolean caseSensitive = true; 230 231 /** Last imported group. */ 232 private int lastGroup; 233 /** Line number of last import. */ 234 private int lastImportLine; 235 /** Name of last import. */ 236 private String lastImport; 237 /** If last import was static. */ 238 private boolean lastImportStatic; 239 /** Whether there were any imports. */ 240 private boolean beforeFirstImport; 241 /** 242 * Whether static and type import groups should be split apart. 243 * When the {@code option} property is set to {@code INFLOW}, {@code ABOVE} or {@code UNDER}, 244 * both the type and static imports use the properties {@code groups} and {@code separated}. 245 * When the {@code option} property is set to {@code TOP} or {@code BOTTOM}, static imports 246 * uses the properties {@code staticGroups} and {@code separatedStaticGroups}. 247 **/ 248 private boolean staticImportsApart; 249 250 /** 251 * Control whether <b>static imports</b> located at <b>top</b> or <b>bottom</b> are 252 * sorted within the group. 253 */ 254 private boolean sortStaticImportsAlphabetically; 255 256 /** 257 * Control whether to use container ordering (Eclipse IDE term) for static imports 258 * or not. 259 */ 260 private boolean useContainerOrderingForStatic; 261 262 /** 263 * Specify policy on the relative order between type imports and static imports. 264 */ 265 private ImportOrderOption option = ImportOrderOption.UNDER; 266 267 /** 268 * Complied array of patterns for property {@code groups}. 269 */ 270 private Pattern[] groupsReg = EMPTY_PATTERN_ARRAY; 271 272 /** 273 * Complied array of patterns for property {@code staticGroups}. 274 */ 275 private Pattern[] staticGroupsReg = EMPTY_PATTERN_ARRAY; 276 277 /** 278 * Setter to specify policy on the relative order between type imports and static imports. 279 * 280 * @param optionStr string to decode option from 281 * @throws IllegalArgumentException if unable to decode 282 * @since 5.0 283 */ 284 public void setOption(String optionStr) { 285 option = ImportOrderOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 286 } 287 288 /** 289 * Setter to specify list of <b>type import</b> groups. Every group identified either by a 290 * common prefix string, or by a regular expression enclosed in forward slashes 291 * (e.g. {@code /regexp/}). All type imports, which does not match any group, falls into an 292 * additional group, located at the end. Thus, the empty list of type groups (the default value) 293 * means one group for all type imports. 294 * 295 * @param packageGroups a comma-separated list of package names/prefixes. 296 * @since 3.2 297 */ 298 public void setGroups(String... packageGroups) { 299 groups = UnmodifiableCollectionUtil.copyOfArray(packageGroups, packageGroups.length); 300 groupsReg = compilePatterns(packageGroups); 301 } 302 303 /** 304 * Setter to specify list of <b>static</b> import groups. Every group identified either by a 305 * common prefix string, or by a regular expression enclosed in forward slashes 306 * (e.g. {@code /regexp/}). All static imports, which does not match any group, fall into an 307 * additional group, located at the end. Thus, the empty list of static groups (the default 308 * value) means one group for all static imports. This property has effect only when 309 * the property {@code option} is set to {@code top} or {@code bottom}. 310 * 311 * @param packageGroups a comma-separated list of package names/prefixes. 312 * @since 8.12 313 */ 314 public void setStaticGroups(String... packageGroups) { 315 staticGroups = UnmodifiableCollectionUtil.copyOfArray(packageGroups, packageGroups.length); 316 staticGroupsReg = compilePatterns(packageGroups); 317 } 318 319 /** 320 * Setter to control whether type imports within each group should be sorted. 321 * It doesn't affect sorting for static imports. 322 * 323 * @param ordered 324 * whether lexicographic ordering of imports within a group 325 * required or not. 326 * @since 3.2 327 */ 328 public void setOrdered(boolean ordered) { 329 this.ordered = ordered; 330 } 331 332 /** 333 * Setter to control whether type import groups should be separated by, at least, 334 * one blank line or comment and aren't separated internally. 335 * It doesn't affect separations for static imports. 336 * 337 * @param separated 338 * whether groups should be separated by one blank line or comment. 339 * @since 3.2 340 */ 341 public void setSeparated(boolean separated) { 342 this.separated = separated; 343 } 344 345 /** 346 * Setter to control whether static import groups should be separated by, at least, 347 * one blank line or comment and aren't separated internally. 348 * This property has effect only when the property 349 * {@code option} is set to {@code top} or {@code bottom} and when property {@code staticGroups} 350 * is enabled. 351 * 352 * @param separatedStaticGroups 353 * whether groups should be separated by one blank line or comment. 354 * @since 8.12 355 */ 356 public void setSeparatedStaticGroups(boolean separatedStaticGroups) { 357 this.separatedStaticGroups = separatedStaticGroups; 358 } 359 360 /** 361 * Setter to control whether string comparison should be case-sensitive or not. 362 * Case-sensitive sorting is in 363 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 364 * It affects both type imports and static imports. 365 * 366 * @param caseSensitive 367 * whether string comparison should be case-sensitive. 368 * @since 3.3 369 */ 370 public void setCaseSensitive(boolean caseSensitive) { 371 this.caseSensitive = caseSensitive; 372 } 373 374 /** 375 * Setter to control whether <b>static imports</b> located at <b>top</b> or 376 * <b>bottom</b> are sorted within the group. 377 * 378 * @param sortAlphabetically true or false. 379 * @since 6.5 380 */ 381 public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) { 382 sortStaticImportsAlphabetically = sortAlphabetically; 383 } 384 385 /** 386 * Setter to control whether to use container ordering (Eclipse IDE term) for static 387 * imports or not. 388 * 389 * @param useContainerOrdering whether to use container ordering for static imports or not. 390 * @since 7.1 391 */ 392 public void setUseContainerOrderingForStatic(boolean useContainerOrdering) { 393 useContainerOrderingForStatic = useContainerOrdering; 394 } 395 396 @Override 397 public int[] getDefaultTokens() { 398 return getRequiredTokens(); 399 } 400 401 @Override 402 public int[] getAcceptableTokens() { 403 return getRequiredTokens(); 404 } 405 406 @Override 407 public int[] getRequiredTokens() { 408 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 409 } 410 411 @Override 412 public void beginTree(DetailAST rootAST) { 413 lastGroup = Integer.MIN_VALUE; 414 lastImportLine = Integer.MIN_VALUE; 415 lastImportStatic = false; 416 beforeFirstImport = true; 417 staticImportsApart = 418 option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM; 419 } 420 421 // -@cs[CyclomaticComplexity] SWITCH was transformed into IF-ELSE. 422 @Override 423 public void visitToken(DetailAST ast) { 424 final FullIdent ident; 425 final boolean isStatic; 426 427 if (ast.getType() == TokenTypes.IMPORT) { 428 ident = FullIdent.createFullIdentBelow(ast); 429 isStatic = false; 430 } 431 else { 432 ident = FullIdent.createFullIdent(ast.getFirstChild() 433 .getNextSibling()); 434 isStatic = true; 435 } 436 437 // using set of IF instead of SWITCH to analyze Enum options to satisfy coverage. 438 // https://github.com/checkstyle/checkstyle/issues/1387 439 if (option == ImportOrderOption.TOP || option == ImportOrderOption.ABOVE) { 440 final boolean isStaticAndNotLastImport = isStatic && !lastImportStatic; 441 doVisitToken(ident, isStatic, isStaticAndNotLastImport, ast); 442 } 443 else if (option == ImportOrderOption.BOTTOM || option == ImportOrderOption.UNDER) { 444 final boolean isLastImportAndNonStatic = lastImportStatic && !isStatic; 445 doVisitToken(ident, isStatic, isLastImportAndNonStatic, ast); 446 } 447 else if (option == ImportOrderOption.INFLOW) { 448 // "previous" argument is useless here 449 doVisitToken(ident, isStatic, true, ast); 450 } 451 else { 452 throw new IllegalStateException( 453 "Unexpected option for static imports: " + option); 454 } 455 456 lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo(); 457 lastImportStatic = isStatic; 458 beforeFirstImport = false; 459 } 460 461 /** 462 * Shares processing... 463 * 464 * @param ident the import to process. 465 * @param isStatic whether the token is static or not. 466 * @param previous previous non-static but current is static (above), or 467 * previous static but current is non-static (under). 468 * @param ast node of the AST. 469 */ 470 private void doVisitToken(FullIdent ident, boolean isStatic, boolean previous, DetailAST ast) { 471 final String name = ident.getText(); 472 final int groupIdx = getGroupNumber(isStatic && staticImportsApart, name); 473 474 if (groupIdx > lastGroup) { 475 if (!beforeFirstImport 476 && ast.getLineNo() - lastImportLine < 2 477 && needSeparator(isStatic)) { 478 log(ast, MSG_SEPARATION, name); 479 } 480 } 481 else if (groupIdx == lastGroup) { 482 doVisitTokenInSameGroup(isStatic, previous, name, ast); 483 } 484 else { 485 log(ast, MSG_ORDERING, name); 486 } 487 if (isSeparatorInGroup(groupIdx, isStatic, ast.getLineNo())) { 488 log(ast, MSG_SEPARATED_IN_GROUP, name); 489 } 490 491 lastGroup = groupIdx; 492 lastImport = name; 493 } 494 495 /** 496 * Checks whether import groups should be separated. 497 * 498 * @param isStatic whether the token is static or not. 499 * @return true if imports groups should be separated. 500 */ 501 private boolean needSeparator(boolean isStatic) { 502 final boolean typeImportSeparator = !isStatic && separated; 503 final boolean staticImportSeparator; 504 if (staticImportsApart) { 505 staticImportSeparator = isStatic && separatedStaticGroups; 506 } 507 else { 508 staticImportSeparator = separated; 509 } 510 final boolean separatorBetween = isStatic != lastImportStatic 511 && (separated || separatedStaticGroups); 512 513 return typeImportSeparator || staticImportSeparator || separatorBetween; 514 } 515 516 /** 517 * Checks whether imports group separated internally. 518 * 519 * @param groupIdx group number. 520 * @param isStatic whether the token is static or not. 521 * @param line the line of the current import. 522 * @return true if imports group are separated internally. 523 */ 524 private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) { 525 final boolean inSameGroup = groupIdx == lastGroup; 526 return (inSameGroup || !needSeparator(isStatic)) && isSeparatorBeforeImport(line); 527 } 528 529 /** 530 * Checks whether there is any separator before current import. 531 * 532 * @param line the line of the current import. 533 * @return true if there is separator before current import which isn't the first import. 534 */ 535 private boolean isSeparatorBeforeImport(int line) { 536 return line - lastImportLine > 1; 537 } 538 539 /** 540 * Shares processing... 541 * 542 * @param isStatic whether the token is static or not. 543 * @param previous previous non-static but current is static (above), or 544 * previous static but current is non-static (under). 545 * @param name the name of the current import. 546 * @param ast node of the AST. 547 */ 548 private void doVisitTokenInSameGroup(boolean isStatic, 549 boolean previous, String name, DetailAST ast) { 550 if (ordered) { 551 if (option == ImportOrderOption.INFLOW) { 552 if (isWrongOrder(name, isStatic)) { 553 log(ast, MSG_ORDERING, name); 554 } 555 } 556 else { 557 final boolean shouldFireError = 558 // previous non-static but current is static (above) 559 // or 560 // previous static but current is non-static (under) 561 previous 562 || 563 // current and previous static or current and 564 // previous non-static 565 lastImportStatic == isStatic 566 && isWrongOrder(name, isStatic); 567 568 if (shouldFireError) { 569 log(ast, MSG_ORDERING, name); 570 } 571 } 572 } 573 } 574 575 /** 576 * Checks whether import name is in wrong order. 577 * 578 * @param name import name. 579 * @param isStatic whether it is a static import name. 580 * @return true if import name is in wrong order. 581 */ 582 private boolean isWrongOrder(String name, boolean isStatic) { 583 final boolean result; 584 if (isStatic) { 585 if (useContainerOrderingForStatic) { 586 result = compareContainerOrder(lastImport, name, caseSensitive) > 0; 587 } 588 else if (staticImportsApart) { 589 result = sortStaticImportsAlphabetically 590 && compare(lastImport, name, caseSensitive) > 0; 591 } 592 else { 593 result = compare(lastImport, name, caseSensitive) > 0; 594 } 595 } 596 else { 597 // out of lexicographic order 598 result = compare(lastImport, name, caseSensitive) > 0; 599 } 600 return result; 601 } 602 603 /** 604 * Compares two import strings. 605 * We first compare the container of the static import, container being the type enclosing 606 * the static element being imported. When this returns 0, we compare the qualified 607 * import name. For e.g. this is what is considered to be container names: 608 * <pre> 609 * import static HttpConstants.COLON => HttpConstants 610 * import static HttpHeaders.addHeader => HttpHeaders 611 * import static HttpHeaders.setHeader => HttpHeaders 612 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 613 * </pre> 614 * <p> 615 * According to this logic, HttpHeaders.Names would come after HttpHeaders. 616 * For more details, see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=473629#c3"> 617 * static imports comparison method</a> in Eclipse. 618 * </p> 619 * 620 * @param importName1 first import name 621 * @param importName2 second import name 622 * @param caseSensitive whether the comparison of fully qualified import names is 623 * case-sensitive 624 * @return the value {@code 0} if str1 is equal to str2; a value 625 * less than {@code 0} if str is less than the str2 (container order 626 * or lexicographical); and a value greater than {@code 0} if str1 is greater than str2 627 * (container order or lexicographically) 628 */ 629 private static int compareContainerOrder(String importName1, String importName2, 630 boolean caseSensitive) { 631 final String container1 = getImportContainer(importName1); 632 final String container2 = getImportContainer(importName2); 633 final int compareContainersOrderResult; 634 if (caseSensitive) { 635 compareContainersOrderResult = container1.compareTo(container2); 636 } 637 else { 638 compareContainersOrderResult = container1.compareToIgnoreCase(container2); 639 } 640 final int result; 641 if (compareContainersOrderResult == 0) { 642 result = compare(importName1, importName2, caseSensitive); 643 } 644 else { 645 result = compareContainersOrderResult; 646 } 647 return result; 648 } 649 650 /** 651 * Extracts import container name from fully qualified import name. 652 * An import container name is the type which encloses the static element being imported. 653 * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names: 654 * <pre> 655 * import static HttpConstants.COLON => HttpConstants 656 * import static HttpHeaders.addHeader => HttpHeaders 657 * import static HttpHeaders.setHeader => HttpHeaders 658 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 659 * </pre> 660 * 661 * @param qualifiedImportName fully qualified import name. 662 * @return import container name. 663 */ 664 private static String getImportContainer(String qualifiedImportName) { 665 final int lastDotIndex = qualifiedImportName.lastIndexOf('.'); 666 return qualifiedImportName.substring(0, lastDotIndex); 667 } 668 669 /** 670 * Finds out what group the specified import belongs to. 671 * 672 * @param isStatic whether the token is static or not. 673 * @param name the import name to find. 674 * @return group number for given import name. 675 */ 676 private int getGroupNumber(boolean isStatic, String name) { 677 final Pattern[] patterns; 678 if (isStatic) { 679 patterns = staticGroupsReg; 680 } 681 else { 682 patterns = groupsReg; 683 } 684 685 int number = getGroupNumber(patterns, name); 686 687 if (isStatic && option == ImportOrderOption.BOTTOM) { 688 number += groups.length + 1; 689 } 690 else if (!isStatic && option == ImportOrderOption.TOP) { 691 number += staticGroups.length + 1; 692 } 693 return number; 694 } 695 696 /** 697 * Finds out what group the specified import belongs to. 698 * 699 * @param patterns groups to check. 700 * @param name the import name to find. 701 * @return group number for given import name. 702 */ 703 private static int getGroupNumber(Pattern[] patterns, String name) { 704 int bestIndex = patterns.length; 705 int bestEnd = -1; 706 int bestPos = Integer.MAX_VALUE; 707 708 // find out what group this belongs in 709 // loop over patterns and get index 710 for (int i = 0; i < patterns.length; i++) { 711 final Matcher matcher = patterns[i].matcher(name); 712 if (matcher.find()) { 713 if (matcher.start() < bestPos) { 714 bestIndex = i; 715 bestEnd = matcher.end(); 716 bestPos = matcher.start(); 717 } 718 else if (matcher.start() == bestPos && matcher.end() > bestEnd) { 719 bestIndex = i; 720 bestEnd = matcher.end(); 721 } 722 } 723 } 724 return bestIndex; 725 } 726 727 /** 728 * Compares two strings. 729 * 730 * @param string1 731 * the first string 732 * @param string2 733 * the second string 734 * @param caseSensitive 735 * whether the comparison is case-sensitive 736 * @return the value {@code 0} if string1 is equal to string2; a value 737 * less than {@code 0} if string1 is lexicographically less 738 * than the string2; and a value greater than {@code 0} if 739 * string1 is lexicographically greater than string2 740 */ 741 private static int compare(String string1, String string2, 742 boolean caseSensitive) { 743 final int result; 744 if (caseSensitive) { 745 result = string1.compareTo(string2); 746 } 747 else { 748 result = string1.compareToIgnoreCase(string2); 749 } 750 751 return result; 752 } 753 754 /** 755 * Compiles the list of package groups and the order they should occur in the file. 756 * 757 * @param packageGroups a comma-separated list of package names/prefixes. 758 * @return array of compiled patterns. 759 * @throws IllegalArgumentException if any of the package groups are not valid. 760 */ 761 private static Pattern[] compilePatterns(String... packageGroups) { 762 final Pattern[] patterns = new Pattern[packageGroups.length]; 763 for (int i = 0; i < packageGroups.length; i++) { 764 String pkg = packageGroups[i]; 765 final Pattern grp; 766 767 // if the pkg name is the wildcard, make it match zero chars 768 // from any name, so it will always be used as last resort. 769 if (WILDCARD_GROUP_NAME.equals(pkg)) { 770 // matches any package 771 grp = Pattern.compile(""); 772 } 773 else if (pkg.startsWith(FORWARD_SLASH)) { 774 if (!pkg.endsWith(FORWARD_SLASH)) { 775 throw new IllegalArgumentException("Invalid group: " + pkg); 776 } 777 pkg = pkg.substring(1, pkg.length() - 1); 778 grp = Pattern.compile(pkg); 779 } 780 else { 781 final StringBuilder pkgBuilder = new StringBuilder(pkg); 782 if (!pkg.endsWith(".")) { 783 pkgBuilder.append('.'); 784 } 785 grp = Pattern.compile("^" + Pattern.quote(pkgBuilder.toString())); 786 } 787 788 patterns[i] = grp; 789 } 790 return patterns; 791 } 792 793 }