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