001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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; 032import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil; 033 034/** 035 * <div> 036 * Checks the ordering/grouping of imports. Features are: 037 * </div> 038 * <ul> 039 * <li> 040 * groups type/static imports: ensures that groups of imports come in a specific order 041 * (e.g., java. comes first, javax. comes second, then everything else) 042 * </li> 043 * <li> 044 * adds a separation between type import groups : ensures that a blank line sit between each group 045 * </li> 046 * <li> 047 * type/static import groups aren't separated internally: ensures that each group aren't separated 048 * internally by blank line or comment 049 * </li> 050 * <li> 051 * sorts type/static imports inside each group: ensures that imports within each group are in 052 * lexicographic order 053 * </li> 054 * <li> 055 * sorts according to case: ensures that the comparison between imports is case-sensitive, in 056 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a> 057 * </li> 058 * <li> 059 * arrange static imports: ensures the relative order between type imports and static imports 060 * (see 061 * <a href="https://checkstyle.org/property_types.html#ImportOrderOption">ImportOrderOption</a>) 062 * </li> 063 * </ul> 064 * 065 * @since 3.2 066 */ 067@FileStatefulCheck 068public class ImportOrderCheck 069 extends AbstractCheck { 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MSG_SEPARATION = "import.separation"; 076 077 /** 078 * A key is pointing to the warning message text in "messages.properties" 079 * file. 080 */ 081 public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally"; 082 083 /** 084 * A key is pointing to the warning message text in "messages.properties" 085 * file. 086 */ 087 public static final String MSG_ORDERING_LEX = "import.ordering.lex"; 088 089 /** 090 * A key is pointing to the warning message text in "messages.properties" 091 * file. 092 */ 093 public static final String MSG_ORDERING_STATIC = "import.ordering.static"; 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_ORDERING_GROUP = "import.ordering.group"; 100 101 /** The special wildcard that catches all remaining groups. */ 102 private static final String WILDCARD_GROUP_NAME = "*"; 103 104 /** The Forward slash. */ 105 private static final String FORWARD_SLASH = "/"; 106 107 /** Empty array of pattern type needed to initialize check. */ 108 private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0]; 109 110 /** 111 * Specify list of <b>type import</b> groups. Every group identified either by a common prefix 112 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 113 * If an import matches two or more groups, 114 * the best match is selected (closest to the start, and the longest match). 115 * All type imports, which does not match any group, falls into an additional group, 116 * located at the end. Thus, the empty list of type groups (the default value) means one group 117 * for all type imports. 118 */ 119 private String[] groups = CommonUtil.EMPTY_STRING_ARRAY; 120 121 /** 122 * Specify list of <b>static</b> import groups. Every group identified either by a common prefix 123 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 124 * If an import matches two or more groups, 125 * the best match is selected (closest to the start, and the longest match). 126 * All static imports, which does not match any group, fall into an additional group, located 127 * at the end. Thus, the empty list of static groups (the default value) means one group for all 128 * static imports. This property has effect only when the property {@code option} is set to 129 * {@code top} or {@code bottom}. 130 */ 131 private String[] staticGroups = CommonUtil.EMPTY_STRING_ARRAY; 132 133 /** 134 * Control whether type import groups should be separated by, at least, one blank 135 * line or comment and aren't separated internally. It doesn't affect separations for static 136 * imports. 137 */ 138 private boolean separated; 139 140 /** 141 * Control whether static import groups should be separated by, at least, one blank 142 * line or comment and aren't separated internally. When the property {@code option} 143 * is set to {@code top} or {@code bottom}, this property requires {@code staticGroups} 144 * to be enabled. When the property {@code option} is set to {@code under} or 145 * {@code above}, this property allows blank lines between static and non-static 146 * imports within the same group. 147 */ 148 private boolean separatedStaticGroups; 149 150 /** 151 * Control whether type imports within each group should be sorted. 152 * It doesn't affect sorting for static imports. 153 */ 154 private boolean ordered = true; 155 156 /** 157 * Control whether string comparison should be case-sensitive or not. Case-sensitive 158 * sorting is in <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 159 * It affects both type imports and static imports. 160 */ 161 private boolean caseSensitive = true; 162 163 /** Last imported group. */ 164 private int lastGroup; 165 /** Line number of last import. */ 166 private int lastImportLine; 167 /** Name of last import. */ 168 private String lastImport; 169 /** If last import was static. */ 170 private boolean lastImportStatic; 171 /** Whether there were any imports. */ 172 private boolean beforeFirstImport; 173 /** 174 * Whether static and type import groups should be split apart. 175 * When the {@code option} property is set to {@code INFLOW}, {@code ABOVE} or {@code UNDER}, 176 * both the type and static imports use the properties {@code groups} and {@code separated}. 177 * When the {@code option} property is set to {@code TOP} or {@code BOTTOM}, static imports 178 * uses the properties {@code staticGroups} and {@code separatedStaticGroups}. 179 **/ 180 private boolean staticImportsApart; 181 182 /** 183 * Control whether <b>static imports</b> located at <b>top</b> or <b>bottom</b> are 184 * sorted within the group. 185 */ 186 private boolean sortStaticImportsAlphabetically; 187 188 /** 189 * Control whether to use container ordering (Eclipse IDE term) for static imports 190 * or not. 191 */ 192 private boolean useContainerOrderingForStatic; 193 194 /** 195 * Specify policy on the relative order between type imports and static imports. 196 */ 197 private ImportOrderOption option = ImportOrderOption.UNDER; 198 199 /** 200 * Complied array of patterns for property {@code groups}. 201 */ 202 private Pattern[] groupsReg = EMPTY_PATTERN_ARRAY; 203 204 /** 205 * Complied array of patterns for property {@code staticGroups}. 206 */ 207 private Pattern[] staticGroupsReg = EMPTY_PATTERN_ARRAY; 208 209 /** 210 * Creates a new {@code ImportOrderCheck} instance. 211 */ 212 public ImportOrderCheck() { 213 // no code by default 214 } 215 216 /** 217 * Setter to specify policy on the relative order between type imports and static imports. 218 * 219 * @param optionStr string to decode option from 220 * @throws IllegalArgumentException if unable to decode 221 * @since 5.0 222 */ 223 public void setOption(String optionStr) { 224 option = ImportOrderOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 225 } 226 227 /** 228 * Setter to specify list of <b>type import</b> groups. Every group identified either by a 229 * common prefix string, or by a regular expression enclosed in forward slashes 230 * (e.g. {@code /regexp/}). If an import matches two or more groups, 231 * the best match is selected (closest to the start, and the longest match). 232 * All type imports, which does not match any group, falls into an 233 * additional group, located at the end. Thus, the empty list of type groups (the default value) 234 * means one group for all type imports. 235 * 236 * @param packageGroups a comma-separated list of package names/prefixes. 237 * @since 3.2 238 */ 239 public void setGroups(String... packageGroups) { 240 groups = UnmodifiableCollectionUtil.copyOfArray(packageGroups, packageGroups.length); 241 groupsReg = compilePatterns(packageGroups); 242 } 243 244 /** 245 * Setter to specify list of <b>static</b> import groups. Every group identified either by a 246 * common prefix string, or by a regular expression enclosed in forward slashes 247 * (e.g. {@code /regexp/}). If an import matches two or more groups, 248 * the best match is selected (closest to the start, and the longest match). 249 * All static imports, which does not match any group, fall into an 250 * additional group, located at the end. Thus, the empty list of static groups (the default 251 * value) means one group for all static imports. This property has effect only when 252 * the property {@code option} is set to {@code top} or {@code bottom}. 253 * 254 * @param packageGroups a comma-separated list of package names/prefixes. 255 * @since 8.12 256 */ 257 public void setStaticGroups(String... packageGroups) { 258 staticGroups = UnmodifiableCollectionUtil.copyOfArray(packageGroups, packageGroups.length); 259 staticGroupsReg = compilePatterns(packageGroups); 260 } 261 262 /** 263 * Setter to control whether type imports within each group should be sorted. 264 * It doesn't affect sorting for static imports. 265 * 266 * @param ordered 267 * whether lexicographic ordering of imports within a group 268 * required or not. 269 * @since 3.2 270 */ 271 public void setOrdered(boolean ordered) { 272 this.ordered = ordered; 273 } 274 275 /** 276 * Setter to control whether type import groups should be separated by, at least, 277 * one blank line or comment and aren't separated internally. 278 * It doesn't affect separations for static imports. 279 * 280 * @param separated 281 * whether groups should be separated by one blank line or comment. 282 * @since 3.2 283 */ 284 public void setSeparated(boolean separated) { 285 this.separated = separated; 286 } 287 288 /** 289 * Setter to control whether static import groups should be separated by, at least, 290 * one blank line or comment and aren't separated internally. 291 * When the property {@code option} is set to {@code top} or {@code bottom}, 292 * this property requires {@code staticGroups} to be enabled. 293 * When the property {@code option} is set to {@code under} or {@code above}, 294 * this property allows blank lines between static and non-static imports 295 * within the same group. 296 * 297 * @param separatedStaticGroups 298 * whether groups should be separated by one blank line or comment. 299 * @since 8.12 300 */ 301 public void setSeparatedStaticGroups(boolean separatedStaticGroups) { 302 this.separatedStaticGroups = separatedStaticGroups; 303 } 304 305 /** 306 * Setter to control whether string comparison should be case-sensitive or not. 307 * Case-sensitive sorting is in 308 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 309 * It affects both type imports and static imports. 310 * 311 * @param caseSensitive 312 * whether string comparison should be case-sensitive. 313 * @since 3.3 314 */ 315 public void setCaseSensitive(boolean caseSensitive) { 316 this.caseSensitive = caseSensitive; 317 } 318 319 /** 320 * Setter to control whether <b>static imports</b> located at <b>top</b> or 321 * <b>bottom</b> are sorted within the group. 322 * 323 * @param sortAlphabetically true or false. 324 * @since 6.5 325 */ 326 public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) { 327 sortStaticImportsAlphabetically = sortAlphabetically; 328 } 329 330 /** 331 * Setter to control whether to use container ordering (Eclipse IDE term) for static 332 * imports or not. 333 * 334 * @param useContainerOrdering whether to use container ordering for static imports or not. 335 * @since 7.1 336 */ 337 public void setUseContainerOrderingForStatic(boolean useContainerOrdering) { 338 useContainerOrderingForStatic = useContainerOrdering; 339 } 340 341 @Override 342 public int[] getDefaultTokens() { 343 return getRequiredTokens(); 344 } 345 346 @Override 347 public int[] getAcceptableTokens() { 348 return getRequiredTokens(); 349 } 350 351 @Override 352 public int[] getRequiredTokens() { 353 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 354 } 355 356 @Override 357 public void beginTree(DetailAST rootAST) { 358 lastGroup = Integer.MIN_VALUE; 359 lastImportLine = Integer.MIN_VALUE; 360 lastImportStatic = false; 361 beforeFirstImport = true; 362 staticImportsApart = 363 option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM; 364 } 365 366 // -@cs[CyclomaticComplexity] SWITCH was transformed into IF-ELSE. 367 @Override 368 public void visitToken(DetailAST ast) { 369 final FullIdent ident; 370 final boolean isStatic; 371 372 if (ast.getType() == TokenTypes.IMPORT) { 373 ident = FullIdent.createFullIdentBelow(ast); 374 isStatic = false; 375 } 376 else { 377 ident = FullIdent.createFullIdent(ast.getFirstChild() 378 .getNextSibling()); 379 isStatic = true; 380 } 381 382 // using set of IF instead of SWITCH to analyze Enum options to satisfy coverage. 383 // https://github.com/checkstyle/checkstyle/issues/1387 384 if (option == ImportOrderOption.TOP || option == ImportOrderOption.ABOVE) { 385 final boolean isStaticAndNotLastImport = isStatic && !lastImportStatic; 386 doVisitToken(ident, isStatic, isStaticAndNotLastImport, ast); 387 } 388 else if (option == ImportOrderOption.BOTTOM || option == ImportOrderOption.UNDER) { 389 final boolean isLastImportAndNonStatic = lastImportStatic && !isStatic; 390 doVisitToken(ident, isStatic, isLastImportAndNonStatic, ast); 391 } 392 else if (option == ImportOrderOption.INFLOW) { 393 // "previous" argument is useless here 394 doVisitToken(ident, isStatic, true, ast); 395 } 396 else { 397 throw new IllegalStateException( 398 String.format(Locale.ROOT, "Unexpected option for static imports: %s", option)); 399 } 400 401 lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo(); 402 lastImportStatic = isStatic; 403 beforeFirstImport = false; 404 } 405 406 /** 407 * Shares processing... 408 * 409 * @param ident the import to process. 410 * @param isStatic whether the token is static or not. 411 * @param previous previous non-static but current is static (above), or 412 * previous static but current is non-static (under). 413 * @param ast node of the AST. 414 */ 415 private void doVisitToken(FullIdent ident, boolean isStatic, boolean previous, DetailAST ast) { 416 final String name = ident.getText(); 417 final int groupIdx = getGroupNumber(isStatic && staticImportsApart, name); 418 419 if (groupIdx > lastGroup) { 420 handleGreaterGroup(isStatic, ast, name); 421 } 422 else if (groupIdx == lastGroup) { 423 doVisitTokenInSameGroup(isStatic, previous, name, ast); 424 425 if (separatedStaticGroups 426 && isStatic != lastImportStatic 427 && !isSeparatorBeforeImport(ast.getLineNo())) { 428 log(ast, MSG_SEPARATION, name); 429 } 430 } 431 else { 432 handleLowerGroup(previous, ast, name); 433 } 434 if (isSeparatorInGroup(groupIdx, isStatic, ast.getLineNo())) { 435 log(ast, MSG_SEPARATED_IN_GROUP, name); 436 } 437 438 lastGroup = groupIdx; 439 lastImport = name; 440 } 441 442 /** 443 * Handles the case when the current import belongs to a group 444 * that comes after the previous group. Verifies whether a 445 * separator between groups is required. 446 * 447 * @param isStatic whether the current import is static 448 * @param ast the AST node of the current import 449 * @param name the fully qualified name of the current import 450 */ 451 private void handleGreaterGroup(boolean isStatic, DetailAST ast, String name) { 452 if (!beforeFirstImport 453 && ast.getLineNo() - lastImportLine < 2 454 && needSeparator(isStatic)) { 455 log(ast, MSG_SEPARATION, name); 456 } 457 } 458 459 /** 460 * Handles the case when the current import belongs to a group 461 * that should appear before the previous group. Logs either 462 * a static-order violation or a group-order violation. 463 * 464 * @param previous indicates a static/non-static ordering transition 465 * @param ast the AST node of the current import 466 * @param name the fully qualified name of the current import 467 */ 468 private void handleLowerGroup(boolean previous, DetailAST ast, String name) { 469 if (previous 470 && (option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM)) { 471 log(ast, MSG_ORDERING_STATIC, name); 472 } 473 else { 474 log(ast, MSG_ORDERING_GROUP, name); 475 } 476 } 477 478 /** 479 * Checks whether import groups should be separated. 480 * 481 * @param isStatic whether the token is static or not. 482 * @return true if imports groups should be separated. 483 */ 484 private boolean needSeparator(boolean isStatic) { 485 final boolean typeImportSeparator = !isStatic && separated; 486 final boolean staticImportSeparator; 487 if (staticImportsApart) { 488 staticImportSeparator = isStatic && separatedStaticGroups; 489 } 490 else { 491 staticImportSeparator = separated; 492 } 493 final boolean separatorBetween = isStatic != lastImportStatic 494 && (separated || separatedStaticGroups); 495 496 return typeImportSeparator || staticImportSeparator || separatorBetween; 497 } 498 499 /** 500 * Checks whether imports group separated internally. 501 * 502 * @param groupIdx group number. 503 * @param isStatic whether the token is static or not. 504 * @param line the line of the current import. 505 * @return true if imports group are separated internally. 506 */ 507 private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) { 508 final boolean inSameGroup = groupIdx == lastGroup; 509 final boolean result; 510 if (inSameGroup) { 511 result = (isStatic == lastImportStatic || !separatedStaticGroups) 512 && isSeparatorBeforeImport(line); 513 } 514 else { 515 result = !needSeparator(isStatic) && isSeparatorBeforeImport(line); 516 } 517 return result; 518 } 519 520 /** 521 * Checks whether there is any separator before current import. 522 * 523 * @param line the line of the current import. 524 * @return true if there is separator before current import which isn't the first import. 525 */ 526 private boolean isSeparatorBeforeImport(int line) { 527 return line - lastImportLine > 1; 528 } 529 530 /** 531 * Shares processing... 532 * 533 * @param isStatic whether the token is static or not. 534 * @param previous previous non-static but current is static (above), or 535 * previous static but current is non-static (under). 536 * @param name the name of the current import. 537 * @param ast node of the AST. 538 */ 539 private void doVisitTokenInSameGroup(boolean isStatic, 540 boolean previous, String name, DetailAST ast) { 541 if (ordered) { 542 if (option == ImportOrderOption.INFLOW) { 543 if (isWrongOrder(name, isStatic)) { 544 log(ast, MSG_ORDERING_LEX, name, lastImport); 545 } 546 } 547 else { 548 if (previous) { 549 // static vs non-static placement violation 550 log(ast, MSG_ORDERING_STATIC, name); 551 } 552 else if (lastImportStatic == isStatic && isWrongOrder(name, isStatic)) { 553 // lexicographical violation 554 log(ast, MSG_ORDERING_LEX, name, lastImport); 555 } 556 } 557 } 558 } 559 560 /** 561 * Checks whether import name is in wrong order. 562 * 563 * @param name import name. 564 * @param isStatic whether it is a static import name. 565 * @return true if import name is in wrong order. 566 */ 567 private boolean isWrongOrder(String name, boolean isStatic) { 568 final boolean result; 569 if (isStatic) { 570 if (useContainerOrderingForStatic) { 571 result = compareContainerOrder(lastImport, name, caseSensitive) > 0; 572 } 573 else if (staticImportsApart) { 574 result = sortStaticImportsAlphabetically 575 && compare(lastImport, name, caseSensitive) > 0; 576 } 577 else { 578 result = compare(lastImport, name, caseSensitive) > 0; 579 } 580 } 581 else { 582 // out of lexicographic order 583 result = compare(lastImport, name, caseSensitive) > 0; 584 } 585 return result; 586 } 587 588 /** 589 * Compares two import strings. 590 * We first compare the container of the static import, container being the type enclosing 591 * the static element being imported. When this returns 0, we compare the qualified 592 * import name. For e.g. this is what is considered to be container names: 593 * <pre> 594 * import static HttpConstants.COLON => HttpConstants 595 * import static HttpHeaders.addHeader => HttpHeaders 596 * import static HttpHeaders.setHeader => HttpHeaders 597 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 598 * </pre> 599 * 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 = staticGroupsReg; 666 } 667 else { 668 patterns = groupsReg; 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}