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