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