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