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