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