1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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 * <div>
36 * Checks the ordering/grouping of imports. Features are:
37 * </div>
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 *
65 * @since 3.2
66 */
67 @FileStatefulCheck
68 public class ImportOrderCheck
69 extends AbstractCheck {
70
71 /**
72 * A key is pointing to the warning message text in "messages.properties"
73 * file.
74 */
75 public static final String MSG_SEPARATION = "import.separation";
76
77 /**
78 * A key is pointing to the warning message text in "messages.properties"
79 * file.
80 */
81 public static final String MSG_ORDERING = "import.ordering";
82
83 /**
84 * A key is pointing to the warning message text in "messages.properties"
85 * file.
86 */
87 public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally";
88
89 /** The special wildcard that catches all remaining groups. */
90 private static final String WILDCARD_GROUP_NAME = "*";
91
92 /** The Forward slash. */
93 private static final String FORWARD_SLASH = "/";
94
95 /** Empty array of pattern type needed to initialize check. */
96 private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0];
97
98 /**
99 * 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 }