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