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.ArrayList; 023import java.util.List; 024import java.util.StringTokenizer; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.FullIdent; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 034 035/** 036 * <p> 037 * Checks that the groups of import declarations appear in the order specified 038 * by the user. If there is an import but its group is not specified in the 039 * configuration such an import should be placed at the end of the import list. 040 * </p> 041 * The rule consists of: 042 * 043 * <p> 044 * 1. STATIC group. This group sets the ordering of static imports. 045 * </p> 046 * 047 * <p> 048 * 2. SAME_PACKAGE(n) group. This group sets the ordering of the same package imports. 049 * Imports are considered on SAME_PACKAGE group if <b>n</b> first domains in package name 050 * and import name are identical. 051 * </p> 052 * 053 * <pre> 054 *{@code 055 *package java.util.concurrent.locks; 056 * 057 *import java.io.File; 058 *import java.util.*; //#1 059 *import java.util.List; //#2 060 *import java.util.StringTokenizer; //#3 061 *import java.util.concurrent.*; //#4 062 *import java.util.concurrent.AbstractExecutorService; //#5 063 *import java.util.concurrent.locks.LockSupport; //#6 064 *import java.util.regex.Pattern; //#7 065 *import java.util.regex.Matcher; //#8 066 *} 067 * </pre> 068 * 069 * <p> 070 * If we have SAME_PACKAGE(3) on configuration file, 071 * imports #4-6 will be considered as a SAME_PACKAGE group (java.util.concurrent.*, 072 * java.util.concurrent.AbstractExecutorService, java.util.concurrent.locks.LockSupport). 073 * SAME_PACKAGE(2) will include #1-8. SAME_PACKAGE(4) will include only #6. 074 * SAME_PACKAGE(5) will result in no imports assigned to SAME_PACKAGE group because 075 * actual package java.util.concurrent.locks has only 4 domains. 076 * </p> 077 * 078 * <p> 079 * 3. THIRD_PARTY_PACKAGE group. This group sets ordering of third party imports. 080 * Third party imports are all imports except STATIC, 081 * SAME_PACKAGE(n), STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS. 082 * </p> 083 * 084 * <p> 085 * 4. STANDARD_JAVA_PACKAGE group. By default this group sets ordering of standard java/javax 086 * imports. 087 * </p> 088 * 089 * <p> 090 * 5. SPECIAL_IMPORTS group. This group may contains some imports 091 * that have particular meaning for the user. 092 * </p> 093 * 094 * <p> 095 * NOTE! 096 * </p> 097 * <p> 098 * Use the separator '###' between rules. 099 * </p> 100 * <p> 101 * To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use 102 * thirdPartyPackageRegExp and standardPackageRegExp options. 103 * </p> 104 * <p> 105 * Pretty often one import can match more than one group. For example, static import from standard 106 * package or regular expressions are configured to allow one import match multiple groups. 107 * In this case, group will be assigned according to priorities: 108 * </p> 109 * <ol> 110 * <li> 111 * STATIC has top priority 112 * </li> 113 * <li> 114 * SAME_PACKAGE has second priority 115 * </li> 116 * <li> 117 * STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS will compete using "best match" rule: longer 118 * matching substring wins; in case of the same length, lower position of matching substring 119 * wins; if position is the same, order of rules in configuration solves the puzzle. 120 * </li> 121 * <li> 122 * THIRD_PARTY has the least priority 123 * </li> 124 * </ol> 125 * <p> 126 * Few examples to illustrate "best match": 127 * </p> 128 * <p> 129 * 1. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="ImportOrderCheck" and input 130 * file: 131 * </p> 132 * <pre> 133 *{@code 134 *import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; 135 *import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck;} 136 * </pre> 137 * <p> 138 * Result: imports will be assigned to SPECIAL_IMPORTS, because matching substring length is 16. 139 * Matching substring for STANDARD_JAVA_PACKAGE is 5. 140 * </p> 141 * <p> 142 * 2. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="Avoid" and file: 143 * </p> 144 * <pre> 145 *{@code 146 *import com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck;} 147 * </pre> 148 * <p> 149 * Result: import will be assigned to SPECIAL_IMPORTS. Matching substring length is 5 for both 150 * patterns. However, "Avoid" position is lower then "Check" position. 151 * </p> 152 * 153 * <pre> 154 * Properties: 155 * </pre> 156 * <table summary="Properties" border="1"> 157 * <tr><th>name</th><th>Description</th><th>type</th><th>default value</th></tr> 158 * <tr><td>customImportOrderRules</td><td>List of order declaration customizing by user.</td> 159 * <td>string</td><td>null</td></tr> 160 * <tr><td>standardPackageRegExp</td><td>RegExp for STANDARD_JAVA_PACKAGE group imports.</td> 161 * <td>regular expression</td><td>^(java|javax)\.</td></tr> 162 * <tr><td>thirdPartyPackageRegExp</td><td>RegExp for THIRD_PARTY_PACKAGE group imports.</td> 163 * <td>regular expression</td><td>.*</td></tr> 164 * <tr><td>specialImportsRegExp</td><td>RegExp for SPECIAL_IMPORTS group imports.</td> 165 * <td>regular expression</td><td>^$</td></tr> 166 * <tr><td>separateLineBetweenGroups</td><td>Force empty line separator between import groups. 167 * </td><td>boolean</td><td>true</td></tr> 168 * <tr><td>sortImportsInGroupAlphabetically</td><td>Force grouping alphabetically, 169 * in ASCII sort order.</td><td>boolean</td><td>false</td></tr> 170 * </table> 171 * 172 * <p> 173 * For example: 174 * </p> 175 * <p>To configure the check so that it matches default Eclipse formatter configuration 176 * (tested on Kepler, Luna and Mars):</p> 177 * <ul> 178 * <li>group of static imports is on the top</li> 179 * <li>groups of non-static imports: "java" and "javax" packages 180 * first, then "org" and then all other imports</li> 181 * <li>imports will be sorted in the groups</li> 182 * <li>groups are separated by, at least, one blank line</li> 183 * </ul> 184 * <pre> 185 * <module name="CustomImportOrder"> 186 * <property name="customImportOrderRules" 187 * value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS"/> 188 * <property name="specialImportsRegExp" value="org"/> 189 * <property name="sortImportsInGroupAlphabetically" value="true"/> 190 * <property name="separateLineBetweenGroups" value="true"/> 191 * </module> 192 * </pre> 193 * 194 * <p>To configure the check so that it matches default IntelliJ IDEA formatter 195 * configuration (tested on v14):</p> 196 * <ul> 197 * <li>group of static imports is on the bottom</li> 198 * <li>groups of non-static imports: all imports except of "javax" 199 * and "java", then "javax" and "java"</li> 200 * <li>imports will be sorted in the groups</li> 201 * <li>groups are separated by, at least, one blank line</li> 202 * </ul> 203 * 204 * <p> 205 * Note: "separated" option is disabled because IDEA default has blank line 206 * between "java" and static imports, and no blank line between 207 * "javax" and "java" 208 * </p> 209 * 210 * <pre> 211 * <module name="CustomImportOrder"> 212 * <property name="customImportOrderRules" 213 * value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE 214 * ###STATIC"/> 215 * <property name="specialImportsRegExp" value="^javax\."/> 216 * <property name="standardPackageRegExp" value="^java\."/> 217 * <property name="sortImportsInGroupAlphabetically" value="true"/> 218 * <property name="separateLineBetweenGroups" value="false"/> 219 *</module> 220 * </pre> 221 * 222 * <p>To configure the check so that it matches default NetBeans formatter 223 * configuration (tested on v8):</p> 224 * <ul> 225 * <li>groups of non-static imports are not defined, all imports will be sorted as a one 226 * group</li> 227 * <li>static imports are not separated, they will be sorted along with other imports</li> 228 * </ul> 229 * 230 * <pre> 231 *<module name="CustomImportOrder"/> 232 * </pre> 233 * <p>To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use 234 * thirdPartyPackageRegExp and standardPackageRegExp options.</p> 235 * <pre> 236 * <module name="CustomImportOrder"> 237 * <property name="customImportOrderRules" 238 * value="STATIC###SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE"/> 239 * <property name="thirdPartyPackageRegExp" value="com|org"/> 240 * <property name="standardPackageRegExp" value="^(java|javax)\."/> 241 * </module> 242 * </pre> 243 * <p> 244 * Also, this check can be configured to force empty line separator between 245 * import groups. For example 246 * </p> 247 * 248 * <pre> 249 * <module name="CustomImportOrder"> 250 * <property name="separateLineBetweenGroups" value="true"/> 251 * </module> 252 * </pre> 253 * <p> 254 * It is possible to enforce 255 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a> 256 * of imports in groups using the following configuration: 257 * </p> 258 * <pre> 259 * <module name="CustomImportOrder"> 260 * <property name="sortImportsInGroupAlphabetically" value="true"/> 261 * </module> 262 * </pre> 263 * <p> 264 * Example of ASCII order: 265 * </p> 266 * <pre> 267 * {@code 268 *import java.awt.Dialog; 269 *import java.awt.Window; 270 *import java.awt.color.ColorSpace; 271 *import java.awt.Frame; // violation here - in ASCII order 'F' should go before 'c', 272 * // as all uppercase come before lowercase letters} 273 * </pre> 274 * <p> 275 * To force checking imports sequence such as: 276 * </p> 277 * 278 * <pre> 279 * {@code 280 * package com.puppycrawl.tools.checkstyle.imports; 281 * 282 * import com.google.common.annotations.GwtCompatible; 283 * import com.google.common.annotations.Beta; 284 * import com.google.common.annotations.VisibleForTesting; 285 * 286 * import org.abego.treelayout.Configuration; 287 * 288 * import static sun.tools.util.ModifierFilter.ALL_ACCESS; 289 * 290 * import com.google.common.annotations.GwtCompatible; // violation here - should be in the 291 * // THIRD_PARTY_PACKAGE group 292 * import android.*;} 293 * </pre> 294 * configure as follows: 295 * <pre> 296 * <module name="CustomImportOrder"> 297 * <property name="customImportOrderRules" 298 * value="SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STATIC###SPECIAL_IMPORTS"/> 299 * <property name="specialImportsRegExp" value="android.*"/> 300 * </module> 301 * </pre> 302 * 303 * @author maxvetrenko 304 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 305 */ 306@FileStatefulCheck 307public class CustomImportOrderCheck extends AbstractCheck { 308 309 /** 310 * A key is pointing to the warning message text in "messages.properties" 311 * file. 312 */ 313 public static final String MSG_LINE_SEPARATOR = "custom.import.order.line.separator"; 314 315 /** 316 * A key is pointing to the warning message text in "messages.properties" 317 * file. 318 */ 319 public static final String MSG_LEX = "custom.import.order.lex"; 320 321 /** 322 * A key is pointing to the warning message text in "messages.properties" 323 * file. 324 */ 325 public static final String MSG_NONGROUP_IMPORT = "custom.import.order.nonGroup.import"; 326 327 /** 328 * A key is pointing to the warning message text in "messages.properties" 329 * file. 330 */ 331 public static final String MSG_NONGROUP_EXPECTED = "custom.import.order.nonGroup.expected"; 332 333 /** 334 * A key is pointing to the warning message text in "messages.properties" 335 * file. 336 */ 337 public static final String MSG_ORDER = "custom.import.order"; 338 339 /** STATIC group name. */ 340 public static final String STATIC_RULE_GROUP = "STATIC"; 341 342 /** SAME_PACKAGE group name. */ 343 public static final String SAME_PACKAGE_RULE_GROUP = "SAME_PACKAGE"; 344 345 /** THIRD_PARTY_PACKAGE group name. */ 346 public static final String THIRD_PARTY_PACKAGE_RULE_GROUP = "THIRD_PARTY_PACKAGE"; 347 348 /** STANDARD_JAVA_PACKAGE group name. */ 349 public static final String STANDARD_JAVA_PACKAGE_RULE_GROUP = "STANDARD_JAVA_PACKAGE"; 350 351 /** SPECIAL_IMPORTS group name. */ 352 public static final String SPECIAL_IMPORTS_RULE_GROUP = "SPECIAL_IMPORTS"; 353 354 /** NON_GROUP group name. */ 355 private static final String NON_GROUP_RULE_GROUP = "NOT_ASSIGNED_TO_ANY_GROUP"; 356 357 /** Pattern used to separate groups of imports. */ 358 private static final Pattern GROUP_SEPARATOR_PATTERN = Pattern.compile("\\s*###\\s*"); 359 360 /** List of order declaration customizing by user. */ 361 private final List<String> customImportOrderRules = new ArrayList<>(); 362 363 /** Contains objects with import attributes. */ 364 private final List<ImportDetails> importToGroupList = new ArrayList<>(); 365 366 /** RegExp for SAME_PACKAGE group imports. */ 367 private String samePackageDomainsRegExp = ""; 368 369 /** RegExp for STANDARD_JAVA_PACKAGE group imports. */ 370 private Pattern standardPackageRegExp = Pattern.compile("^(java|javax)\\."); 371 372 /** RegExp for THIRD_PARTY_PACKAGE group imports. */ 373 private Pattern thirdPartyPackageRegExp = Pattern.compile(".*"); 374 375 /** RegExp for SPECIAL_IMPORTS group imports. */ 376 private Pattern specialImportsRegExp = Pattern.compile("^$"); 377 378 /** Force empty line separator between import groups. */ 379 private boolean separateLineBetweenGroups = true; 380 381 /** Force grouping alphabetically, in ASCII order. */ 382 private boolean sortImportsInGroupAlphabetically; 383 384 /** Number of first domains for SAME_PACKAGE group. */ 385 private int samePackageMatchingDepth = 2; 386 387 /** 388 * Sets standardRegExp specified by user. 389 * @param regexp 390 * user value. 391 */ 392 public final void setStandardPackageRegExp(Pattern regexp) { 393 standardPackageRegExp = regexp; 394 } 395 396 /** 397 * Sets thirdPartyRegExp specified by user. 398 * @param regexp 399 * user value. 400 */ 401 public final void setThirdPartyPackageRegExp(Pattern regexp) { 402 thirdPartyPackageRegExp = regexp; 403 } 404 405 /** 406 * Sets specialImportsRegExp specified by user. 407 * @param regexp 408 * user value. 409 */ 410 public final void setSpecialImportsRegExp(Pattern regexp) { 411 specialImportsRegExp = regexp; 412 } 413 414 /** 415 * Sets separateLineBetweenGroups specified by user. 416 * @param value 417 * user value. 418 */ 419 public final void setSeparateLineBetweenGroups(boolean value) { 420 separateLineBetweenGroups = value; 421 } 422 423 /** 424 * Sets sortImportsInGroupAlphabetically specified by user. 425 * @param value 426 * user value. 427 */ 428 public final void setSortImportsInGroupAlphabetically(boolean value) { 429 sortImportsInGroupAlphabetically = value; 430 } 431 432 /** 433 * Sets a custom import order from the rules in the string format specified 434 * by user. 435 * @param inputCustomImportOrder 436 * user value. 437 */ 438 public final void setCustomImportOrderRules(final String inputCustomImportOrder) { 439 for (String currentState : GROUP_SEPARATOR_PATTERN.split(inputCustomImportOrder)) { 440 addRulesToList(currentState); 441 } 442 customImportOrderRules.add(NON_GROUP_RULE_GROUP); 443 } 444 445 @Override 446 public int[] getDefaultTokens() { 447 return getRequiredTokens(); 448 } 449 450 @Override 451 public int[] getAcceptableTokens() { 452 return getRequiredTokens(); 453 } 454 455 @Override 456 public int[] getRequiredTokens() { 457 return new int[] { 458 TokenTypes.IMPORT, 459 TokenTypes.STATIC_IMPORT, 460 TokenTypes.PACKAGE_DEF, 461 }; 462 } 463 464 @Override 465 public void beginTree(DetailAST rootAST) { 466 importToGroupList.clear(); 467 } 468 469 @Override 470 public void visitToken(DetailAST ast) { 471 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 472 if (customImportOrderRules.contains(SAME_PACKAGE_RULE_GROUP)) { 473 samePackageDomainsRegExp = createSamePackageRegexp( 474 samePackageMatchingDepth, ast); 475 } 476 } 477 else { 478 final String importFullPath = getFullImportIdent(ast); 479 final int lineNo = ast.getLineNo(); 480 final boolean isStatic = ast.getType() == TokenTypes.STATIC_IMPORT; 481 importToGroupList.add(new ImportDetails(importFullPath, 482 lineNo, getImportGroup(isStatic, importFullPath), 483 isStatic)); 484 } 485 } 486 487 @Override 488 public void finishTree(DetailAST rootAST) { 489 if (!importToGroupList.isEmpty()) { 490 finishImportList(); 491 } 492 } 493 494 /** Examine the order of all the imports and log any violations. */ 495 private void finishImportList() { 496 final ImportDetails firstImport = importToGroupList.get(0); 497 String currentGroup = getImportGroup(firstImport.isStaticImport(), 498 firstImport.getImportFullPath()); 499 int currentGroupNumber = customImportOrderRules.indexOf(currentGroup); 500 String previousImportFromCurrentGroup = null; 501 502 for (ImportDetails importObject : importToGroupList) { 503 final String importGroup = importObject.getImportGroup(); 504 final String fullImportIdent = importObject.getImportFullPath(); 505 506 if (getCountOfEmptyLinesBefore(importObject.getLineNumber()) > 1) { 507 log(importObject.getLineNumber(), MSG_LINE_SEPARATOR, fullImportIdent); 508 } 509 if (importGroup.equals(currentGroup)) { 510 if (sortImportsInGroupAlphabetically 511 && previousImportFromCurrentGroup != null 512 && compareImports(fullImportIdent, previousImportFromCurrentGroup) < 0) { 513 log(importObject.getLineNumber(), MSG_LEX, 514 fullImportIdent, previousImportFromCurrentGroup); 515 } 516 else { 517 previousImportFromCurrentGroup = fullImportIdent; 518 } 519 } 520 else { 521 //not the last group, last one is always NON_GROUP 522 if (customImportOrderRules.size() > currentGroupNumber + 1) { 523 final String nextGroup = getNextImportGroup(currentGroupNumber + 1); 524 if (importGroup.equals(nextGroup)) { 525 if (separateLineBetweenGroups 526 && getCountOfEmptyLinesBefore(importObject.getLineNumber()) == 0) { 527 log(importObject.getLineNumber(), MSG_LINE_SEPARATOR, fullImportIdent); 528 } 529 currentGroup = nextGroup; 530 currentGroupNumber = customImportOrderRules.indexOf(nextGroup); 531 previousImportFromCurrentGroup = fullImportIdent; 532 } 533 else { 534 logWrongImportGroupOrder(importObject.getLineNumber(), 535 importGroup, nextGroup, fullImportIdent); 536 } 537 } 538 else { 539 logWrongImportGroupOrder(importObject.getLineNumber(), 540 importGroup, currentGroup, fullImportIdent); 541 } 542 } 543 } 544 } 545 546 /** 547 * Log wrong import group order. 548 * @param currentImportLine 549 * line number of current import current import. 550 * @param importGroup 551 * import group. 552 * @param currentGroupNumber 553 * current group number we are checking. 554 * @param fullImportIdent 555 * full import name. 556 */ 557 private void logWrongImportGroupOrder(int currentImportLine, String importGroup, 558 String currentGroupNumber, String fullImportIdent) { 559 if (NON_GROUP_RULE_GROUP.equals(importGroup)) { 560 log(currentImportLine, MSG_NONGROUP_IMPORT, fullImportIdent); 561 } 562 else if (NON_GROUP_RULE_GROUP.equals(currentGroupNumber)) { 563 log(currentImportLine, MSG_NONGROUP_EXPECTED, importGroup, fullImportIdent); 564 } 565 else { 566 log(currentImportLine, MSG_ORDER, importGroup, currentGroupNumber, fullImportIdent); 567 } 568 } 569 570 /** 571 * Get next import group. 572 * @param currentGroupNumber 573 * current group number. 574 * @return 575 * next import group. 576 */ 577 private String getNextImportGroup(int currentGroupNumber) { 578 int nextGroupNumber = currentGroupNumber; 579 580 while (customImportOrderRules.size() > nextGroupNumber + 1) { 581 if (hasAnyImportInCurrentGroup(customImportOrderRules.get(nextGroupNumber))) { 582 break; 583 } 584 nextGroupNumber++; 585 } 586 return customImportOrderRules.get(nextGroupNumber); 587 } 588 589 /** 590 * Checks if current group contains any import. 591 * @param currentGroup 592 * current group. 593 * @return 594 * true, if current group contains at least one import. 595 */ 596 private boolean hasAnyImportInCurrentGroup(String currentGroup) { 597 boolean result = false; 598 for (ImportDetails currentImport : importToGroupList) { 599 if (currentGroup.equals(currentImport.getImportGroup())) { 600 result = true; 601 break; 602 } 603 } 604 return result; 605 } 606 607 /** 608 * Get import valid group. 609 * @param isStatic 610 * is static import. 611 * @param importPath 612 * full import path. 613 * @return import valid group. 614 */ 615 private String getImportGroup(boolean isStatic, String importPath) { 616 RuleMatchForImport bestMatch = new RuleMatchForImport(NON_GROUP_RULE_GROUP, 0, 0); 617 if (isStatic && customImportOrderRules.contains(STATIC_RULE_GROUP)) { 618 bestMatch.group = STATIC_RULE_GROUP; 619 bestMatch.matchLength = importPath.length(); 620 } 621 else if (customImportOrderRules.contains(SAME_PACKAGE_RULE_GROUP)) { 622 final String importPathTrimmedToSamePackageDepth = 623 getFirstDomainsFromIdent(samePackageMatchingDepth, importPath); 624 if (samePackageDomainsRegExp.equals(importPathTrimmedToSamePackageDepth)) { 625 bestMatch.group = SAME_PACKAGE_RULE_GROUP; 626 bestMatch.matchLength = importPath.length(); 627 } 628 } 629 if (bestMatch.group.equals(NON_GROUP_RULE_GROUP)) { 630 for (String group : customImportOrderRules) { 631 if (STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(group)) { 632 bestMatch = findBetterPatternMatch(importPath, 633 STANDARD_JAVA_PACKAGE_RULE_GROUP, standardPackageRegExp, bestMatch); 634 } 635 if (SPECIAL_IMPORTS_RULE_GROUP.equals(group)) { 636 bestMatch = findBetterPatternMatch(importPath, 637 group, specialImportsRegExp, bestMatch); 638 } 639 } 640 } 641 if (bestMatch.group.equals(NON_GROUP_RULE_GROUP) 642 && customImportOrderRules.contains(THIRD_PARTY_PACKAGE_RULE_GROUP) 643 && thirdPartyPackageRegExp.matcher(importPath).find()) { 644 bestMatch.group = THIRD_PARTY_PACKAGE_RULE_GROUP; 645 } 646 return bestMatch.group; 647 } 648 649 /** Tries to find better matching regular expression: 650 * longer matching substring wins; in case of the same length, 651 * lower position of matching substring wins. 652 * @param importPath 653 * Full import identifier 654 * @param group 655 * Import group we are trying to assign the import 656 * @param regExp 657 * Regular expression for import group 658 * @param currentBestMatch 659 * object with currently best match 660 * @return better match (if found) or the same (currentBestMatch) 661 */ 662 private static RuleMatchForImport findBetterPatternMatch(String importPath, String group, 663 Pattern regExp, RuleMatchForImport currentBestMatch) { 664 RuleMatchForImport betterMatchCandidate = currentBestMatch; 665 final Matcher matcher = regExp.matcher(importPath); 666 while (matcher.find()) { 667 final int length = matcher.end() - matcher.start(); 668 if (length > betterMatchCandidate.matchLength 669 || length == betterMatchCandidate.matchLength 670 && matcher.start() < betterMatchCandidate.matchPosition) { 671 betterMatchCandidate = new RuleMatchForImport(group, length, matcher.start()); 672 } 673 } 674 return betterMatchCandidate; 675 } 676 677 /** 678 * Checks compare two import paths. 679 * @param import1 680 * current import. 681 * @param import2 682 * previous import. 683 * @return a negative integer, zero, or a positive integer as the 684 * specified String is greater than, equal to, or less 685 * than this String, ignoring case considerations. 686 */ 687 private static int compareImports(String import1, String import2) { 688 int result = 0; 689 final String separator = "\\."; 690 final String[] import1Tokens = import1.split(separator); 691 final String[] import2Tokens = import2.split(separator); 692 for (int i = 0; i != import1Tokens.length && i != import2Tokens.length; i++) { 693 final String import1Token = import1Tokens[i]; 694 final String import2Token = import2Tokens[i]; 695 result = import1Token.compareTo(import2Token); 696 if (result != 0) { 697 break; 698 } 699 } 700 if (result == 0) { 701 result = Integer.compare(import1Tokens.length, import2Tokens.length); 702 } 703 return result; 704 } 705 706 /** 707 * Counts empty lines before given. 708 * @param lineNo 709 * Line number of current import. 710 * @return count of empty lines before given. 711 */ 712 private int getCountOfEmptyLinesBefore(int lineNo) { 713 int result = 0; 714 final String[] lines = getLines(); 715 // [lineNo - 2] is the number of the previous line 716 // because the numbering starts from zero. 717 int lineBeforeIndex = lineNo - 2; 718 while (lineBeforeIndex >= 0 719 && CommonUtils.isBlank(lines[lineBeforeIndex])) { 720 lineBeforeIndex--; 721 result++; 722 } 723 return result; 724 } 725 726 /** 727 * Forms import full path. 728 * @param token 729 * current token. 730 * @return full path or null. 731 */ 732 private static String getFullImportIdent(DetailAST token) { 733 String ident = ""; 734 if (token != null) { 735 ident = FullIdent.createFullIdent(token.findFirstToken(TokenTypes.DOT)).getText(); 736 } 737 return ident; 738 } 739 740 /** 741 * Parses ordering rule and adds it to the list with rules. 742 * @param ruleStr 743 * String with rule. 744 */ 745 private void addRulesToList(String ruleStr) { 746 if (STATIC_RULE_GROUP.equals(ruleStr) 747 || THIRD_PARTY_PACKAGE_RULE_GROUP.equals(ruleStr) 748 || STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(ruleStr) 749 || SPECIAL_IMPORTS_RULE_GROUP.equals(ruleStr)) { 750 customImportOrderRules.add(ruleStr); 751 } 752 else if (ruleStr.startsWith(SAME_PACKAGE_RULE_GROUP)) { 753 final String rule = ruleStr.substring(ruleStr.indexOf('(') + 1, 754 ruleStr.indexOf(')')); 755 samePackageMatchingDepth = Integer.parseInt(rule); 756 if (samePackageMatchingDepth <= 0) { 757 throw new IllegalArgumentException( 758 "SAME_PACKAGE rule parameter should be positive integer: " + ruleStr); 759 } 760 customImportOrderRules.add(SAME_PACKAGE_RULE_GROUP); 761 } 762 else { 763 throw new IllegalStateException("Unexpected rule: " + ruleStr); 764 } 765 } 766 767 /** 768 * Creates samePackageDomainsRegExp of the first package domains. 769 * @param firstPackageDomainsCount 770 * number of first package domains. 771 * @param packageNode 772 * package node. 773 * @return same package regexp. 774 */ 775 private static String createSamePackageRegexp(int firstPackageDomainsCount, 776 DetailAST packageNode) { 777 final String packageFullPath = getFullImportIdent(packageNode); 778 return getFirstDomainsFromIdent(firstPackageDomainsCount, packageFullPath); 779 } 780 781 /** 782 * Extracts defined amount of domains from the left side of package/import identifier. 783 * @param firstPackageDomainsCount 784 * number of first package domains. 785 * @param packageFullPath 786 * full identifier containing path to package or imported object. 787 * @return String with defined amount of domains or full identifier 788 * (if full identifier had less domain then specified) 789 */ 790 private static String getFirstDomainsFromIdent( 791 final int firstPackageDomainsCount, final String packageFullPath) { 792 final StringBuilder builder = new StringBuilder(256); 793 final StringTokenizer tokens = new StringTokenizer(packageFullPath, "."); 794 int count = firstPackageDomainsCount; 795 796 while (count > 0 && tokens.hasMoreTokens()) { 797 builder.append(tokens.nextToken()).append('.'); 798 count--; 799 } 800 return builder.toString(); 801 } 802 803 /** 804 * Contains import attributes as line number, import full path, import 805 * group. 806 * @author max 807 */ 808 private static class ImportDetails { 809 810 /** Import full path. */ 811 private final String importFullPath; 812 813 /** Import line number. */ 814 private final int lineNumber; 815 816 /** Import group. */ 817 private final String importGroup; 818 819 /** Is static import. */ 820 private final boolean staticImport; 821 822 /** 823 * Initialise importFullPath, lineNumber, importGroup, staticImport. 824 * @param importFullPath 825 * import full path. 826 * @param lineNumber 827 * import line number. 828 * @param importGroup 829 * import group. 830 * @param staticImport 831 * if import is static. 832 */ 833 ImportDetails(String importFullPath, 834 int lineNumber, String importGroup, boolean staticImport) { 835 this.importFullPath = importFullPath; 836 this.lineNumber = lineNumber; 837 this.importGroup = importGroup; 838 this.staticImport = staticImport; 839 } 840 841 /** 842 * Get import full path variable. 843 * @return import full path variable. 844 */ 845 public String getImportFullPath() { 846 return importFullPath; 847 } 848 849 /** 850 * Get import line number. 851 * @return import line. 852 */ 853 public int getLineNumber() { 854 return lineNumber; 855 } 856 857 /** 858 * Get import group. 859 * @return import group. 860 */ 861 public String getImportGroup() { 862 return importGroup; 863 } 864 865 /** 866 * Checks if import is static. 867 * @return true, if import is static. 868 */ 869 public boolean isStaticImport() { 870 return staticImport; 871 } 872 873 } 874 875 /** 876 * Contains matching attributes assisting in definition of "best matching" 877 * group for import. 878 * @author ivanov-alex 879 */ 880 private static class RuleMatchForImport { 881 882 /** Position of matching string for current best match. */ 883 private final int matchPosition; 884 /** Length of matching string for current best match. */ 885 private int matchLength; 886 /** Import group for current best match. */ 887 private String group; 888 889 /** Constructor to initialize the fields. 890 * @param group 891 * Matched group. 892 * @param length 893 * Matching length. 894 * @param position 895 * Matching position. 896 */ 897 RuleMatchForImport(String group, int length, int position) { 898 this.group = group; 899 matchLength = length; 900 matchPosition = position; 901 } 902 903 } 904 905}