View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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.CommonUtils;
32  
33  /**
34   * Checks the ordering/grouping of imports. Features are:
35   * <ul>
36   * <li>groups imports: ensures that groups of imports come in a specific order
37   * (e.g., java. comes first, javax. comes second, then everything else)</li>
38   * <li>adds a separation between groups : ensures that a blank line sit between
39   * each group</li>
40   * <li>import groups aren't separated internally: ensures that
41   * each group aren't separated internally by blank line or comment</li>
42   * <li>sorts imports inside each group: ensures that imports within each group
43   * are in lexicographic order</li>
44   * <li>sorts according to case: ensures that the comparison between import is
45   * case sensitive</li>
46   * <li>groups static imports: ensures that static imports are at the top (or the
47   * bottom) of all the imports, or above (or under) each group, or are treated
48   * like non static imports (@see {@link ImportOrderOption}</li>
49   * </ul>.
50   *
51   * <pre>
52   * Properties:
53   * </pre>
54   * <table summary="Properties" border="1">
55   *   <tr><th>name</th><th>Description</th><th>type</th><th>default value</th></tr>
56   *   <tr><td>option</td><td>policy on the relative order between regular imports and static
57   *       imports</td><td>{@link ImportOrderOption}</td><td>under</td></tr>
58   *   <tr><td>groups</td><td>list of imports groups (every group identified either by a common
59   *       prefix string, or by a regular expression enclosed in forward slashes (e.g. /regexp/)</td>
60   *       <td>list of strings</td><td>empty list</td></tr>
61   *   <tr><td>ordered</td><td>whether imports within group should be sorted</td>
62   *       <td>Boolean</td><td>true</td></tr>
63   *   <tr><td>separated</td><td>whether imports groups should be separated by, at least,
64   *       one blank line or comment and aren't separated internally
65   *       </td><td>Boolean</td><td>false</td></tr>
66   *   <tr><td>caseSensitive</td><td>whether string comparison should be case sensitive or not.
67   *       Case sensitive sorting is in ASCII sort order</td><td>Boolean</td><td>true</td></tr>
68   *   <tr><td>sortStaticImportsAlphabetically</td><td>whether static imports grouped by top or
69   *       bottom option are sorted alphabetically or not</td><td>Boolean</td><td>false</td></tr>
70   *   <tr><td>useContainerOrderingForStatic</td><td>whether to use container ordering
71   *       (Eclipse IDE term) for static imports or not</td><td>Boolean</td><td>false</td></tr>
72   * </table>
73   *
74   * <p>
75   * Example:
76   * </p>
77   * <p>To configure the check so that it matches default Eclipse formatter configuration
78   *    (tested on Kepler, Luna and Mars):</p>
79   * <ul>
80   *     <li>group of static imports is on the top</li>
81   *     <li>groups of non-static imports: &quot;java&quot; then &quot;javax&quot;
82   *         packages first, then &quot;org&quot; and then all other imports</li>
83   *     <li>imports will be sorted in the groups</li>
84   *     <li>groups are separated by, at least, one blank line and aren't separated internally</li>
85   * </ul>
86   *
87   * <pre>
88   * &lt;module name=&quot;ImportOrder&quot;&gt;
89   *    &lt;property name=&quot;groups&quot; value=&quot;/^javax?\./,org&quot;/&gt;
90   *    &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
91   *    &lt;property name=&quot;separated&quot; value=&quot;true&quot;/&gt;
92   *    &lt;property name=&quot;option&quot; value=&quot;above&quot;/&gt;
93   *    &lt;property name=&quot;sortStaticImportsAlphabetically&quot; value=&quot;true&quot;/&gt;
94   * &lt;/module&gt;
95   * </pre>
96   *
97   * <p>To configure the check so that it matches default IntelliJ IDEA formatter configuration
98   *    (tested on v14):</p>
99   * <ul>
100  *     <li>group of static imports is on the bottom</li>
101  *     <li>groups of non-static imports: all imports except of &quot;javax&quot; and
102  *         &quot;java&quot;, then &quot;javax&quot; and &quot;java&quot;</li>
103  *     <li>imports will be sorted in the groups</li>
104  *     <li>groups are separated by, at least, one blank line and aren't separated internally</li>
105  * </ul>
106  *
107  *         <p>
108  *         Note: &quot;separated&quot; option is disabled because IDEA default has blank line
109  *         between &quot;java&quot; and static imports, and no blank line between
110  *         &quot;javax&quot; and &quot;java&quot;
111  *         </p>
112  *
113  * <pre>
114  * &lt;module name=&quot;ImportOrder&quot;&gt;
115  *     &lt;property name=&quot;groups&quot; value=&quot;*,javax,java&quot;/&gt;
116  *     &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
117  *     &lt;property name=&quot;separated&quot; value=&quot;false&quot;/&gt;
118  *     &lt;property name=&quot;option&quot; value=&quot;bottom&quot;/&gt;
119  *     &lt;property name=&quot;sortStaticImportsAlphabetically&quot; value=&quot;true&quot;/&gt;
120  * &lt;/module&gt;
121  * </pre>
122  *
123  * <p>To configure the check so that it matches default NetBeans formatter configuration
124  *    (tested on v8):</p>
125  * <ul>
126  *     <li>groups of non-static imports are not defined, all imports will be sorted
127  *         as a one group</li>
128  *     <li>static imports are not separated, they will be sorted along with other imports</li>
129  * </ul>
130  *
131  * <pre>
132  * &lt;module name=&quot;ImportOrder&quot;&gt;
133  *     &lt;property name=&quot;option&quot; value=&quot;inflow&quot;/&gt;
134  * &lt;/module&gt;
135  * </pre>
136  *
137  * <p>
138  * Group descriptions enclosed in slashes are interpreted as regular
139  * expressions. If multiple groups match, the one matching a longer
140  * substring of the imported name will take precedence, with ties
141  * broken first in favor of earlier matches and finally in favor of
142  * the first matching group.
143  * </p>
144  *
145  * <p>
146  * There is always a wildcard group to which everything not in a named group
147  * belongs. If an import does not match a named group, the group belongs to
148  * this wildcard group. The wildcard group position can be specified using the
149  * {@code *} character.
150  * </p>
151  *
152  * <p>Check also has on option making it more flexible:
153  * <b>sortStaticImportsAlphabetically</b> - sets whether static imports grouped by
154  * <b>top</b> or <b>bottom</b> option should be sorted alphabetically or
155  * not, default value is <b>false</b>. It is applied to static imports grouped
156  * with <b>top</b> or <b>bottom</b> options.<br>
157  * This option is helping in reconciling of this Check and other tools like
158  * Eclipse's Organize Imports feature.
159  * </p>
160  * <p>
161  * To configure the Check allows static imports grouped to the <b>top</b>
162  * being sorted alphabetically:
163  * </p>
164  *
165  * <pre>
166  * {@code
167  * import static java.lang.Math.abs;
168  * import static org.abego.treelayout.Configuration.AlignmentInLevel; // OK, alphabetical order
169  *
170  * import org.abego.*;
171  *
172  * import java.util.Set;
173  *
174  * public class SomeClass { ... }
175  * }
176  * </pre>
177  *
178  *
179  * @author Bill Schneider
180  * @author o_sukhodolsky
181  * @author David DIDIER
182  * @author Steve McKay
183  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
184  * @author Andrei Selkin
185  */
186 @FileStatefulCheck
187 public class ImportOrderCheck
188     extends AbstractCheck {
189 
190     /**
191      * A key is pointing to the warning message text in "messages.properties"
192      * file.
193      */
194     public static final String MSG_SEPARATION = "import.separation";
195 
196     /**
197      * A key is pointing to the warning message text in "messages.properties"
198      * file.
199      */
200     public static final String MSG_ORDERING = "import.ordering";
201 
202     /**
203      * A key is pointing to the warning message text in "messages.properties"
204      * file.
205      */
206     public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally";
207 
208     /** The special wildcard that catches all remaining groups. */
209     private static final String WILDCARD_GROUP_NAME = "*";
210 
211     /** Empty array of pattern type needed to initialize check. */
212     private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0];
213 
214     /** List of import groups specified by the user. */
215     private Pattern[] groups = EMPTY_PATTERN_ARRAY;
216     /** Require imports in group be separated. */
217     private boolean separated;
218     /** Require imports in group. */
219     private boolean ordered = true;
220     /** Should comparison be case sensitive. */
221     private boolean caseSensitive = true;
222 
223     /** Last imported group. */
224     private int lastGroup;
225     /** Line number of last import. */
226     private int lastImportLine;
227     /** Name of last import. */
228     private String lastImport;
229     /** If last import was static. */
230     private boolean lastImportStatic;
231     /** Whether there was any imports. */
232     private boolean beforeFirstImport;
233     /** Whether static imports should be sorted alphabetically or not. */
234     private boolean sortStaticImportsAlphabetically;
235     /** Whether to use container ordering (Eclipse IDE term) for static imports or not. */
236     private boolean useContainerOrderingForStatic;
237 
238     /** The policy to enforce. */
239     private ImportOrderOption option = ImportOrderOption.UNDER;
240 
241     /**
242      * Set the option to enforce.
243      * @param optionStr string to decode option from
244      * @throws IllegalArgumentException if unable to decode
245      */
246     public void setOption(String optionStr) {
247         try {
248             option = ImportOrderOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
249         }
250         catch (IllegalArgumentException iae) {
251             throw new IllegalArgumentException("unable to parse " + optionStr, iae);
252         }
253     }
254 
255     /**
256      * Sets the list of package groups and the order they should occur in the
257      * file.
258      *
259      * @param packageGroups a comma-separated list of package names/prefixes.
260      */
261     public void setGroups(String... packageGroups) {
262         groups = new Pattern[packageGroups.length];
263 
264         for (int i = 0; i < packageGroups.length; i++) {
265             String pkg = packageGroups[i];
266             final Pattern grp;
267 
268             // if the pkg name is the wildcard, make it match zero chars
269             // from any name, so it will always be used as last resort.
270             if (WILDCARD_GROUP_NAME.equals(pkg)) {
271                 // matches any package
272                 grp = Pattern.compile("");
273             }
274             else if (CommonUtils.startsWithChar(pkg, '/')) {
275                 if (!CommonUtils.endsWithChar(pkg, '/')) {
276                     throw new IllegalArgumentException("Invalid group");
277                 }
278                 pkg = pkg.substring(1, pkg.length() - 1);
279                 grp = Pattern.compile(pkg);
280             }
281             else {
282                 final StringBuilder pkgBuilder = new StringBuilder(pkg);
283                 if (!CommonUtils.endsWithChar(pkg, '.')) {
284                     pkgBuilder.append('.');
285                 }
286                 grp = Pattern.compile("^" + Pattern.quote(pkgBuilder.toString()));
287             }
288 
289             groups[i] = grp;
290         }
291     }
292 
293     /**
294      * Sets whether or not imports should be ordered within any one group of
295      * imports.
296      *
297      * @param ordered
298      *            whether lexicographic ordering of imports within a group
299      *            required or not.
300      */
301     public void setOrdered(boolean ordered) {
302         this.ordered = ordered;
303     }
304 
305     /**
306      * Sets whether or not groups of imports must be separated from one another
307      * by at least one blank line.
308      *
309      * @param separated
310      *            whether groups should be separated by one blank line.
311      */
312     public void setSeparated(boolean separated) {
313         this.separated = separated;
314     }
315 
316     /**
317      * Sets whether string comparison should be case sensitive or not.
318      *
319      * @param caseSensitive
320      *            whether string comparison should be case sensitive.
321      */
322     public void setCaseSensitive(boolean caseSensitive) {
323         this.caseSensitive = caseSensitive;
324     }
325 
326     /**
327      * Sets whether static imports (when grouped using 'top' and 'bottom' option)
328      * are sorted alphabetically or according to the package groupings.
329      * @param sortAlphabetically true or false.
330      */
331     public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) {
332         sortStaticImportsAlphabetically = sortAlphabetically;
333     }
334 
335     /**
336      * Sets whether to use container ordering (Eclipse IDE term) for static imports or not.
337      * @param useContainerOrdering whether to use container ordering for static imports or not.
338      */
339     public void setUseContainerOrderingForStatic(boolean useContainerOrdering) {
340         useContainerOrderingForStatic = useContainerOrdering;
341     }
342 
343     @Override
344     public int[] getDefaultTokens() {
345         return getAcceptableTokens();
346     }
347 
348     @Override
349     public int[] getAcceptableTokens() {
350         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
351     }
352 
353     @Override
354     public int[] getRequiredTokens() {
355         return new int[] {TokenTypes.IMPORT};
356     }
357 
358     @Override
359     public void beginTree(DetailAST rootAST) {
360         lastGroup = Integer.MIN_VALUE;
361         lastImportLine = Integer.MIN_VALUE;
362         lastImport = "";
363         lastImportStatic = false;
364         beforeFirstImport = true;
365     }
366 
367     // -@cs[CyclomaticComplexity] SWITCH was transformed into IF-ELSE.
368     @Override
369     public void visitToken(DetailAST ast) {
370         final FullIdent ident;
371         final boolean isStatic;
372 
373         if (ast.getType() == TokenTypes.IMPORT) {
374             ident = FullIdent.createFullIdentBelow(ast);
375             isStatic = false;
376         }
377         else {
378             ident = FullIdent.createFullIdent(ast.getFirstChild()
379                     .getNextSibling());
380             isStatic = true;
381         }
382 
383         final boolean isStaticAndNotLastImport = isStatic && !lastImportStatic;
384         final boolean isLastImportAndNonStatic = lastImportStatic && !isStatic;
385 
386         // using set of IF instead of SWITCH to analyze Enum options to satisfy coverage.
387         // https://github.com/checkstyle/checkstyle/issues/1387
388         if (option == ImportOrderOption.TOP) {
389             if (isLastImportAndNonStatic) {
390                 lastGroup = Integer.MIN_VALUE;
391                 lastImport = "";
392             }
393             doVisitToken(ident, isStatic, isStaticAndNotLastImport);
394 
395             if (isStaticAndNotLastImport && !beforeFirstImport) {
396                 log(ident.getLineNo(), MSG_ORDERING, ident.getText());
397             }
398         }
399         else if (option == ImportOrderOption.BOTTOM) {
400             if (isStaticAndNotLastImport) {
401                 lastGroup = Integer.MIN_VALUE;
402                 lastImport = "";
403             }
404             doVisitToken(ident, isStatic, isLastImportAndNonStatic);
405 
406             if (isLastImportAndNonStatic) {
407                 log(ident.getLineNo(), MSG_ORDERING, ident.getText());
408             }
409         }
410         else if (option == ImportOrderOption.ABOVE) {
411             // previous non-static but current is static
412             doVisitToken(ident, isStatic, isStaticAndNotLastImport);
413         }
414         else if (option == ImportOrderOption.UNDER) {
415             doVisitToken(ident, isStatic, isLastImportAndNonStatic);
416         }
417         else if (option == ImportOrderOption.INFLOW) {
418             // "previous" argument is useless here
419             doVisitToken(ident, isStatic, true);
420         }
421         else {
422             throw new IllegalStateException(
423                     "Unexpected option for static imports: " + option);
424         }
425 
426         lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo();
427         lastImportStatic = isStatic;
428         beforeFirstImport = false;
429     }
430 
431     /**
432      * Shares processing...
433      *
434      * @param ident the import to process.
435      * @param isStatic whether the token is static or not.
436      * @param previous previous non-static but current is static (above), or
437      *                  previous static but current is non-static (under).
438      */
439     private void doVisitToken(FullIdent ident, boolean isStatic,
440             boolean previous) {
441         final String name = ident.getText();
442         final int groupIdx = getGroupNumber(name);
443         final int line = ident.getLineNo();
444 
445         if (groupIdx > lastGroup) {
446             if (!beforeFirstImport && separated && line - lastImportLine < 2
447                 && !isInSameGroup(groupIdx, isStatic)) {
448                 log(line, MSG_SEPARATION, name);
449             }
450         }
451         else if (isInSameGroup(groupIdx, isStatic)) {
452             doVisitTokenInSameGroup(isStatic, previous, name, line);
453         }
454         else {
455             log(line, MSG_ORDERING, name);
456         }
457         if (isSeparatorInGroup(groupIdx, isStatic, line)) {
458             log(line, MSG_SEPARATED_IN_GROUP, name);
459         }
460 
461         lastGroup = groupIdx;
462         lastImport = name;
463     }
464 
465     /**
466      * Checks whether imports group separated internally.
467      * @param groupIdx group number.
468      * @param isStatic whether the token is static or not.
469      * @param line the line of the current import.
470      * @return true if imports group are separated internally.
471      */
472     private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) {
473         final boolean inSameGroup = isInSameGroup(groupIdx, isStatic);
474         return (!separated || inSameGroup) && isSeparatorBeforeImport(line);
475     }
476 
477     /**
478      * Checks whether there is any separator before current import.
479      * @param line the line of the current import.
480      * @return true if there is separator before current import which isn't the first import.
481      */
482     private boolean isSeparatorBeforeImport(int line) {
483         return !beforeFirstImport && line - lastImportLine > 1;
484     }
485 
486     /**
487      * Checks whether imports are in same group.
488      * @param groupIdx group number.
489      * @param isStatic whether the token is static or not.
490      * @return true if imports are in same group.
491      */
492     private boolean isInSameGroup(int groupIdx, boolean isStatic) {
493         final boolean isStaticImportGroupIndependent =
494             option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM;
495         final boolean result;
496         if (isStaticImportGroupIndependent) {
497             result = isStatic && lastImportStatic
498                 || groupIdx == lastGroup && isStatic == lastImportStatic;
499         }
500         else {
501             result = groupIdx == lastGroup;
502         }
503         return result;
504     }
505 
506     /**
507      * Shares processing...
508      *
509      * @param isStatic whether the token is static or not.
510      * @param previous previous non-static but current is static (above), or
511      *     previous static but current is non-static (under).
512      * @param name the name of the current import.
513      * @param line the line of the current import.
514      */
515     private void doVisitTokenInSameGroup(boolean isStatic,
516             boolean previous, String name, int line) {
517         if (ordered) {
518             if (option == ImportOrderOption.INFLOW) {
519                 if (isWrongOrder(name, isStatic)) {
520                     log(line, MSG_ORDERING, name);
521                 }
522             }
523             else {
524                 final boolean shouldFireError =
525                     // previous non-static but current is static (above)
526                     // or
527                     // previous static but current is non-static (under)
528                     previous
529                         ||
530                         // current and previous static or current and
531                         // previous non-static
532                         lastImportStatic == isStatic
533                     && isWrongOrder(name, isStatic);
534 
535                 if (shouldFireError) {
536                     log(line, MSG_ORDERING, name);
537                 }
538             }
539         }
540     }
541 
542     /**
543      * Checks whether import name is in wrong order.
544      * @param name import name.
545      * @param isStatic whether it is a static import name.
546      * @return true if import name is in wrong order.
547      */
548     private boolean isWrongOrder(String name, boolean isStatic) {
549         final boolean result;
550         if (isStatic) {
551             if (useContainerOrderingForStatic) {
552                 result = compareContainerOrder(lastImport, name, caseSensitive) >= 0;
553             }
554             else if (option == ImportOrderOption.TOP
555                 || option == ImportOrderOption.BOTTOM) {
556                 result = sortStaticImportsAlphabetically
557                     && compare(lastImport, name, caseSensitive) >= 0;
558             }
559             else {
560                 result = compare(lastImport, name, caseSensitive) >= 0;
561             }
562         }
563         else {
564             // out of lexicographic order
565             result = compare(lastImport, name, caseSensitive) >= 0;
566         }
567         return result;
568     }
569 
570     /**
571      * Compares two import strings.
572      * We first compare the container of the static import, container being the type enclosing
573      * the static element being imported. When this returns 0, we compare the qualified
574      * import name. For e.g. this is what is considered to be container names:
575      * <p>
576      * import static HttpConstants.COLON     => HttpConstants
577      * import static HttpHeaders.addHeader   => HttpHeaders
578      * import static HttpHeaders.setHeader   => HttpHeaders
579      * import static HttpHeaders.Names.DATE  => HttpHeaders.Names
580      * </p>
581      * <p>
582      * According to this logic, HttpHeaders.Names would come after HttpHeaders.
583      *
584      * For more details, see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=473629#c3">
585      * static imports comparison method</a> in Eclipse.
586      * </p>
587      *
588      * @param importName1 first import name.
589      * @param importName2 second import name.
590      * @param caseSensitive whether the comparison of fully qualified import names is case
591      *                      sensitive.
592      * @return the value {@code 0} if str1 is equal to str2; a value
593      *         less than {@code 0} if str is less than the str2 (container order
594      *         or lexicographical); and a value greater than {@code 0} if str1 is greater than str2
595      *         (container order or lexicographically).
596      */
597     private static int compareContainerOrder(String importName1, String importName2,
598                                              boolean caseSensitive) {
599         final String container1 = getImportContainer(importName1);
600         final String container2 = getImportContainer(importName2);
601         final int compareContainersOrderResult;
602         if (caseSensitive) {
603             compareContainersOrderResult = container1.compareTo(container2);
604         }
605         else {
606             compareContainersOrderResult = container1.compareToIgnoreCase(container2);
607         }
608         final int result;
609         if (compareContainersOrderResult == 0) {
610             result = compare(importName1, importName2, caseSensitive);
611         }
612         else {
613             result = compareContainersOrderResult;
614         }
615         return result;
616     }
617 
618     /**
619      * Extracts import container name from fully qualified import name.
620      * An import container name is the type which encloses the static element being imported.
621      * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names:
622      * <p>
623      * import static HttpConstants.COLON     => HttpConstants
624      * import static HttpHeaders.addHeader   => HttpHeaders
625      * import static HttpHeaders.setHeader   => HttpHeaders
626      * import static HttpHeaders.Names.DATE  => HttpHeaders.Names
627      * </p>
628      * @param qualifiedImportName fully qualified import name.
629      * @return import container name.
630      */
631     private static String getImportContainer(String qualifiedImportName) {
632         final int lastDotIndex = qualifiedImportName.lastIndexOf('.');
633         return qualifiedImportName.substring(0, lastDotIndex);
634     }
635 
636     /**
637      * Finds out what group the specified import belongs to.
638      *
639      * @param name the import name to find.
640      * @return group number for given import name.
641      */
642     private int getGroupNumber(String name) {
643         int bestIndex = groups.length;
644         int bestEnd = -1;
645         int bestPos = Integer.MAX_VALUE;
646 
647         // find out what group this belongs in
648         // loop over groups and get index
649         for (int i = 0; i < groups.length; i++) {
650             final Matcher matcher = groups[i].matcher(name);
651             if (matcher.find()) {
652                 if (matcher.start() < bestPos) {
653                     bestIndex = i;
654                     bestEnd = matcher.end();
655                     bestPos = matcher.start();
656                 }
657                 else if (matcher.start() == bestPos && matcher.end() > bestEnd) {
658                     bestIndex = i;
659                     bestEnd = matcher.end();
660                 }
661             }
662         }
663 
664         return bestIndex;
665     }
666 
667     /**
668      * Compares two strings.
669      *
670      * @param string1
671      *            the first string.
672      * @param string2
673      *            the second string.
674      * @param caseSensitive
675      *            whether the comparison is case sensitive.
676      * @return the value {@code 0} if string1 is equal to string2; a value
677      *         less than {@code 0} if string1 is lexicographically less
678      *         than the string2; and a value greater than {@code 0} if
679      *         string1 is lexicographically greater than string2.
680      */
681     private static int compare(String string1, String string2,
682             boolean caseSensitive) {
683         final int result;
684         if (caseSensitive) {
685             result = string1.compareTo(string2);
686         }
687         else {
688             result = string1.compareToIgnoreCase(string2);
689         }
690 
691         return result;
692     }
693 
694 }