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