View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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   * <div>
36   * Checks the ordering/grouping of imports. Features are:
37   * </div>
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/}). If an import matches two or more groups,
77   * the best match is selected (closest to the start, and the longest match).
78   * All type imports, which does not match any group, falls into an
79   * additional group, located at the end.
80   * Thus, the empty list of type groups (the default value) means one group for all type imports.
81   * Type is {@code java.lang.String[]}.
82   * Default value is {@code ""}.
83   * </li>
84   * <li>
85   * Property {@code option} - Specify policy on the relative order between type imports and static
86   * imports.
87   * Type is {@code com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderOption}.
88   * Default value is {@code under}.
89   * </li>
90   * <li>
91   * Property {@code ordered} - Control whether type imports within each group should be
92   * sorted.
93   * It doesn't affect sorting for static imports.
94   * Type is {@code boolean}.
95   * Default value is {@code true}.
96   * </li>
97   * <li>
98   * Property {@code separated} - Control whether type import groups should be separated
99   * 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
160 public 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 }