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