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     =&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}