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: "java" then "javax" 082 * packages first, then "org" 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 * <module name="ImportOrder"> 089 * <property name="groups" value="/^javax?\./,org"/> 090 * <property name="ordered" value="true"/> 091 * <property name="separated" value="true"/> 092 * <property name="option" value="above"/> 093 * <property name="sortStaticImportsAlphabetically" value="true"/> 094 * </module> 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 "javax" and 102 * "java", then "javax" and "java"</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: "separated" option is disabled because IDEA default has blank line 109 * between "java" and static imports, and no blank line between 110 * "javax" and "java" 111 * </p> 112 * 113 * <pre> 114 * <module name="ImportOrder"> 115 * <property name="groups" value="*,javax,java"/> 116 * <property name="ordered" value="true"/> 117 * <property name="separated" value="false"/> 118 * <property name="option" value="bottom"/> 119 * <property name="sortStaticImportsAlphabetically" value="true"/> 120 * </module> 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 * <module name="ImportOrder"> 133 * <property name="option" value="inflow"/> 134 * </module> 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}