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