001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.CommonUtil; 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 * <p> 042 * The rule consists of: 043 * </p> 044 * <ol> 045 * <li> 046 * STATIC group. This group sets the ordering of static imports. 047 * </li> 048 * <li> 049 * SAME_PACKAGE(n) group. This group sets the ordering of the same package imports. 050 * Imports are considered on SAME_PACKAGE group if <b>n</b> first domains in package 051 * name and import name are identical: 052 * <pre> 053 * package java.util.concurrent.locks; 054 * 055 * import java.io.File; 056 * import java.util.*; //#1 057 * import java.util.List; //#2 058 * import java.util.StringTokenizer; //#3 059 * import java.util.concurrent.*; //#4 060 * import java.util.concurrent.AbstractExecutorService; //#5 061 * import java.util.concurrent.locks.LockSupport; //#6 062 * import java.util.regex.Pattern; //#7 063 * import java.util.regex.Matcher; //#8 064 * </pre> 065 * If we have SAME_PACKAGE(3) on configuration file, imports #4-6 will be considered as 066 * a SAME_PACKAGE group (java.util.concurrent.*, java.util.concurrent.AbstractExecutorService, 067 * java.util.concurrent.locks.LockSupport). SAME_PACKAGE(2) will include #1-8. 068 * SAME_PACKAGE(4) will include only #6. SAME_PACKAGE(5) will result in no imports assigned 069 * to SAME_PACKAGE group because actual package java.util.concurrent.locks has only 4 domains. 070 * </li> 071 * <li> 072 * THIRD_PARTY_PACKAGE group. This group sets ordering of third party imports. 073 * Third party imports are all imports except STATIC, SAME_PACKAGE(n), STANDARD_JAVA_PACKAGE and 074 * SPECIAL_IMPORTS. 075 * </li> 076 * <li> 077 * STANDARD_JAVA_PACKAGE group. By default, this group sets ordering of standard java/javax imports. 078 * </li> 079 * <li> 080 * SPECIAL_IMPORTS group. This group may contain some imports that have particular meaning for the 081 * user. 082 * </li> 083 * </ol> 084 * <p> 085 * Use the separator '###' between rules. 086 * </p> 087 * <p> 088 * To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use 089 * thirdPartyPackageRegExp and standardPackageRegExp options. 090 * </p> 091 * <p> 092 * Pretty often one import can match more than one group. For example, static import from standard 093 * package or regular expressions are configured to allow one import match multiple groups. 094 * In this case, group will be assigned according to priorities: 095 * </p> 096 * <ol> 097 * <li> 098 * STATIC has top priority 099 * </li> 100 * <li> 101 * SAME_PACKAGE has second priority 102 * </li> 103 * <li> 104 * STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS will compete using "best match" rule: longer 105 * matching substring wins; in case of the same length, lower position of matching substring 106 * wins; if position is the same, order of rules in configuration solves the puzzle. 107 * </li> 108 * <li> 109 * THIRD_PARTY has the least priority 110 * </li> 111 * </ol> 112 * <p> 113 * Few examples to illustrate "best match": 114 * </p> 115 * <p> 116 * 1. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="ImportOrderCheck" and input file: 117 * </p> 118 * <pre> 119 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; 120 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; 121 * </pre> 122 * <p> 123 * Result: imports will be assigned to SPECIAL_IMPORTS, because matching substring length is 16. 124 * Matching substring for STANDARD_JAVA_PACKAGE is 5. 125 * </p> 126 * <p> 127 * 2. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="Avoid" and file: 128 * </p> 129 * <pre> 130 * import com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck; 131 * </pre> 132 * <p> 133 * Result: import will be assigned to SPECIAL_IMPORTS. Matching substring length is 5 for both 134 * patterns. However, "Avoid" position is lower than "Check" position. 135 * </p> 136 * <ul> 137 * <li> 138 * Property {@code customImportOrderRules} - Specify format of order declaration 139 * customizing by user. 140 * Type is {@code java.lang.String}. 141 * Default value is {@code ""}. 142 * </li> 143 * <li> 144 * Property {@code standardPackageRegExp} - Specify RegExp for STANDARD_JAVA_PACKAGE group imports. 145 * Type is {@code java.util.regex.Pattern}. 146 * Default value is {@code "^(java|javax)\."}. 147 * </li> 148 * <li> 149 * Property {@code thirdPartyPackageRegExp} - Specify RegExp for THIRD_PARTY_PACKAGE group imports. 150 * Type is {@code java.util.regex.Pattern}. 151 * Default value is {@code ".*"}. 152 * </li> 153 * <li> 154 * Property {@code specialImportsRegExp} - Specify RegExp for SPECIAL_IMPORTS group imports. 155 * Type is {@code java.util.regex.Pattern}. 156 * Default value is {@code "^$"}. 157 * </li> 158 * <li> 159 * Property {@code separateLineBetweenGroups} - Force empty line separator between 160 * import groups. 161 * Type is {@code boolean}. 162 * Default value is {@code true}. 163 * </li> 164 * <li> 165 * Property {@code sortImportsInGroupAlphabetically} - Force grouping alphabetically, 166 * in <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 167 * Type is {@code boolean}. 168 * Default value is {@code false}. 169 * </li> 170 * </ul> 171 * <p> 172 * To configure the check : 173 * </p> 174 * <pre> 175 * <module name="CustomImportOrder"/> 176 * </pre> 177 * <p> 178 * Example: 179 * </p> 180 * <pre> 181 * package com.company; 182 * import org.apache.commons.io.FileUtils; // OK 183 * import static java.util.*; // OK 184 * import java.time.*; // OK 185 * import static java.io.*; // OK 186 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 187 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 188 * </pre> 189 * <p> 190 * To configure the check so that it checks in the order 191 * (static imports,standard java packages,third party package): 192 * </p> 193 * <pre> 194 * <module name="CustomImportOrder"> 195 * <property name="customImportOrderRules" 196 * value="STATIC###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE"/> 197 * </module> 198 * </pre> 199 * <p> 200 * Example: 201 * </p> 202 * <pre> 203 * package com.company; 204 * 205 * import static java.util.*; // OK 206 * 207 * import java.time.*; // OK 208 * import javax.net.*; // OK 209 * import static java.io.*; // violation as static imports should be in top 210 * 211 * import org.apache.commons.io.FileUtils; // OK 212 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 213 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 214 * </pre> 215 * <p> 216 * To configure the check such that only java packages are included in standard java packages 217 * </p> 218 * <pre> 219 * <module name="CustomImportOrder"> 220 * <property name="customImportOrderRules" 221 * value="STATIC###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE"/> 222 * <property name="standardPackageRegExp" value="^java\."/> 223 * </module> 224 * </pre> 225 * <p> 226 * Example: 227 * </p> 228 * <pre> 229 * package com.company; 230 * 231 * import static java.util.*; // OK 232 * import static java.io.*; // OK 233 * 234 * import java.time.*; // OK 235 * import javax.net.*; // violation as it is not included in standard java package group. 236 * 237 * import org.apache.commons.io.FileUtils; // violation 238 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 239 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 240 * </pre> 241 * <p> 242 * To configure the check to include only "com" packages as third party group imports: 243 * </p> 244 * <pre> 245 * <module name="CustomImportOrder"> 246 * <property name="customImportOrderRules" 247 * value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/> 248 * <property name="thirdPartyPackageRegExp" value="^com\."/> 249 * </module> 250 * </pre> 251 * <p> 252 * Example: 253 * </p> 254 * <pre> 255 * package com.company; 256 * 257 * import static java.util.*; // OK 258 * import static java.io.*; // OK 259 * 260 * import java.time.*; // OK 261 * import javax.net.*; // OK 262 * 263 * import org.apache.commons.io.FileUtils; // violation(should be in end) 264 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // violation 265 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 266 * </pre> 267 * <p> 268 * To configure the check to force some packages in special import group: 269 * </p> 270 * <pre> 271 * <module name="CustomImportOrder"> 272 * <property name="customImportOrderRules" 273 * value="STATIC###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE"/> 274 * <property name="specialImportsRegExp" value="^org\."/> 275 * </module> 276 * </pre> 277 * <p> 278 * Example: 279 * </p> 280 * <pre> 281 * package com.company; 282 * 283 * import static java.util.*; // OK 284 * import static java.io.*; // OK 285 * 286 * import org.json.JSONObject; // OK 287 * 288 * import java.time.*; // OK 289 * import javax.net.*; // OK 290 * 291 * import org.apache.commons.io.FileUtils; // violation 292 * </pre> 293 * <p> 294 * To configure the check such that empty line separator between two groups is enabled: 295 * </p> 296 * <pre> 297 * <module name="CustomImportOrder"> 298 * <property name="customImportOrderRules" 299 * value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/> 300 * <property name="specialImportsRegExp" value="^org\."/> 301 * <property name="thirdPartyPackageRegExp" value="^com\."/> 302 * <property name="separateLineBetweenGroups" value="true"/> 303 * </module> 304 * </pre> 305 * <p> 306 * Example: 307 * </p> 308 * <pre> 309 * package com.company; 310 * 311 * import static java.util.*; // OK 312 * import static java.io.*; // OK 313 * 314 * import java.time.*; // OK 315 * import javax.net.*; // OK 316 * import org.apache.commons.io.FileUtils; // violation 317 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // violation 318 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 319 * </pre> 320 * <p> 321 * To configure the check such that import groups are forced to be sorted alphabetically: 322 * </p> 323 * <pre> 324 * <module name="CustomImportOrder"> 325 * <property name="customImportOrderRules" 326 * value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/> 327 * <property name="specialImportsRegExp" value="^org\."/> 328 * <property name="thirdPartyPackageRegExp" value="^com\."/> 329 * <property name="separateLineBetweenGroups" value="false"/> 330 * <property name="sortImportsInGroupAlphabetically" value="true"/> 331 * </module> 332 * </pre> 333 * <p> 334 * Example: 335 * </p> 336 * <pre> 337 * package com.company; 338 * 339 * import static java.util.*; // OK 340 * import static java.io.*; // Violation since it should come before"java.util" 341 * 342 * import java.time.*; // OK 343 * import javax.net.*; // OK 344 * import org.apache.commons.io.FileUtils; // OK 345 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 346 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 347 * </pre> 348 * <p> 349 * To configure the check so that it matches default Eclipse formatter configuration 350 * (tested on Kepler and Luna releases): 351 * </p> 352 * <ul> 353 * <li> 354 * group of static imports is on the top 355 * </li> 356 * <li> 357 * groups of non-static imports: "java" and "javax" packages first, then "org" and then all other 358 * imports 359 * </li> 360 * <li> 361 * imports will be sorted in the groups 362 * </li> 363 * <li> 364 * groups are separated by single blank line 365 * </li> 366 * </ul> 367 * <p> 368 * Notes: 369 * </p> 370 * <ul> 371 * <li> 372 * "com" package is not mentioned on configuration, because it is ignored by Eclipse Kepler and Luna 373 * (looks like Eclipse defect) 374 * </li> 375 * <li> 376 * configuration below doesn't work in all 100% cases due to inconsistent behavior prior to Mars 377 * release, but covers most scenarios 378 * </li> 379 * </ul> 380 * <pre> 381 * <module name="CustomImportOrder"> 382 * <property name="customImportOrderRules" 383 * value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS"/> 384 * <property name="specialImportsRegExp" value="^org\."/> 385 * <property name="sortImportsInGroupAlphabetically" value="true"/> 386 * <property name="separateLineBetweenGroups" value="true"/> 387 * </module> 388 * </pre> 389 * <p> 390 * Example: 391 * </p> 392 * <pre> 393 * package com.company; 394 * 395 * import static java.util.*; // OK 396 * import static java.io.*; // Violation since it should come before"java.util" 397 * 398 * import java.time.*; // OK 399 * import javax.net.*; // OK 400 * import org.apache.commons.io.FileUtils; // Violation should be separated by space 401 * 402 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 403 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 404 * </pre> 405 * <p> 406 * To configure the check so that it matches default Eclipse formatter configuration 407 * (tested on Mars release): 408 * </p> 409 * <ul> 410 * <li> 411 * group of static imports is on the top 412 * </li> 413 * <li> 414 * groups of non-static imports: "java" and "javax" packages first, then "org" and "com", 415 * then all other imports as one group 416 * </li> 417 * <li> 418 * imports will be sorted in the groups 419 * </li> 420 * <li> 421 * groups are separated by one blank line 422 * </li> 423 * </ul> 424 * <pre> 425 * <module name="CustomImportOrder"> 426 * <property name="customImportOrderRules" 427 * value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/> 428 * <property name="specialImportsRegExp" value="^org\."/> 429 * <property name="thirdPartyPackageRegExp" value="^com\."/> 430 * <property name="sortImportsInGroupAlphabetically" value="true"/> 431 * <property name="separateLineBetweenGroups" value="true"/> 432 * </module> 433 * </pre> 434 * <p> 435 * Example: 436 * </p> 437 * <pre> 438 * package com.company; 439 * 440 * import static java.io.*; // OK 441 * import static java.util.*; // OK 442 * 443 * import java.time.*; // OK 444 * import javax.net.*; // OK 445 * 446 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // Violation 447 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // Violation 448 * 449 * import org.apache.commons.io.FileUtils; 450 * </pre> 451 * <p> 452 * To configure the check so that it matches default IntelliJ IDEA formatter configuration 453 * (tested on v14): 454 * </p> 455 * <ul> 456 * <li> 457 * group of static imports is on the bottom 458 * </li> 459 * <li> 460 * groups of non-static imports: all imports except of "javax" and "java", then "javax" and "java" 461 * </li> 462 * <li> 463 * imports will be sorted in the groups 464 * </li> 465 * <li> 466 * groups are separated by one blank line 467 * </li> 468 * </ul> 469 * <p> 470 * Note: "separated" option is disabled because IDEA default has blank line between "java" and 471 * static imports, and no blank line between "javax" and "java" 472 * </p> 473 * <pre> 474 * <module name="CustomImportOrder"> 475 * <property name="customImportOrderRules" 476 * value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> 477 * <property name="specialImportsRegExp" value="^javax\."/> 478 * <property name="standardPackageRegExp" value="^java\."/> 479 * <property name="sortImportsInGroupAlphabetically" value="true"/> 480 * <property name="separateLineBetweenGroups" value="false"/> 481 * </module> 482 * </pre> 483 * <p> 484 * Example: 485 * </p> 486 * <pre> 487 * package com.company; 488 * 489 * import static java.io.*; // OK 490 * import static java.util.*; // OK 491 * 492 * import java.time.*; // violation should be in standard package group 493 * // below special import 494 * 495 * import javax.net.*; // Violation should be in special import group 496 * 497 * import org.apache.commons.io.FileUtils; // Violation should be in 498 * // THIRD PARTY PACKAGE GROUP 499 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // Violation 500 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // Violation 501 * </pre> 502 * <p> 503 * To configure the check so that it matches default NetBeans formatter configuration 504 * (tested on v8): 505 * </p> 506 * <ul> 507 * <li> 508 * groups of non-static imports are not defined, all imports will be sorted as a one group 509 * </li> 510 * <li> 511 * static imports are not separated, they will be sorted along with other imports 512 * </li> 513 * </ul> 514 * <pre> 515 * <module name="CustomImportOrder"/> 516 * </pre> 517 * <p> 518 * Example: 519 * </p> 520 * <pre> 521 * package com.company; 522 * 523 * import static java.io.*; // OK 524 * import static java.util.*; // OK 525 * import java.time.*; // OK 526 * import javax.net.*; // OK 527 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 528 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 529 * 530 * import org.apache.commons.io.FileUtils; // should not be separated by line 531 * </pre> 532 * <p> 533 * To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use 534 * thirdPartyPackageRegExp and standardPackageRegExp options. 535 * </p> 536 * <pre> 537 * <module name="CustomImportOrder"> 538 * <property name="customImportOrderRules" 539 * value="STATIC###SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE"/> 540 * <property name="thirdPartyPackageRegExp" value="^(com|org)\."/> 541 * <property name="standardPackageRegExp" value="^(java|javax)\."/> 542 * </module> 543 * </pre> 544 * <p> 545 * Example: 546 * </p> 547 * <pre> 548 * package com.company; 549 * 550 * import static java.io.*; // OK 551 * import static java.util.*; // OK 552 * import java.time.*; // violation 553 * import javax.net.*; // violation 554 * 555 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 556 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 557 * import org.apache.commons.io.FileUtils; // OK 558 * </pre> 559 * <p> 560 * Also, this check can be configured to force empty line separator between 561 * import groups. For example. 562 * </p> 563 * <pre> 564 * <module name="CustomImportOrder"> 565 * <property name="separateLineBetweenGroups" value="true"/> 566 * </module> 567 * </pre> 568 * <p> 569 * Example: 570 * </p> 571 * <pre> 572 * package com.company; 573 * 574 * import static java.io.*; // OK 575 * import static java.util.*; // OK 576 * import java.time.*; // OK 577 * import javax.net.*; // OK 578 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK 579 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK 580 * import org.apache.commons.io.FileUtils; // OK 581 * </pre> 582 * <p> 583 * It is possible to enforce 584 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a> 585 * of imports in groups using the following configuration: 586 * </p> 587 * <pre> 588 * <module name="CustomImportOrder"> 589 * <property name="sortImportsInGroupAlphabetically" value="true"/> 590 * </module> 591 * </pre> 592 * <p> 593 * Example of ASCII order: 594 * </p> 595 * <pre> 596 * import java.awt.Dialog; 597 * import java.awt.Window; 598 * import java.awt.color.ColorSpace; 599 * import java.awt.Frame; // violation here - in ASCII order 'F' should go before 'c', 600 * // as all uppercase come before lowercase letters 601 * </pre> 602 * <p> 603 * To force checking imports sequence such as: 604 * </p> 605 * <pre> 606 * package com.puppycrawl.tools.checkstyle.imports; 607 * 608 * import com.google.common.annotations.GwtCompatible; 609 * import com.google.common.annotations.Beta; 610 * import com.google.common.annotations.VisibleForTesting; 611 * 612 * import org.abego.treelayout.Configuration; 613 * 614 * import static sun.tools.util.ModifierFilter.ALL_ACCESS; 615 * 616 * import com.google.common.annotations.GwtCompatible; // violation here - should be in the 617 * // THIRD_PARTY_PACKAGE group 618 * import android.*; 619 * </pre> 620 * <p> 621 * configure as follows: 622 * </p> 623 * <pre> 624 * <module name="CustomImportOrder"> 625 * <property name="customImportOrderRules" 626 * value="SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STATIC###SPECIAL_IMPORTS"/> 627 * <property name="specialImportsRegExp" value="^android\."/> 628 * </module> 629 * </pre> 630 * <p> 631 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 632 * </p> 633 * <p> 634 * Violation Message Keys: 635 * </p> 636 * <ul> 637 * <li> 638 * {@code custom.import.order} 639 * </li> 640 * <li> 641 * {@code custom.import.order.lex} 642 * </li> 643 * <li> 644 * {@code custom.import.order.line.separator} 645 * </li> 646 * <li> 647 * {@code custom.import.order.nonGroup.expected} 648 * </li> 649 * <li> 650 * {@code custom.import.order.nonGroup.import} 651 * </li> 652 * <li> 653 * {@code custom.import.order.separated.internally} 654 * </li> 655 * </ul> 656 * 657 * @since 5.8 658 */ 659@FileStatefulCheck 660public class CustomImportOrderCheck extends AbstractCheck { 661 662 /** 663 * A key is pointing to the warning message text in "messages.properties" 664 * file. 665 */ 666 public static final String MSG_LINE_SEPARATOR = "custom.import.order.line.separator"; 667 668 /** 669 * A key is pointing to the warning message text in "messages.properties" 670 * file. 671 */ 672 public static final String MSG_SEPARATED_IN_GROUP = "custom.import.order.separated.internally"; 673 674 /** 675 * A key is pointing to the warning message text in "messages.properties" 676 * file. 677 */ 678 public static final String MSG_LEX = "custom.import.order.lex"; 679 680 /** 681 * A key is pointing to the warning message text in "messages.properties" 682 * file. 683 */ 684 public static final String MSG_NONGROUP_IMPORT = "custom.import.order.nonGroup.import"; 685 686 /** 687 * A key is pointing to the warning message text in "messages.properties" 688 * file. 689 */ 690 public static final String MSG_NONGROUP_EXPECTED = "custom.import.order.nonGroup.expected"; 691 692 /** 693 * A key is pointing to the warning message text in "messages.properties" 694 * file. 695 */ 696 public static final String MSG_ORDER = "custom.import.order"; 697 698 /** STATIC group name. */ 699 public static final String STATIC_RULE_GROUP = "STATIC"; 700 701 /** SAME_PACKAGE group name. */ 702 public static final String SAME_PACKAGE_RULE_GROUP = "SAME_PACKAGE"; 703 704 /** THIRD_PARTY_PACKAGE group name. */ 705 public static final String THIRD_PARTY_PACKAGE_RULE_GROUP = "THIRD_PARTY_PACKAGE"; 706 707 /** STANDARD_JAVA_PACKAGE group name. */ 708 public static final String STANDARD_JAVA_PACKAGE_RULE_GROUP = "STANDARD_JAVA_PACKAGE"; 709 710 /** SPECIAL_IMPORTS group name. */ 711 public static final String SPECIAL_IMPORTS_RULE_GROUP = "SPECIAL_IMPORTS"; 712 713 /** NON_GROUP group name. */ 714 private static final String NON_GROUP_RULE_GROUP = "NOT_ASSIGNED_TO_ANY_GROUP"; 715 716 /** Pattern used to separate groups of imports. */ 717 private static final Pattern GROUP_SEPARATOR_PATTERN = Pattern.compile("\\s*###\\s*"); 718 719 /** Processed list of import order rules. */ 720 private final List<String> customOrderRules = new ArrayList<>(); 721 722 /** Contains objects with import attributes. */ 723 private final List<ImportDetails> importToGroupList = new ArrayList<>(); 724 725 /** Specify format of order declaration customizing by user. */ 726 private String customImportOrderRules = ""; 727 728 /** Specify RegExp for SAME_PACKAGE group imports. */ 729 private String samePackageDomainsRegExp = ""; 730 731 /** Specify RegExp for STANDARD_JAVA_PACKAGE group imports. */ 732 private Pattern standardPackageRegExp = Pattern.compile("^(java|javax)\\."); 733 734 /** Specify RegExp for THIRD_PARTY_PACKAGE group imports. */ 735 private Pattern thirdPartyPackageRegExp = Pattern.compile(".*"); 736 737 /** Specify RegExp for SPECIAL_IMPORTS group imports. */ 738 private Pattern specialImportsRegExp = Pattern.compile("^$"); 739 740 /** Force empty line separator between import groups. */ 741 private boolean separateLineBetweenGroups = true; 742 743 /** 744 * Force grouping alphabetically, 745 * in <a href="https://en.wikipedia.org/wiki/ASCII#Order"> ASCII sort order</a>. 746 */ 747 private boolean sortImportsInGroupAlphabetically; 748 749 /** Number of first domains for SAME_PACKAGE group. */ 750 private int samePackageMatchingDepth = 2; 751 752 /** 753 * Setter to specify RegExp for STANDARD_JAVA_PACKAGE group imports. 754 * 755 * @param regexp 756 * user value. 757 */ 758 public final void setStandardPackageRegExp(Pattern regexp) { 759 standardPackageRegExp = regexp; 760 } 761 762 /** 763 * Setter to specify RegExp for THIRD_PARTY_PACKAGE group imports. 764 * 765 * @param regexp 766 * user value. 767 */ 768 public final void setThirdPartyPackageRegExp(Pattern regexp) { 769 thirdPartyPackageRegExp = regexp; 770 } 771 772 /** 773 * Setter to specify RegExp for SPECIAL_IMPORTS group imports. 774 * 775 * @param regexp 776 * user value. 777 */ 778 public final void setSpecialImportsRegExp(Pattern regexp) { 779 specialImportsRegExp = regexp; 780 } 781 782 /** 783 * Setter to force empty line separator between import groups. 784 * 785 * @param value 786 * user value. 787 */ 788 public final void setSeparateLineBetweenGroups(boolean value) { 789 separateLineBetweenGroups = value; 790 } 791 792 /** 793 * Setter to force grouping alphabetically, in 794 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 795 * 796 * @param value 797 * user value. 798 */ 799 public final void setSortImportsInGroupAlphabetically(boolean value) { 800 sortImportsInGroupAlphabetically = value; 801 } 802 803 /** 804 * Setter to specify format of order declaration customizing by user. 805 * 806 * @param inputCustomImportOrder 807 * user value. 808 */ 809 public final void setCustomImportOrderRules(final String inputCustomImportOrder) { 810 if (!customImportOrderRules.equals(inputCustomImportOrder)) { 811 for (String currentState : GROUP_SEPARATOR_PATTERN.split(inputCustomImportOrder)) { 812 addRulesToList(currentState); 813 } 814 customOrderRules.add(NON_GROUP_RULE_GROUP); 815 } 816 customImportOrderRules = inputCustomImportOrder; 817 } 818 819 @Override 820 public int[] getDefaultTokens() { 821 return getRequiredTokens(); 822 } 823 824 @Override 825 public int[] getAcceptableTokens() { 826 return getRequiredTokens(); 827 } 828 829 @Override 830 public int[] getRequiredTokens() { 831 return new int[] { 832 TokenTypes.IMPORT, 833 TokenTypes.STATIC_IMPORT, 834 TokenTypes.PACKAGE_DEF, 835 }; 836 } 837 838 @Override 839 public void beginTree(DetailAST rootAST) { 840 importToGroupList.clear(); 841 } 842 843 @Override 844 public void visitToken(DetailAST ast) { 845 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 846 samePackageDomainsRegExp = createSamePackageRegexp( 847 samePackageMatchingDepth, ast); 848 } 849 else { 850 final String importFullPath = getFullImportIdent(ast); 851 final boolean isStatic = ast.getType() == TokenTypes.STATIC_IMPORT; 852 importToGroupList.add(new ImportDetails(importFullPath, 853 getImportGroup(isStatic, importFullPath), isStatic, ast)); 854 } 855 } 856 857 @Override 858 public void finishTree(DetailAST rootAST) { 859 if (!importToGroupList.isEmpty()) { 860 finishImportList(); 861 } 862 } 863 864 /** Examine the order of all the imports and log any violations. */ 865 private void finishImportList() { 866 String currentGroup = getFirstGroup(); 867 int currentGroupNumber = customOrderRules.lastIndexOf(currentGroup); 868 ImportDetails previousImportObjectFromCurrentGroup = null; 869 String previousImportFromCurrentGroup = null; 870 871 for (ImportDetails importObject : importToGroupList) { 872 final String importGroup = importObject.getImportGroup(); 873 final String fullImportIdent = importObject.getImportFullPath(); 874 875 if (importGroup.equals(currentGroup)) { 876 validateExtraEmptyLine(previousImportObjectFromCurrentGroup, 877 importObject, fullImportIdent); 878 if (isAlphabeticalOrderBroken(previousImportFromCurrentGroup, fullImportIdent)) { 879 log(importObject.getImportAST(), MSG_LEX, 880 fullImportIdent, previousImportFromCurrentGroup); 881 } 882 else { 883 previousImportFromCurrentGroup = fullImportIdent; 884 } 885 previousImportObjectFromCurrentGroup = importObject; 886 } 887 else { 888 // not the last group, last one is always NON_GROUP 889 if (customOrderRules.size() > currentGroupNumber + 1) { 890 final String nextGroup = getNextImportGroup(currentGroupNumber + 1); 891 if (importGroup.equals(nextGroup)) { 892 validateMissedEmptyLine(previousImportObjectFromCurrentGroup, 893 importObject, fullImportIdent); 894 currentGroup = nextGroup; 895 currentGroupNumber = customOrderRules.lastIndexOf(nextGroup); 896 previousImportFromCurrentGroup = fullImportIdent; 897 } 898 else { 899 logWrongImportGroupOrder(importObject.getImportAST(), 900 importGroup, nextGroup, fullImportIdent); 901 } 902 previousImportObjectFromCurrentGroup = importObject; 903 } 904 else { 905 logWrongImportGroupOrder(importObject.getImportAST(), 906 importGroup, currentGroup, fullImportIdent); 907 } 908 } 909 } 910 } 911 912 /** 913 * Log violation if empty line is missed. 914 * 915 * @param previousImport previous import from current group. 916 * @param importObject current import. 917 * @param fullImportIdent full import identifier. 918 */ 919 private void validateMissedEmptyLine(ImportDetails previousImport, 920 ImportDetails importObject, String fullImportIdent) { 921 if (isEmptyLineMissed(previousImport, importObject)) { 922 log(importObject.getImportAST(), MSG_LINE_SEPARATOR, fullImportIdent); 923 } 924 } 925 926 /** 927 * Log violation if extra empty line is present. 928 * 929 * @param previousImport previous import from current group. 930 * @param importObject current import. 931 * @param fullImportIdent full import identifier. 932 */ 933 private void validateExtraEmptyLine(ImportDetails previousImport, 934 ImportDetails importObject, String fullImportIdent) { 935 if (isSeparatedByExtraEmptyLine(previousImport, importObject)) { 936 log(importObject.getImportAST(), MSG_SEPARATED_IN_GROUP, fullImportIdent); 937 } 938 } 939 940 /** 941 * Get first import group. 942 * 943 * @return 944 * first import group of file. 945 */ 946 private String getFirstGroup() { 947 final ImportDetails firstImport = importToGroupList.get(0); 948 return getImportGroup(firstImport.isStaticImport(), 949 firstImport.getImportFullPath()); 950 } 951 952 /** 953 * Examine alphabetical order of imports. 954 * 955 * @param previousImport 956 * previous import of current group. 957 * @param currentImport 958 * current import. 959 * @return 960 * true, if previous and current import are not in alphabetical order. 961 */ 962 private boolean isAlphabeticalOrderBroken(String previousImport, 963 String currentImport) { 964 return sortImportsInGroupAlphabetically 965 && previousImport != null 966 && compareImports(currentImport, previousImport) < 0; 967 } 968 969 /** 970 * Examine empty lines between groups. 971 * 972 * @param previousImportObject 973 * previous import in current group. 974 * @param currentImportObject 975 * current import. 976 * @return 977 * true, if current import NOT separated from previous import by empty line. 978 */ 979 private boolean isEmptyLineMissed(ImportDetails previousImportObject, 980 ImportDetails currentImportObject) { 981 return separateLineBetweenGroups 982 && getCountOfEmptyLinesBetween( 983 previousImportObject.getEndLineNumber(), 984 currentImportObject.getStartLineNumber()) != 1; 985 } 986 987 /** 988 * Examine that imports separated by more than one empty line. 989 * 990 * @param previousImportObject 991 * previous import in current group. 992 * @param currentImportObject 993 * current import. 994 * @return 995 * true, if current import separated from previous by more than one empty line. 996 */ 997 private boolean isSeparatedByExtraEmptyLine(ImportDetails previousImportObject, 998 ImportDetails currentImportObject) { 999 return previousImportObject != null 1000 && getCountOfEmptyLinesBetween( 1001 previousImportObject.getEndLineNumber(), 1002 currentImportObject.getStartLineNumber()) > 0; 1003 } 1004 1005 /** 1006 * Log wrong import group order. 1007 * 1008 * @param importAST 1009 * import ast. 1010 * @param importGroup 1011 * import group. 1012 * @param currentGroupNumber 1013 * current group number we are checking. 1014 * @param fullImportIdent 1015 * full import name. 1016 */ 1017 private void logWrongImportGroupOrder(DetailAST importAST, String importGroup, 1018 String currentGroupNumber, String fullImportIdent) { 1019 if (NON_GROUP_RULE_GROUP.equals(importGroup)) { 1020 log(importAST, MSG_NONGROUP_IMPORT, fullImportIdent); 1021 } 1022 else if (NON_GROUP_RULE_GROUP.equals(currentGroupNumber)) { 1023 log(importAST, MSG_NONGROUP_EXPECTED, importGroup, fullImportIdent); 1024 } 1025 else { 1026 log(importAST, MSG_ORDER, importGroup, currentGroupNumber, fullImportIdent); 1027 } 1028 } 1029 1030 /** 1031 * Get next import group. 1032 * 1033 * @param currentGroupNumber 1034 * current group number. 1035 * @return 1036 * next import group. 1037 */ 1038 private String getNextImportGroup(int currentGroupNumber) { 1039 int nextGroupNumber = currentGroupNumber; 1040 1041 while (customOrderRules.size() > nextGroupNumber + 1) { 1042 if (hasAnyImportInCurrentGroup(customOrderRules.get(nextGroupNumber))) { 1043 break; 1044 } 1045 nextGroupNumber++; 1046 } 1047 return customOrderRules.get(nextGroupNumber); 1048 } 1049 1050 /** 1051 * Checks if current group contains any import. 1052 * 1053 * @param currentGroup 1054 * current group. 1055 * @return 1056 * true, if current group contains at least one import. 1057 */ 1058 private boolean hasAnyImportInCurrentGroup(String currentGroup) { 1059 boolean result = false; 1060 for (ImportDetails currentImport : importToGroupList) { 1061 if (currentGroup.equals(currentImport.getImportGroup())) { 1062 result = true; 1063 break; 1064 } 1065 } 1066 return result; 1067 } 1068 1069 /** 1070 * Get import valid group. 1071 * 1072 * @param isStatic 1073 * is static import. 1074 * @param importPath 1075 * full import path. 1076 * @return import valid group. 1077 */ 1078 private String getImportGroup(boolean isStatic, String importPath) { 1079 RuleMatchForImport bestMatch = new RuleMatchForImport(NON_GROUP_RULE_GROUP, 0, 0); 1080 if (isStatic && customOrderRules.contains(STATIC_RULE_GROUP)) { 1081 bestMatch.group = STATIC_RULE_GROUP; 1082 bestMatch.matchLength = importPath.length(); 1083 } 1084 else if (customOrderRules.contains(SAME_PACKAGE_RULE_GROUP)) { 1085 final String importPathTrimmedToSamePackageDepth = 1086 getFirstDomainsFromIdent(samePackageMatchingDepth, importPath); 1087 if (samePackageDomainsRegExp.equals(importPathTrimmedToSamePackageDepth)) { 1088 bestMatch.group = SAME_PACKAGE_RULE_GROUP; 1089 bestMatch.matchLength = importPath.length(); 1090 } 1091 } 1092 for (String group : customOrderRules) { 1093 if (STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(group)) { 1094 bestMatch = findBetterPatternMatch(importPath, 1095 STANDARD_JAVA_PACKAGE_RULE_GROUP, standardPackageRegExp, bestMatch); 1096 } 1097 if (SPECIAL_IMPORTS_RULE_GROUP.equals(group)) { 1098 bestMatch = findBetterPatternMatch(importPath, 1099 group, specialImportsRegExp, bestMatch); 1100 } 1101 } 1102 1103 if (NON_GROUP_RULE_GROUP.equals(bestMatch.group) 1104 && customOrderRules.contains(THIRD_PARTY_PACKAGE_RULE_GROUP) 1105 && thirdPartyPackageRegExp.matcher(importPath).find()) { 1106 bestMatch.group = THIRD_PARTY_PACKAGE_RULE_GROUP; 1107 } 1108 return bestMatch.group; 1109 } 1110 1111 /** 1112 * Tries to find better matching regular expression: 1113 * longer matching substring wins; in case of the same length, 1114 * lower position of matching substring wins. 1115 * 1116 * @param importPath 1117 * Full import identifier 1118 * @param group 1119 * Import group we are trying to assign the import 1120 * @param regExp 1121 * Regular expression for import group 1122 * @param currentBestMatch 1123 * object with currently best match 1124 * @return better match (if found) or the same (currentBestMatch) 1125 */ 1126 private static RuleMatchForImport findBetterPatternMatch(String importPath, String group, 1127 Pattern regExp, RuleMatchForImport currentBestMatch) { 1128 RuleMatchForImport betterMatchCandidate = currentBestMatch; 1129 final Matcher matcher = regExp.matcher(importPath); 1130 while (matcher.find()) { 1131 final int matchStart = matcher.start(); 1132 final int length = matcher.end() - matchStart; 1133 if (length > betterMatchCandidate.matchLength 1134 || length == betterMatchCandidate.matchLength 1135 && matchStart < betterMatchCandidate.matchPosition) { 1136 betterMatchCandidate = new RuleMatchForImport(group, length, matchStart); 1137 } 1138 } 1139 return betterMatchCandidate; 1140 } 1141 1142 /** 1143 * Checks compare two import paths. 1144 * 1145 * @param import1 1146 * current import. 1147 * @param import2 1148 * previous import. 1149 * @return a negative integer, zero, or a positive integer as the 1150 * specified String is greater than, equal to, or less 1151 * than this String, ignoring case considerations. 1152 */ 1153 private static int compareImports(String import1, String import2) { 1154 int result = 0; 1155 final String separator = "\\."; 1156 final String[] import1Tokens = import1.split(separator); 1157 final String[] import2Tokens = import2.split(separator); 1158 for (int i = 0; i != import1Tokens.length && i != import2Tokens.length; i++) { 1159 final String import1Token = import1Tokens[i]; 1160 final String import2Token = import2Tokens[i]; 1161 result = import1Token.compareTo(import2Token); 1162 if (result != 0) { 1163 break; 1164 } 1165 } 1166 if (result == 0) { 1167 result = Integer.compare(import1Tokens.length, import2Tokens.length); 1168 } 1169 return result; 1170 } 1171 1172 /** 1173 * Counts empty lines between given parameters. 1174 * 1175 * @param fromLineNo 1176 * One-based line number of previous import. 1177 * @param toLineNo 1178 * One-based line number of current import. 1179 * @return count of empty lines between given parameters, exclusive, 1180 * eg., (fromLineNo, toLineNo). 1181 */ 1182 private int getCountOfEmptyLinesBetween(int fromLineNo, int toLineNo) { 1183 int result = 0; 1184 final String[] lines = getLines(); 1185 1186 for (int i = fromLineNo + 1; i <= toLineNo - 1; i++) { 1187 // "- 1" because the numbering is one-based 1188 if (CommonUtil.isBlank(lines[i - 1])) { 1189 result++; 1190 } 1191 } 1192 return result; 1193 } 1194 1195 /** 1196 * Forms import full path. 1197 * 1198 * @param token 1199 * current token. 1200 * @return full path or null. 1201 */ 1202 private static String getFullImportIdent(DetailAST token) { 1203 String ident = ""; 1204 if (token != null) { 1205 ident = FullIdent.createFullIdent(token.findFirstToken(TokenTypes.DOT)).getText(); 1206 } 1207 return ident; 1208 } 1209 1210 /** 1211 * Parses ordering rule and adds it to the list with rules. 1212 * 1213 * @param ruleStr 1214 * String with rule. 1215 * @throws IllegalArgumentException when SAME_PACKAGE rule parameter is not positive integer 1216 * @throws IllegalStateException when ruleStr is unexpected value 1217 */ 1218 private void addRulesToList(String ruleStr) { 1219 if (STATIC_RULE_GROUP.equals(ruleStr) 1220 || THIRD_PARTY_PACKAGE_RULE_GROUP.equals(ruleStr) 1221 || STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(ruleStr) 1222 || SPECIAL_IMPORTS_RULE_GROUP.equals(ruleStr)) { 1223 customOrderRules.add(ruleStr); 1224 } 1225 else if (ruleStr.startsWith(SAME_PACKAGE_RULE_GROUP)) { 1226 final String rule = ruleStr.substring(ruleStr.indexOf('(') + 1, 1227 ruleStr.indexOf(')')); 1228 samePackageMatchingDepth = Integer.parseInt(rule); 1229 if (samePackageMatchingDepth <= 0) { 1230 throw new IllegalArgumentException( 1231 "SAME_PACKAGE rule parameter should be positive integer: " + ruleStr); 1232 } 1233 customOrderRules.add(SAME_PACKAGE_RULE_GROUP); 1234 } 1235 else { 1236 throw new IllegalStateException("Unexpected rule: " + ruleStr); 1237 } 1238 } 1239 1240 /** 1241 * Creates samePackageDomainsRegExp of the first package domains. 1242 * 1243 * @param firstPackageDomainsCount 1244 * number of first package domains. 1245 * @param packageNode 1246 * package node. 1247 * @return same package regexp. 1248 */ 1249 private static String createSamePackageRegexp(int firstPackageDomainsCount, 1250 DetailAST packageNode) { 1251 final String packageFullPath = getFullImportIdent(packageNode); 1252 return getFirstDomainsFromIdent(firstPackageDomainsCount, packageFullPath); 1253 } 1254 1255 /** 1256 * Extracts defined amount of domains from the left side of package/import identifier. 1257 * 1258 * @param firstPackageDomainsCount 1259 * number of first package domains. 1260 * @param packageFullPath 1261 * full identifier containing path to package or imported object. 1262 * @return String with defined amount of domains or full identifier 1263 * (if full identifier had less domain than specified) 1264 */ 1265 private static String getFirstDomainsFromIdent( 1266 final int firstPackageDomainsCount, final String packageFullPath) { 1267 final StringBuilder builder = new StringBuilder(256); 1268 final StringTokenizer tokens = new StringTokenizer(packageFullPath, "."); 1269 int count = firstPackageDomainsCount; 1270 1271 while (count > 0 && tokens.hasMoreTokens()) { 1272 builder.append(tokens.nextToken()).append('.'); 1273 count--; 1274 } 1275 return builder.toString(); 1276 } 1277 1278 /** 1279 * Contains import attributes as line number, import full path, import 1280 * group. 1281 */ 1282 private static final class ImportDetails { 1283 1284 /** Import full path. */ 1285 private final String importFullPath; 1286 1287 /** Import group. */ 1288 private final String importGroup; 1289 1290 /** Is static import. */ 1291 private final boolean staticImport; 1292 1293 /** Import AST. */ 1294 private final DetailAST importAST; 1295 1296 /** 1297 * Initialise importFullPath, importGroup, staticImport, importAST. 1298 * 1299 * @param importFullPath 1300 * import full path. 1301 * @param importGroup 1302 * import group. 1303 * @param staticImport 1304 * if import is static. 1305 * @param importAST 1306 * import ast 1307 */ 1308 private ImportDetails(String importFullPath, String importGroup, boolean staticImport, 1309 DetailAST importAST) { 1310 this.importFullPath = importFullPath; 1311 this.importGroup = importGroup; 1312 this.staticImport = staticImport; 1313 this.importAST = importAST; 1314 } 1315 1316 /** 1317 * Get import full path variable. 1318 * 1319 * @return import full path variable. 1320 */ 1321 public String getImportFullPath() { 1322 return importFullPath; 1323 } 1324 1325 /** 1326 * Get import start line number from ast. 1327 * 1328 * @return import start line from ast. 1329 */ 1330 public int getStartLineNumber() { 1331 return importAST.getLineNo(); 1332 } 1333 1334 /** 1335 * Get import end line number from ast. 1336 * <p> 1337 * <b>Note:</b> It can be different from <b>startLineNumber</b> when import statement span 1338 * multiple lines. 1339 * </p> 1340 * 1341 * @return import end line from ast. 1342 */ 1343 public int getEndLineNumber() { 1344 return importAST.getLastChild().getLineNo(); 1345 } 1346 1347 /** 1348 * Get import group. 1349 * 1350 * @return import group. 1351 */ 1352 public String getImportGroup() { 1353 return importGroup; 1354 } 1355 1356 /** 1357 * Checks if import is static. 1358 * 1359 * @return true, if import is static. 1360 */ 1361 public boolean isStaticImport() { 1362 return staticImport; 1363 } 1364 1365 /** 1366 * Get import ast. 1367 * 1368 * @return import ast. 1369 */ 1370 public DetailAST getImportAST() { 1371 return importAST; 1372 } 1373 1374 } 1375 1376 /** 1377 * Contains matching attributes assisting in definition of "best matching" 1378 * group for import. 1379 */ 1380 private static final class RuleMatchForImport { 1381 1382 /** Position of matching string for current best match. */ 1383 private final int matchPosition; 1384 /** Length of matching string for current best match. */ 1385 private int matchLength; 1386 /** Import group for current best match. */ 1387 private String group; 1388 1389 /** 1390 * Constructor to initialize the fields. 1391 * 1392 * @param group 1393 * Matched group. 1394 * @param length 1395 * Matching length. 1396 * @param position 1397 * Matching position. 1398 */ 1399 private RuleMatchForImport(String group, int length, int position) { 1400 this.group = group; 1401 matchLength = length; 1402 matchPosition = position; 1403 } 1404 1405 } 1406 1407}