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