001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.imports;
021
022import java.util.Locale;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.FullIdent;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
032
033/**
034 * Checks the ordering/grouping of imports. Features are:
035 * <ul>
036 * <li>groups imports: ensures that groups of imports come in a specific order
037 * (e.g., java. comes first, javax. comes second, then everything else)</li>
038 * <li>adds a separation between groups : ensures that a blank line sit between
039 * each group</li>
040 * <li>import groups aren't separated internally: ensures that
041 * each group aren't separated internally by blank line or comment</li>
042 * <li>sorts imports inside each group: ensures that imports within each group
043 * are in lexicographic order</li>
044 * <li>sorts according to case: ensures that the comparison between import is
045 * case sensitive</li>
046 * <li>groups static imports: ensures that static imports are at the top (or the
047 * bottom) of all the imports, or above (or under) each group, or are treated
048 * like non static imports (@see {@link ImportOrderOption}</li>
049 * </ul>.
050 *
051 * <pre>
052 * Properties:
053 * </pre>
054 * <table summary="Properties" border="1">
055 *   <tr><th>name</th><th>Description</th><th>type</th><th>default value</th></tr>
056 *   <tr><td>option</td><td>policy on the relative order between regular imports and static
057 *       imports</td><td>{@link ImportOrderOption}</td><td>under</td></tr>
058 *   <tr><td>groups</td><td>list of imports groups (every group identified either by a common
059 *       prefix string, or by a regular expression enclosed in forward slashes (e.g. /regexp/)</td>
060 *       <td>list of strings</td><td>empty list</td></tr>
061 *   <tr><td>ordered</td><td>whether imports within group should be sorted</td>
062 *       <td>Boolean</td><td>true</td></tr>
063 *   <tr><td>separated</td><td>whether imports groups should be separated by, at least,
064 *       one blank line or comment and aren't separated internally
065 *       </td><td>Boolean</td><td>false</td></tr>
066 *   <tr><td>caseSensitive</td><td>whether string comparison should be case sensitive or not.
067 *       Case sensitive sorting is in ASCII sort order</td><td>Boolean</td><td>true</td></tr>
068 *   <tr><td>sortStaticImportsAlphabetically</td><td>whether static imports grouped by top or
069 *       bottom option are sorted alphabetically or not</td><td>Boolean</td><td>false</td></tr>
070 *   <tr><td>useContainerOrderingForStatic</td><td>whether to use container ordering
071 *       (Eclipse IDE term) for static imports or not</td><td>Boolean</td><td>false</td></tr>
072 * </table>
073 *
074 * <p>
075 * Example:
076 * </p>
077 * <p>To configure the check so that it matches default Eclipse formatter configuration
078 *    (tested on Kepler, Luna and Mars):</p>
079 * <ul>
080 *     <li>group of static imports is on the top</li>
081 *     <li>groups of non-static imports: &quot;java&quot; then &quot;javax&quot;
082 *         packages first, then &quot;org&quot; and then all other imports</li>
083 *     <li>imports will be sorted in the groups</li>
084 *     <li>groups are separated by, at least, one blank line and aren't separated internally</li>
085 * </ul>
086 *
087 * <pre>
088 * &lt;module name=&quot;ImportOrder&quot;&gt;
089 *    &lt;property name=&quot;groups&quot; value=&quot;/^javax?\./,org&quot;/&gt;
090 *    &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
091 *    &lt;property name=&quot;separated&quot; value=&quot;true&quot;/&gt;
092 *    &lt;property name=&quot;option&quot; value=&quot;above&quot;/&gt;
093 *    &lt;property name=&quot;sortStaticImportsAlphabetically&quot; value=&quot;true&quot;/&gt;
094 * &lt;/module&gt;
095 * </pre>
096 *
097 * <p>To configure the check so that it matches default IntelliJ IDEA formatter configuration
098 *    (tested on v14):</p>
099 * <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
187public 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}