View Javadoc
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     =&gt; HttpConstants
532      * import static HttpHeaders.addHeader   =&gt; HttpHeaders
533      * import static HttpHeaders.setHeader   =&gt; HttpHeaders
534      * import static HttpHeaders.Names.DATE  =&gt; 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     =&gt; HttpConstants
579      * import static HttpHeaders.addHeader   =&gt; HttpHeaders
580      * import static HttpHeaders.setHeader   =&gt; HttpHeaders
581      * import static HttpHeaders.Names.DATE  =&gt; 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 }