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