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