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.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 * <ul>
035 * <li>groups imports: ensures that groups of imports come in a specific order
036 * (e.g., java. comes first, javax. comes second, then everything else)</li>
037 * <li>adds a separation between groups : ensures that a blank line sit between
038 * each group</li>
039 * <li>import groups aren't separated internally: ensures that
040 * each group aren't separated internally by blank line or comment</li>
041 * <li>sorts imports inside each group: ensures that imports within each group
042 * are in lexicographic order</li>
043 * <li>sorts according to case: ensures that the comparison between import is
044 * case sensitive</li>
045 * <li>groups static imports: ensures that static imports are at the top (or the
046 * bottom) of all the imports, or above (or under) each group, or are treated
047 * like non static imports (@see {@link ImportOrderOption}</li>
048 * </ul>.
049 *
050 * <pre>
051 * Properties:
052 * </pre>
053 * <table summary="Properties" border="1">
054 *   <tr><th>name</th><th>Description</th><th>type</th><th>default value</th></tr>
055 *   <tr><td>option</td><td>policy on the relative order between regular imports and static
056 *       imports</td><td>{@link ImportOrderOption}</td><td>under</td></tr>
057 *   <tr><td>groups</td><td>list of imports groups (every group identified either by a common
058 *       prefix string, or by a regular expression enclosed in forward slashes (e.g. /regexp/)</td>
059 *       <td>list of strings</td><td>empty list</td></tr>
060 *   <tr><td>ordered</td><td>whether imports within group should be sorted</td>
061 *       <td>Boolean</td><td>true</td></tr>
062 *   <tr><td>separated</td><td>whether imports groups should be separated by, at least,
063 *       one blank line or comment and aren't separated internally
064 *       </td><td>Boolean</td><td>false</td></tr>
065 *   <tr><td>caseSensitive</td><td>whether string comparison should be case sensitive or not.
066 *       Case sensitive sorting is in ASCII sort order</td><td>Boolean</td><td>true</td></tr>
067 *   <tr><td>sortStaticImportsAlphabetically</td><td>whether static imports grouped by top or
068 *       bottom option are sorted alphabetically or not</td><td>Boolean</td><td>false</td></tr>
069 *   <tr><td>useContainerOrderingForStatic</td><td>whether to use container ordering
070 *       (Eclipse IDE term) for static imports or not</td><td>Boolean</td><td>false</td></tr>
071 * </table>
072 *
073 * <p>
074 * Example:
075 * </p>
076 * <p>To configure the check so that it matches default Eclipse formatter configuration
077 *    (tested on Kepler, Luna and Mars):</p>
078 * <ul>
079 *     <li>group of static imports is on the top</li>
080 *     <li>groups of non-static imports: &quot;java&quot; then &quot;javax&quot;
081 *         packages first, then &quot;org&quot; and then all other imports</li>
082 *     <li>imports will be sorted in the groups</li>
083 *     <li>groups are separated by, at least, one blank line and aren't separated internally</li>
084 * </ul>
085 *
086 * <pre>
087 * &lt;module name=&quot;ImportOrder&quot;&gt;
088 *    &lt;property name=&quot;groups&quot; value=&quot;/^javax?\./,org&quot;/&gt;
089 *    &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
090 *    &lt;property name=&quot;separated&quot; value=&quot;true&quot;/&gt;
091 *    &lt;property name=&quot;option&quot; value=&quot;above&quot;/&gt;
092 *    &lt;property name=&quot;sortStaticImportsAlphabetically&quot; value=&quot;true&quot;/&gt;
093 * &lt;/module&gt;
094 * </pre>
095 *
096 * <p>To configure the check so that it matches default IntelliJ IDEA formatter configuration
097 *    (tested on v14):</p>
098 * <ul>
099 *     <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
186public 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        }
396        else if (option == ImportOrderOption.BOTTOM) {
397
398            if (isStaticAndNotLastImport) {
399                lastGroup = Integer.MIN_VALUE;
400                lastImport = "";
401            }
402            doVisitToken(ident, isStatic, isLastImportAndNonStatic);
403
404        }
405        else if (option == ImportOrderOption.ABOVE) {
406            // previous non-static but current is static
407            doVisitToken(ident, isStatic, isStaticAndNotLastImport);
408
409        }
410        else if (option == ImportOrderOption.UNDER) {
411            doVisitToken(ident, isStatic, isLastImportAndNonStatic);
412
413        }
414        else if (option == ImportOrderOption.INFLOW) {
415            // "previous" argument is useless here
416            doVisitToken(ident, isStatic, true);
417
418        }
419        else {
420            throw new IllegalStateException(
421                    "Unexpected option for static imports: " + option);
422        }
423
424        lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo();
425        lastImportStatic = isStatic;
426        beforeFirstImport = false;
427    }
428
429    /**
430     * Shares processing...
431     *
432     * @param ident the import to process.
433     * @param isStatic whether the token is static or not.
434     * @param previous previous non-static but current is static (above), or
435     *                  previous static but current is non-static (under).
436     */
437    private void doVisitToken(FullIdent ident, boolean isStatic,
438            boolean previous) {
439        final String name = ident.getText();
440        final int groupIdx = getGroupNumber(name);
441        final int line = ident.getLineNo();
442
443        if (isInSameGroup(groupIdx, isStatic)) {
444            doVisitTokenInSameGroup(isStatic, previous, name, line);
445        }
446        else if (groupIdx > lastGroup) {
447            if (!beforeFirstImport && separated && line - lastImportLine < 2) {
448                log(line, MSG_SEPARATION, name);
449            }
450        }
451        else {
452            log(line, MSG_ORDERING, name);
453        }
454        if (isSeparatorInGroup(groupIdx, isStatic, line)) {
455            log(line, MSG_SEPARATED_IN_GROUP, name);
456        }
457
458        lastGroup = groupIdx;
459        lastImport = name;
460    }
461
462    /**
463     * Checks whether imports group separated internally.
464     * @param groupIdx group number.
465     * @param isStatic whether the token is static or not.
466     * @param line the line of the current import.
467     * @return true if imports group are separated internally.
468     */
469    private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) {
470        final boolean inSameGroup = isInSameGroup(groupIdx, isStatic);
471        return (!separated || inSameGroup) && isSeparatorBeforeImport(line);
472    }
473
474    /**
475     * Checks whether there is any separator before current import.
476     * @param line the line of the current import.
477     * @return true if there is separator before current import which isn't the first import.
478     */
479    private boolean isSeparatorBeforeImport(int line) {
480        return !beforeFirstImport && line - lastImportLine > 1;
481    }
482
483    /**
484     * Checks whether imports are in same group.
485     * @param groupIdx group number.
486     * @param isStatic whether the token is static or not.
487     * @return true if imports are in same group.
488     */
489    private boolean isInSameGroup(int groupIdx, boolean isStatic) {
490        final boolean isStaticImportGroupIndependent =
491            option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM;
492        final boolean result;
493        if (isStaticImportGroupIndependent) {
494            result = isStatic && lastImportStatic
495                || groupIdx == lastGroup && isStatic == lastImportStatic;
496        }
497        else {
498            result = groupIdx == lastGroup;
499        }
500        return result;
501    }
502
503    /**
504     * Shares processing...
505     *
506     * @param isStatic whether the token is static or not.
507     * @param previous previous non-static but current is static (above), or
508     *     previous static but current is non-static (under).
509     * @param name the name of the current import.
510     * @param line the line of the current import.
511     */
512    private void doVisitTokenInSameGroup(boolean isStatic,
513            boolean previous, String name, int line) {
514        if (ordered) {
515            if (option == ImportOrderOption.INFLOW) {
516                if (isWrongOrder(name, isStatic)) {
517                    log(line, MSG_ORDERING, name);
518                }
519            }
520            else {
521                final boolean shouldFireError =
522                    // previous non-static but current is static (above)
523                    // or
524                    // previous static but current is non-static (under)
525                    previous
526                        ||
527                        // current and previous static or current and
528                        // previous non-static
529                        lastImportStatic == isStatic
530                    && isWrongOrder(name, isStatic);
531
532                if (shouldFireError) {
533                    log(line, MSG_ORDERING, name);
534                }
535            }
536        }
537    }
538
539    /**
540     * Checks whether import name is in wrong order.
541     * @param name import name.
542     * @param isStatic whether it is a static import name.
543     * @return true if import name is in wrong order.
544     */
545    private boolean isWrongOrder(String name, boolean isStatic) {
546        final boolean result;
547        if (isStatic) {
548            if (useContainerOrderingForStatic) {
549                result = compareContainerOrder(lastImport, name, caseSensitive) >= 0;
550            }
551            else if (option == ImportOrderOption.TOP
552                || option == ImportOrderOption.BOTTOM) {
553                result = sortStaticImportsAlphabetically
554                    && compare(lastImport, name, caseSensitive) >= 0;
555            }
556            else {
557                result = compare(lastImport, name, caseSensitive) >= 0;
558            }
559        }
560        else {
561            // out of lexicographic order
562            result = compare(lastImport, name, caseSensitive) >= 0;
563        }
564        return result;
565    }
566
567    /**
568     * Compares two import strings.
569     * We first compare the container of the static import, container being the type enclosing
570     * the static element being imported. When this returns 0, we compare the qualified
571     * import name. For e.g. this is what is considered to be container names:
572     * <p>
573     * import static HttpConstants.COLON     => HttpConstants
574     * import static HttpHeaders.addHeader   => HttpHeaders
575     * import static HttpHeaders.setHeader   => HttpHeaders
576     * import static HttpHeaders.Names.DATE  => HttpHeaders.Names
577     * </p>
578     * <p>
579     * According to this logic, HttpHeaders.Names would come after HttpHeaders.
580     *
581     * For more details, see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=473629#c3">
582     * static imports comparison method</a> in Eclipse.
583     * </p>
584     *
585     * @param importName1 first import name.
586     * @param importName2 second import name.
587     * @param caseSensitive whether the comparison of fully qualified import names is case
588     *                      sensitive.
589     * @return the value {@code 0} if str1 is equal to str2; a value
590     *         less than {@code 0} if str is less than the str2 (container order
591     *         or lexicographical); and a value greater than {@code 0} if str1 is greater than str2
592     *         (container order or lexicographically).
593     */
594    private static int compareContainerOrder(String importName1, String importName2,
595                                             boolean caseSensitive) {
596        final String container1 = getImportContainer(importName1);
597        final String container2 = getImportContainer(importName2);
598        final int compareContainersOrderResult;
599        if (caseSensitive) {
600            compareContainersOrderResult = container1.compareTo(container2);
601        }
602        else {
603            compareContainersOrderResult = container1.compareToIgnoreCase(container2);
604        }
605        final int result;
606        if (compareContainersOrderResult == 0) {
607            result = compare(importName1, importName2, caseSensitive);
608        }
609        else {
610            result = compareContainersOrderResult;
611        }
612        return result;
613    }
614
615    /**
616     * Extracts import container name from fully qualified import name.
617     * An import container name is the type which encloses the static element being imported.
618     * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names:
619     * <p>
620     * import static HttpConstants.COLON     => HttpConstants
621     * import static HttpHeaders.addHeader   => HttpHeaders
622     * import static HttpHeaders.setHeader   => HttpHeaders
623     * import static HttpHeaders.Names.DATE  => HttpHeaders.Names
624     * </p>
625     * @param qualifiedImportName fully qualified import name.
626     * @return import container name.
627     */
628    private static String getImportContainer(String qualifiedImportName) {
629        final int lastDotIndex = qualifiedImportName.lastIndexOf('.');
630        return qualifiedImportName.substring(0, lastDotIndex);
631    }
632
633    /**
634     * Finds out what group the specified import belongs to.
635     *
636     * @param name the import name to find.
637     * @return group number for given import name.
638     */
639    private int getGroupNumber(String name) {
640        int bestIndex = groups.length;
641        int bestEnd = -1;
642        int bestPos = Integer.MAX_VALUE;
643
644        // find out what group this belongs in
645        // loop over groups and get index
646        for (int i = 0; i < groups.length; i++) {
647            final Matcher matcher = groups[i].matcher(name);
648            if (matcher.find()) {
649                if (matcher.start() < bestPos) {
650                    bestIndex = i;
651                    bestEnd = matcher.end();
652                    bestPos = matcher.start();
653                }
654                else if (matcher.start() == bestPos && matcher.end() > bestEnd) {
655                    bestIndex = i;
656                    bestEnd = matcher.end();
657                }
658            }
659        }
660
661        return bestIndex;
662    }
663
664    /**
665     * Compares two strings.
666     *
667     * @param string1
668     *            the first string.
669     * @param string2
670     *            the second string.
671     * @param caseSensitive
672     *            whether the comparison is case sensitive.
673     * @return the value {@code 0} if string1 is equal to string2; a value
674     *         less than {@code 0} if string1 is lexicographically less
675     *         than the string2; and a value greater than {@code 0} if
676     *         string1 is lexicographically greater than string2.
677     */
678    private static int compare(String string1, String string2,
679            boolean caseSensitive) {
680        final int result;
681        if (caseSensitive) {
682            result = string1.compareTo(string2);
683        }
684        else {
685            result = string1.compareToIgnoreCase(string2);
686        }
687
688        return result;
689    }
690}