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