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 => HttpConstants
570 * import static HttpHeaders.addHeader => HttpHeaders
571 * import static HttpHeaders.setHeader => HttpHeaders
572 * import static HttpHeaders.Names.DATE => 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 => HttpConstants
617 * import static HttpHeaders.addHeader => HttpHeaders
618 * import static HttpHeaders.setHeader => HttpHeaders
619 * import static HttpHeaders.Names.DATE => 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 }