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