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.Locale; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.FullIdent; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 032 033/** 034 * <p> 035 * Checks the ordering/grouping of imports. Features are: 036 * </p> 037 * <ul> 038 * <li> 039 * groups type/static imports: ensures that groups of imports come in a specific order 040 * (e.g., java. comes first, javax. comes second, then everything else) 041 * </li> 042 * <li> 043 * adds a separation between type import groups : ensures that a blank line sit between each group 044 * </li> 045 * <li> 046 * type/static import groups aren't separated internally: ensures that each group aren't separated 047 * internally by blank line or comment 048 * </li> 049 * <li> 050 * sorts type/static imports inside each group: ensures that imports within each group are in 051 * lexicographic order 052 * </li> 053 * <li> 054 * sorts according to case: ensures that the comparison between imports is case-sensitive, in 055 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a> 056 * </li> 057 * <li> 058 * arrange static imports: ensures the relative order between type imports and static imports 059 * (see 060 * <a href="https://checkstyle.org/property_types.html#ImportOrderOption">ImportOrderOption</a>) 061 * </li> 062 * </ul> 063 * <ul> 064 * <li> 065 * Property {@code option} - specify policy on the relative order between type imports and static 066 * imports. 067 * Type is {@code com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderOption}. 068 * Default value is {@code under}. 069 * </li> 070 * <li> 071 * Property {@code groups} - specify list of <b>type import</b> groups. Every group identified 072 * either by a common prefix string, or by a regular expression enclosed in forward slashes 073 * (e.g. {@code /regexp/}). All type imports, which does not match any group, falls into an 074 * additional group, located at the end. 075 * Thus, the empty list of type groups (the default value) means one group for all type imports. 076 * Type is {@code java.util.regex.Pattern[]}. 077 * Default value is {@code ""}. 078 * </li> 079 * <li> 080 * Property {@code ordered} - control whether type imports within each group should be 081 * sorted. 082 * It doesn't affect sorting for static imports. 083 * Type is {@code boolean}. 084 * Default value is {@code true}. 085 * </li> 086 * <li> 087 * Property {@code separated} - control whether type import groups should be separated 088 * by, at least, one blank line or comment and aren't separated internally. 089 * It doesn't affect separations for static imports. 090 * Type is {@code boolean}. 091 * Default value is {@code false}. 092 * </li> 093 * <li> 094 * Property {@code separatedStaticGroups} - control whether static import groups should 095 * be separated by, at least, one blank line or comment and aren't separated internally. 096 * This property has effect only when the property {@code option} is set to {@code top} 097 * or {@code bottom} and when property {@code staticGroups} is enabled. 098 * Type is {@code boolean}. 099 * Default value is {@code false}. 100 * </li> 101 * <li> 102 * Property {@code caseSensitive} - control whether string comparison should be 103 * case-sensitive or not. Case-sensitive sorting is in 104 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 105 * It affects both type imports and static imports. 106 * Type is {@code boolean}. 107 * Default value is {@code true}. 108 * </li> 109 * <li> 110 * Property {@code staticGroups} - specify list of <b>static</b> import groups. Every group 111 * identified either by a common prefix string, or by a regular expression enclosed in forward 112 * slashes (e.g. {@code /regexp/}). All static imports, which does not match any group, fall into 113 * an additional group, located at the end. Thus, the empty list of static groups (the default 114 * value) means one group for all static imports. This property has effect only when the property 115 * {@code option} is set to {@code top} or {@code bottom}. 116 * Type is {@code java.util.regex.Pattern[]}. 117 * Default value is {@code ""}. 118 * </li> 119 * <li> 120 * Property {@code sortStaticImportsAlphabetically} - control whether 121 * <b>static imports</b> located at <b>top</b> or <b>bottom</b> are sorted within the group. 122 * Type is {@code boolean}. 123 * Default value is {@code false}. 124 * </li> 125 * <li> 126 * Property {@code useContainerOrderingForStatic} - control whether to use container 127 * ordering (Eclipse IDE term) for static imports or not. 128 * Type is {@code boolean}. 129 * Default value is {@code false}. 130 * </li> 131 * </ul> 132 * <p> 133 * To configure the check: 134 * </p> 135 * <pre> 136 * <module name="ImportOrder"/> 137 * </pre> 138 * <p> 139 * Example: 140 * </p> 141 * <pre> 142 * import java.io.IOException; 143 * import java.net.URL; 144 * 145 * import java.io.IOException; // violation, extra separation before import 146 * // and wrong order, comes before 'java.net.URL'. 147 * import javax.net.ssl.TrustManager; // violation, extra separation due to above comment 148 * import javax.swing.JComponent; 149 * import org.apache.http.conn.ClientConnectionManager; // OK 150 * import java.util.Set; // violation, wrong order, 'java' should not come after 'org' imports 151 * import com.neurologic.http.HttpClient; // violation, wrong order, 'com' imports comes at top 152 * import com.neurologic.http.impl.ApacheHttpClient; // OK 153 * 154 * public class SomeClass { } 155 * </pre> 156 * <p> 157 * To configure the check so that it matches default Eclipse formatter configuration 158 * (tested on Kepler and Luna releases): 159 * </p> 160 * <ul> 161 * <li> 162 * group of static imports is on the top 163 * </li> 164 * <li> 165 * groups of type imports: "java" and "javax" packages first, then "org" and then all other imports 166 * </li> 167 * <li> 168 * imports will be sorted in the groups 169 * </li> 170 * <li> 171 * groups are separated by, at least, one blank line and aren't separated internally 172 * </li> 173 * </ul> 174 * <p> 175 * Notes: 176 * </p> 177 * <ul> 178 * <li> 179 * "com" package is not mentioned on configuration, because it is ignored by Eclipse Kepler and Luna 180 * (looks like Eclipse defect) 181 * </li> 182 * <li> 183 * configuration below doesn't work in all 100% cases due to inconsistent behavior prior to 184 * Mars release, but covers most scenarios 185 * </li> 186 * </ul> 187 * <pre> 188 * <module name="ImportOrder"> 189 * <property name="groups" value="/^java\./,javax,org"/> 190 * <property name="ordered" value="true"/> 191 * <property name="separated" value="true"/> 192 * <property name="option" value="above"/> 193 * <property name="sortStaticImportsAlphabetically" value="true"/> 194 * </module> 195 * </pre> 196 * <p> 197 * Example: 198 * </p> 199 * <pre> 200 * import static java.lang.System.out; 201 * import static java.lang.Math; // violation, alphabetical case-sensitive ASCII order, 'M' < 'S' 202 * import java.io.IOException; 203 * 204 * import java.net.URL; // violation, extra separation before import 205 * import java.security.KeyManagementException; 206 * 207 * import javax.net.ssl.TrustManager; 208 * 209 * import javax.net.ssl.X509TrustManager; // violation, groups should not be separated internally 210 * 211 * import org.apache.http.conn.ClientConnectionManager; 212 * 213 * public class SomeClass { } 214 * </pre> 215 * <p> 216 * To configure the check so that it matches default Eclipse formatter configuration 217 * (tested on Mars release): 218 * </p> 219 * <ul> 220 * <li> 221 * group of static imports is on the top 222 * </li> 223 * <li> 224 * groups of type imports: "java" and "javax" packages first, then "org" and "com", 225 * then all other imports as one group 226 * </li> 227 * <li> 228 * imports will be sorted in the groups 229 * </li> 230 * <li> 231 * groups are separated by, at least, one blank line and aren't separated internally 232 * </li> 233 * </ul> 234 * <pre> 235 * <module name="ImportOrder"> 236 * <property name="groups" value="/^java\./,javax,org,com"/> 237 * <property name="ordered" value="true"/> 238 * <property name="separated" value="true"/> 239 * <property name="option" value="above"/> 240 * <property name="sortStaticImportsAlphabetically" value="true"/> 241 * </module> 242 * </pre> 243 * <p> 244 * Example: 245 * </p> 246 * <pre> 247 * import static java.io.File.createTempFile; 248 * import static java.lang.Math.abs; // OK, alphabetical case-sensitive ASCII order, 'i' < 'l' 249 * import java.lang.Math.sqrt; // OK, follows property 'Option' value 'above' 250 * import java.io.File; // violation, alphabetical case-sensitive ASCII order, 'i' < 'l' 251 * 252 * import java.io.IOException; // violation, extra separation in 'java' import group 253 * 254 * import org.albedo.*; 255 * 256 * import static javax.WindowConstants.*; // violation, wrong order, 'javax' comes before 'org' 257 * import javax.swing.JComponent; 258 * import org.apache.http.ClientConnectionManager; // violation, must separate from previous import 259 * import org.linux.apache.server.SoapServer; // OK 260 * 261 * import com.neurologic.http.HttpClient; // OK 262 * import com.neurologic.http.impl.ApacheHttpClient; // OK 263 * 264 * public class SomeClass { } 265 * </pre> 266 * <p> 267 * To configure the check so that it matches default IntelliJ IDEA formatter 268 * configuration (tested on v2018.2): 269 * </p> 270 * <ul> 271 * <li> 272 * group of static imports is on the bottom 273 * </li> 274 * <li> 275 * groups of type imports: all imports except of "javax" and "java", then "javax" and "java" 276 * </li> 277 * <li> 278 * imports will be sorted in the groups 279 * </li> 280 * <li> 281 * groups are separated by, at least, one blank line and aren't separated internally 282 * </li> 283 * </ul> 284 * <p> 285 * Note: a <a href="https://checkstyle.org/config_filters.html#SuppressionXpathSingleFilter"> 286 * suppression xpath single filter</a> is needed because 287 * IDEA has no blank line between "javax" and "java". 288 * ImportOrder has a limitation by design to enforce an empty line between groups ("java", "javax"). 289 * There is no flexibility to enforce empty lines between some groups and no empty lines between 290 * other groups. 291 * </p> 292 * <p> 293 * Note: "separated" option is disabled because IDEA default has blank line between "java" and 294 * static imports, and no blank line between "javax" and "java". 295 * </p> 296 * <pre> 297 * <module name="ImportOrder"> 298 * <property name="groups" value="*,javax,java"/> 299 * <property name="ordered" value="true"/> 300 * <property name="separated" value="false"/> 301 * <property name="option" value="bottom"/> 302 * <property name="sortStaticImportsAlphabetically" value="true"/> 303 * </module> 304 * <module name="SuppressionXpathSingleFilter"> 305 * <property name="checks" value="ImportOrder"/> 306 * <property name="message" value="^'java\..*'.*"/> 307 * </module> 308 * </pre> 309 * <p> 310 * Example: 311 * </p> 312 * <pre> 313 * import com.neurologic.http.impl.ApacheHttpClient; // OK 314 * import static java.awt.Button.A; 315 * import javax.swing.JComponent; // violation, wrong order, caused by above static import 316 * // all static imports comes at bottom 317 * import java.net.URL; // violation, extra separation in import group 318 * import java.security.KeyManagementException; 319 * import javax.swing.JComponent; // violation, wrong order, 'javax' should be above 'java' imports 320 * import com.neurologic.http.HttpClient; // violation, wrong order, 'com' imports should be at top 321 * 322 * public class TestClass { } 323 * </pre> 324 * <p> 325 * To configure the check so that it matches default NetBeans formatter configuration 326 * (tested on v8): 327 * </p> 328 * <ul> 329 * <li> 330 * groups of type imports are not defined, all imports will be sorted as a one group 331 * </li> 332 * <li> 333 * static imports are not separated, they will be sorted along with other imports 334 * </li> 335 * </ul> 336 * <pre> 337 * <module name="ImportOrder"> 338 * <property name="option" value="inflow"/> 339 * </module> 340 * </pre> 341 * <p> 342 * Example: 343 * </p> 344 * <pre> 345 * import static java.io.File.createTempFile; 346 * import java.lang.Math.sqrt; 347 * 348 * import javax.swing.JComponent; // violation, extra separation in import group 349 * import static javax.windowConstants.*; // OK 350 * // all static imports are processed like non static imports. 351 * public class SomeClass { } 352 * </pre> 353 * <p> 354 * Group descriptions enclosed in slashes are interpreted as regular expressions. 355 * If multiple groups match, the one matching a longer substring of the imported name 356 * will take precedence, with ties broken first in favor of earlier matches and finally 357 * in favor of the first matching group. 358 * </p> 359 * <p> 360 * There is always a wildcard group to which everything not in a named group belongs. 361 * If an import does not match a named group, the group belongs to this wildcard group. 362 * The wildcard group position can be specified using the {@code *} character. 363 * </p> 364 * <p> 365 * Check also has on option making it more flexible: <b>sortStaticImportsAlphabetically</b> 366 * - sets whether static imports grouped by <b>top</b> or <b>bottom</b> option should be sorted 367 * alphabetically or not, default value is <b>false</b>. It is applied to static imports grouped 368 * with <b>top</b> or <b>bottom</b> options. This option is helping in reconciling of this 369 * Check and other tools like Eclipse's Organize Imports feature. 370 * </p> 371 * <p> 372 * To configure the Check allows static imports grouped to the <b>top</b> being sorted 373 * alphabetically: 374 * </p> 375 * <pre> 376 * <module name="ImportOrder"> 377 * <property name="sortStaticImportsAlphabetically" value="true"/> 378 * <property name="option" value="top"/> 379 * </module> 380 * </pre> 381 * <pre> 382 * import static java.lang.Math.PI; 383 * import static java.lang.Math.abs; // OK, alphabetical case-sensitive ASCII order, 'P' < 'a' 384 * import static org.abego.treelayout.Configuration.AlignmentInLevel; // OK, alphabetical order 385 * 386 * import java.util.Set; // violation, extra separation in import group 387 * import static java.lang.Math.abs; // violation, wrong order, all static imports comes at 'top' 388 * import org.abego.*; 389 * 390 * public class SomeClass { } 391 * </pre> 392 * <p> 393 * To configure the Check with groups of static imports: 394 * </p> 395 * <pre> 396 * <module name="ImportOrder"> 397 * <property name="staticGroups" value="org,java"/> 398 * <property name="sortStaticImportsAlphabetically" value="true"/> 399 * <property name="option" value="top"/> 400 * </module> 401 * </pre> 402 * <pre> 403 * import static org.abego.treelayout.Configuration.AlignmentInLevel; // Group 1 404 * import static java.lang.Math.abs; // Group 2 405 * import static java.lang.String.format; // Group 2 406 * import static com.google.common.primitives.Doubles.BYTES; // Group "everything else" 407 * 408 * public class SomeClass { } 409 * </pre> 410 * <p> 411 * The following example shows the idea of 'useContainerOrderingForStatic' option that is 412 * useful for Eclipse IDE users to match ordering validation. 413 * This is how the import comparison works for static imports: we first compare 414 * the container of the static import, container is the type enclosing the static element 415 * being imported. When the result of the comparison is 0 (containers are equal), 416 * we compare the fully qualified import names. 417 * </p> 418 * <p> 419 * For e.g. this is what is considered to be container names for the given example: 420 * </p> 421 * <pre> 422 * import static HttpConstants.COLON => HttpConstants 423 * import static HttpHeaders.addHeader => HttpHeaders 424 * import static HttpHeaders.setHeader => HttpHeaders 425 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 426 * </pre> 427 * <p> 428 * According to this logic, HttpHeaders.Names should come after HttpHeaders. 429 * </p> 430 * <p> 431 * Example for {@code useContainerOrderingForStatic=true} 432 * </p> 433 * <pre> 434 * <module name="ImportOrder"> 435 * <property name="useContainerOrderingForStatic" value="true"/> 436 * <property name="ordered" value="true"/> 437 * <property name="option" value="top"/> 438 * <property name="caseSensitive" value="false"/> 439 * <property name="sortStaticImportsAlphabetically" value="true"/> 440 * </module> 441 * </pre> 442 * <pre> 443 * import static io.netty.handler.codec.http.HttpConstants.COLON; 444 * import static io.netty.handler.codec.http.HttpHeaders.addHeader; 445 * import static io.netty.handler.codec.http.HttpHeaders.setHeader; 446 * import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; 447 * 448 * public class InputEclipseStaticImportsOrder { } 449 * </pre> 450 * <p> 451 * Example for {@code useContainerOrderingForStatic=false} 452 * </p> 453 * <pre> 454 * <module name="ImportOrder"> 455 * <property name="useContainerOrderingForStatic" value="false"/> 456 * <property name="ordered" value="true"/> 457 * <property name="option" value="top"/> 458 * <property name="caseSensitive" value="false"/> 459 * <property name="sortStaticImportsAlphabetically" value="true"/> 460 * </module> 461 * </pre> 462 * <pre> 463 * import static io.netty.handler.codec.http.HttpConstants.COLON; 464 * import static io.netty.handler.codec.http.HttpHeaders.addHeader; 465 * import static io.netty.handler.codec.http.HttpHeaders.setHeader; 466 * import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; // violation 467 * 468 * public class InputEclipseStaticImportsOrder { } 469 * </pre> 470 * <p> 471 * To configure the check to enforce static import group separation 472 * </p> 473 * <p> 474 * Example for {@code separatedStaticGroups=true} 475 * </p> 476 * <pre> 477 * <module name="ImportOrder"> 478 * <property name="staticGroups" value="*,java,javax,org"/> 479 * <property name="option" value="top"/> 480 * <property name="separatedStaticGroups" value="true"/> 481 * </module> 482 * </pre> 483 * <pre> 484 * import static java.lang.Math.PI; 485 * import static java.io.File.createTempFile; 486 * import static javax.swing.JComponent; // violation, should be separated from previous imports 487 * import static javax.WindowConstants.*; // OK 488 * 489 * import java.net.URL; 490 * 491 * public class SomeClass { } 492 * </pre> 493 * <p> 494 * To configure the Check with groups of static imports when staticGroups="" 495 * represents all imports as {@code everything else} group: 496 * </p> 497 * <pre> 498 * <module name="ImportOrder"> 499 * <property name="staticGroups" value=""/> 500 * <property name="option" value="top"/> 501 * </module> 502 * </pre> 503 * <pre> 504 * import static java.io.File.listRoots; // OK 505 * import static javax.swing.WindowConstants.*; // OK 506 * import static java.io.File.createTempFile; // OK 507 * import static com.puppycrawl.tools.checkstyle; // OK 508 * 509 * public class SomeClass { } 510 * </pre> 511 * <p> 512 * To configure the Check with groups of static imports when 513 * staticGroups="java, javax" represents three groups i.e java*, javax* 514 * and * (everything else). In below example the static imports {@code com...} 515 * should be in last group: 516 * </p> 517 * <pre> 518 * <module name="ImportOrder"> 519 * <property name="staticGroups" value="java, javax"/> 520 * <property name="option" value="top"/> 521 * </module> 522 * </pre> 523 * <pre> 524 * import static java.io.File.listRoots; // OK 525 * import static javax.swing.WindowConstants.*; // OK 526 * import static java.io.File.createTempFile; // violation should be before javax 527 * import static com.puppycrawl.tools.checkstyle; // OK 528 * 529 * public class SomeClass { } 530 * </pre> 531 * <p> 532 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 533 * </p> 534 * <p> 535 * Violation Message Keys: 536 * </p> 537 * <ul> 538 * <li> 539 * {@code import.groups.separated.internally} 540 * </li> 541 * <li> 542 * {@code import.ordering} 543 * </li> 544 * <li> 545 * {@code import.separation} 546 * </li> 547 * </ul> 548 * 549 * @since 3.2 550 */ 551@FileStatefulCheck 552public class ImportOrderCheck 553 extends AbstractCheck { 554 555 /** 556 * A key is pointing to the warning message text in "messages.properties" 557 * file. 558 */ 559 public static final String MSG_SEPARATION = "import.separation"; 560 561 /** 562 * A key is pointing to the warning message text in "messages.properties" 563 * file. 564 */ 565 public static final String MSG_ORDERING = "import.ordering"; 566 567 /** 568 * A key is pointing to the warning message text in "messages.properties" 569 * file. 570 */ 571 public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally"; 572 573 /** The special wildcard that catches all remaining groups. */ 574 private static final String WILDCARD_GROUP_NAME = "*"; 575 576 /** Empty array of pattern type needed to initialize check. */ 577 private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0]; 578 579 /** 580 * Specify list of <b>type import</b> groups. Every group identified either by a common prefix 581 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 582 * All type imports, which does not match any group, falls into an additional group, 583 * located at the end. Thus, the empty list of type groups (the default value) means one group 584 * for all type imports. 585 */ 586 private Pattern[] groups = EMPTY_PATTERN_ARRAY; 587 588 /** 589 * Specify list of <b>static</b> import groups. Every group identified either by a common prefix 590 * string, or by a regular expression enclosed in forward slashes (e.g. {@code /regexp/}). 591 * All static imports, which does not match any group, fall into an additional group, located 592 * at the end. Thus, the empty list of static groups (the default value) means one group for all 593 * static imports. This property has effect only when the property {@code option} is set to 594 * {@code top} or {@code bottom}. 595 */ 596 private Pattern[] staticGroups = EMPTY_PATTERN_ARRAY; 597 598 /** 599 * Control whether type import groups should be separated by, at least, one blank 600 * line or comment and aren't separated internally. It doesn't affect separations for static 601 * imports. 602 */ 603 private boolean separated; 604 605 /** 606 * Control whether static import groups should be separated by, at least, one blank 607 * line or comment and aren't separated internally. This property has effect only when the 608 * property {@code option} is set to {@code top} or {@code bottom} and when property 609 * {@code staticGroups} is enabled. 610 */ 611 private boolean separatedStaticGroups; 612 613 /** 614 * Control whether type imports within each group should be sorted. 615 * It doesn't affect sorting for static imports. 616 */ 617 private boolean ordered = true; 618 619 /** 620 * Control whether string comparison should be case-sensitive or not. Case-sensitive 621 * sorting is in <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 622 * It affects both type imports and static imports. 623 */ 624 private boolean caseSensitive = true; 625 626 /** Last imported group. */ 627 private int lastGroup; 628 /** Line number of last import. */ 629 private int lastImportLine; 630 /** Name of last import. */ 631 private String lastImport; 632 /** If last import was static. */ 633 private boolean lastImportStatic; 634 /** Whether there were any imports. */ 635 private boolean beforeFirstImport; 636 /** 637 * Whether static and type import groups should be split apart. 638 * When the {@code option} property is set to {@code INFLOW}, {@code ABOVE} or {@code UNDER}, 639 * both the type and static imports use the properties {@code groups} and {@code separated}. 640 * When the {@code option} property is set to {@code TOP} or {@code BOTTOM}, static imports 641 * uses the properties {@code staticGroups} and {@code separatedStaticGroups}. 642 **/ 643 private boolean staticImportsApart; 644 645 /** 646 * Control whether <b>static imports</b> located at <b>top</b> or <b>bottom</b> are 647 * sorted within the group. 648 */ 649 private boolean sortStaticImportsAlphabetically; 650 651 /** 652 * Control whether to use container ordering (Eclipse IDE term) for static imports 653 * or not. 654 */ 655 private boolean useContainerOrderingForStatic; 656 657 /** 658 * Specify policy on the relative order between type imports and static imports. 659 */ 660 private ImportOrderOption option = ImportOrderOption.UNDER; 661 662 /** 663 * Setter to specify policy on the relative order between type imports and static imports. 664 * 665 * @param optionStr string to decode option from 666 * @throws IllegalArgumentException if unable to decode 667 */ 668 public void setOption(String optionStr) { 669 option = ImportOrderOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 670 } 671 672 /** 673 * Setter to specify list of <b>type import</b> groups. Every group identified either by a 674 * common prefix string, or by a regular expression enclosed in forward slashes 675 * (e.g. {@code /regexp/}). All type imports, which does not match any group, falls into an 676 * additional group, located at the end. Thus, the empty list of type groups (the default value) 677 * means one group for all type imports. 678 * 679 * @param packageGroups a comma-separated list of package names/prefixes. 680 */ 681 public void setGroups(String... packageGroups) { 682 groups = compilePatterns(packageGroups); 683 } 684 685 /** 686 * Setter to specify list of <b>static</b> import groups. Every group identified either by a 687 * common prefix string, or by a regular expression enclosed in forward slashes 688 * (e.g. {@code /regexp/}). All static imports, which does not match any group, fall into an 689 * additional group, located at the end. Thus, the empty list of static groups (the default 690 * value) means one group for all static imports. This property has effect only when 691 * the property {@code option} is set to {@code top} or {@code bottom}. 692 * 693 * @param packageGroups a comma-separated list of package names/prefixes. 694 */ 695 public void setStaticGroups(String... packageGroups) { 696 staticGroups = compilePatterns(packageGroups); 697 } 698 699 /** 700 * Setter to control whether type imports within each group should be sorted. 701 * It doesn't affect sorting for static imports. 702 * 703 * @param ordered 704 * whether lexicographic ordering of imports within a group 705 * required or not. 706 */ 707 public void setOrdered(boolean ordered) { 708 this.ordered = ordered; 709 } 710 711 /** 712 * Setter to control whether type import groups should be separated by, at least, 713 * one blank line or comment and aren't separated internally. 714 * It doesn't affect separations for static imports. 715 * 716 * @param separated 717 * whether groups should be separated by one blank line or comment. 718 */ 719 public void setSeparated(boolean separated) { 720 this.separated = separated; 721 } 722 723 /** 724 * Setter to control whether static import groups should be separated by, at least, 725 * one blank line or comment and aren't separated internally. 726 * This property has effect only when the property 727 * {@code option} is set to {@code top} or {@code bottom} and when property {@code staticGroups} 728 * is enabled. 729 * 730 * @param separatedStaticGroups 731 * whether groups should be separated by one blank line or comment. 732 */ 733 public void setSeparatedStaticGroups(boolean separatedStaticGroups) { 734 this.separatedStaticGroups = separatedStaticGroups; 735 } 736 737 /** 738 * Setter to control whether string comparison should be case-sensitive or not. 739 * Case-sensitive sorting is in 740 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>. 741 * It affects both type imports and static imports. 742 * 743 * @param caseSensitive 744 * whether string comparison should be case-sensitive. 745 */ 746 public void setCaseSensitive(boolean caseSensitive) { 747 this.caseSensitive = caseSensitive; 748 } 749 750 /** 751 * Setter to control whether <b>static imports</b> located at <b>top</b> or 752 * <b>bottom</b> are sorted within the group. 753 * 754 * @param sortAlphabetically true or false. 755 */ 756 public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) { 757 sortStaticImportsAlphabetically = sortAlphabetically; 758 } 759 760 /** 761 * Setter to control whether to use container ordering (Eclipse IDE term) for static 762 * imports or not. 763 * 764 * @param useContainerOrdering whether to use container ordering for static imports or not. 765 */ 766 public void setUseContainerOrderingForStatic(boolean useContainerOrdering) { 767 useContainerOrderingForStatic = useContainerOrdering; 768 } 769 770 @Override 771 public int[] getDefaultTokens() { 772 return getRequiredTokens(); 773 } 774 775 @Override 776 public int[] getAcceptableTokens() { 777 return getRequiredTokens(); 778 } 779 780 @Override 781 public int[] getRequiredTokens() { 782 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 783 } 784 785 @Override 786 public void beginTree(DetailAST rootAST) { 787 lastGroup = Integer.MIN_VALUE; 788 lastImportLine = Integer.MIN_VALUE; 789 lastImport = ""; 790 lastImportStatic = false; 791 beforeFirstImport = true; 792 staticImportsApart = 793 option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM; 794 } 795 796 // -@cs[CyclomaticComplexity] SWITCH was transformed into IF-ELSE. 797 @Override 798 public void visitToken(DetailAST ast) { 799 final FullIdent ident; 800 final boolean isStatic; 801 802 if (ast.getType() == TokenTypes.IMPORT) { 803 ident = FullIdent.createFullIdentBelow(ast); 804 isStatic = false; 805 } 806 else { 807 ident = FullIdent.createFullIdent(ast.getFirstChild() 808 .getNextSibling()); 809 isStatic = true; 810 } 811 812 // using set of IF instead of SWITCH to analyze Enum options to satisfy coverage. 813 // https://github.com/checkstyle/checkstyle/issues/1387 814 if (option == ImportOrderOption.TOP || option == ImportOrderOption.ABOVE) { 815 final boolean isStaticAndNotLastImport = isStatic && !lastImportStatic; 816 doVisitToken(ident, isStatic, isStaticAndNotLastImport, ast); 817 } 818 else if (option == ImportOrderOption.BOTTOM || option == ImportOrderOption.UNDER) { 819 final boolean isLastImportAndNonStatic = lastImportStatic && !isStatic; 820 doVisitToken(ident, isStatic, isLastImportAndNonStatic, ast); 821 } 822 else if (option == ImportOrderOption.INFLOW) { 823 // "previous" argument is useless here 824 doVisitToken(ident, isStatic, true, ast); 825 } 826 else { 827 throw new IllegalStateException( 828 "Unexpected option for static imports: " + option); 829 } 830 831 lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo(); 832 lastImportStatic = isStatic; 833 beforeFirstImport = false; 834 } 835 836 /** 837 * Shares processing... 838 * 839 * @param ident the import to process. 840 * @param isStatic whether the token is static or not. 841 * @param previous previous non-static but current is static (above), or 842 * previous static but current is non-static (under). 843 * @param ast node of the AST. 844 */ 845 private void doVisitToken(FullIdent ident, boolean isStatic, boolean previous, DetailAST ast) { 846 final String name = ident.getText(); 847 final int groupIdx = getGroupNumber(isStatic && staticImportsApart, name); 848 849 if (groupIdx > lastGroup) { 850 if (!beforeFirstImport 851 && ast.getLineNo() - lastImportLine < 2 852 && needSeparator(isStatic)) { 853 log(ast, MSG_SEPARATION, name); 854 } 855 } 856 else if (groupIdx == lastGroup) { 857 doVisitTokenInSameGroup(isStatic, previous, name, ast); 858 } 859 else { 860 log(ast, MSG_ORDERING, name); 861 } 862 if (isSeparatorInGroup(groupIdx, isStatic, ast.getLineNo())) { 863 log(ast, MSG_SEPARATED_IN_GROUP, name); 864 } 865 866 lastGroup = groupIdx; 867 lastImport = name; 868 } 869 870 /** 871 * Checks whether import groups should be separated. 872 * 873 * @param isStatic whether the token is static or not. 874 * @return true if imports groups should be separated. 875 */ 876 private boolean needSeparator(boolean isStatic) { 877 final boolean typeImportSeparator = !isStatic && separated; 878 final boolean staticImportSeparator; 879 if (staticImportsApart) { 880 staticImportSeparator = isStatic && separatedStaticGroups; 881 } 882 else { 883 staticImportSeparator = separated; 884 } 885 final boolean separatorBetween = isStatic != lastImportStatic 886 && (separated || separatedStaticGroups); 887 888 return typeImportSeparator || staticImportSeparator || separatorBetween; 889 } 890 891 /** 892 * Checks whether imports group separated internally. 893 * 894 * @param groupIdx group number. 895 * @param isStatic whether the token is static or not. 896 * @param line the line of the current import. 897 * @return true if imports group are separated internally. 898 */ 899 private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) { 900 final boolean inSameGroup = groupIdx == lastGroup; 901 return (inSameGroup || !needSeparator(isStatic)) && isSeparatorBeforeImport(line); 902 } 903 904 /** 905 * Checks whether there is any separator before current import. 906 * 907 * @param line the line of the current import. 908 * @return true if there is separator before current import which isn't the first import. 909 */ 910 private boolean isSeparatorBeforeImport(int line) { 911 return line - lastImportLine > 1; 912 } 913 914 /** 915 * Shares processing... 916 * 917 * @param isStatic whether the token is static or not. 918 * @param previous previous non-static but current is static (above), or 919 * previous static but current is non-static (under). 920 * @param name the name of the current import. 921 * @param ast node of the AST. 922 */ 923 private void doVisitTokenInSameGroup(boolean isStatic, 924 boolean previous, String name, DetailAST ast) { 925 if (ordered) { 926 if (option == ImportOrderOption.INFLOW) { 927 if (isWrongOrder(name, isStatic)) { 928 log(ast, MSG_ORDERING, name); 929 } 930 } 931 else { 932 final boolean shouldFireError = 933 // previous non-static but current is static (above) 934 // or 935 // previous static but current is non-static (under) 936 previous 937 || 938 // current and previous static or current and 939 // previous non-static 940 lastImportStatic == isStatic 941 && isWrongOrder(name, isStatic); 942 943 if (shouldFireError) { 944 log(ast, MSG_ORDERING, name); 945 } 946 } 947 } 948 } 949 950 /** 951 * Checks whether import name is in wrong order. 952 * 953 * @param name import name. 954 * @param isStatic whether it is a static import name. 955 * @return true if import name is in wrong order. 956 */ 957 private boolean isWrongOrder(String name, boolean isStatic) { 958 final boolean result; 959 if (isStatic) { 960 if (useContainerOrderingForStatic) { 961 result = compareContainerOrder(lastImport, name, caseSensitive) > 0; 962 } 963 else if (staticImportsApart) { 964 result = sortStaticImportsAlphabetically 965 && compare(lastImport, name, caseSensitive) > 0; 966 } 967 else { 968 result = compare(lastImport, name, caseSensitive) > 0; 969 } 970 } 971 else { 972 // out of lexicographic order 973 result = compare(lastImport, name, caseSensitive) > 0; 974 } 975 return result; 976 } 977 978 /** 979 * Compares two import strings. 980 * We first compare the container of the static import, container being the type enclosing 981 * the static element being imported. When this returns 0, we compare the qualified 982 * import name. For e.g. this is what is considered to be container names: 983 * <pre> 984 * import static HttpConstants.COLON => HttpConstants 985 * import static HttpHeaders.addHeader => HttpHeaders 986 * import static HttpHeaders.setHeader => HttpHeaders 987 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 988 * </pre> 989 * <p> 990 * According to this logic, HttpHeaders.Names would come after HttpHeaders. 991 * For more details, see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=473629#c3"> 992 * static imports comparison method</a> in Eclipse. 993 * </p> 994 * 995 * @param importName1 first import name 996 * @param importName2 second import name 997 * @param caseSensitive whether the comparison of fully qualified import names is 998 * case-sensitive 999 * @return the value {@code 0} if str1 is equal to str2; a value 1000 * less than {@code 0} if str is less than the str2 (container order 1001 * or lexicographical); and a value greater than {@code 0} if str1 is greater than str2 1002 * (container order or lexicographically) 1003 */ 1004 private static int compareContainerOrder(String importName1, String importName2, 1005 boolean caseSensitive) { 1006 final String container1 = getImportContainer(importName1); 1007 final String container2 = getImportContainer(importName2); 1008 final int compareContainersOrderResult; 1009 if (caseSensitive) { 1010 compareContainersOrderResult = container1.compareTo(container2); 1011 } 1012 else { 1013 compareContainersOrderResult = container1.compareToIgnoreCase(container2); 1014 } 1015 final int result; 1016 if (compareContainersOrderResult == 0) { 1017 result = compare(importName1, importName2, caseSensitive); 1018 } 1019 else { 1020 result = compareContainersOrderResult; 1021 } 1022 return result; 1023 } 1024 1025 /** 1026 * Extracts import container name from fully qualified import name. 1027 * An import container name is the type which encloses the static element being imported. 1028 * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names: 1029 * <pre> 1030 * import static HttpConstants.COLON => HttpConstants 1031 * import static HttpHeaders.addHeader => HttpHeaders 1032 * import static HttpHeaders.setHeader => HttpHeaders 1033 * import static HttpHeaders.Names.DATE => HttpHeaders.Names 1034 * </pre> 1035 * 1036 * @param qualifiedImportName fully qualified import name. 1037 * @return import container name. 1038 */ 1039 private static String getImportContainer(String qualifiedImportName) { 1040 final int lastDotIndex = qualifiedImportName.lastIndexOf('.'); 1041 return qualifiedImportName.substring(0, lastDotIndex); 1042 } 1043 1044 /** 1045 * Finds out what group the specified import belongs to. 1046 * 1047 * @param isStatic whether the token is static or not. 1048 * @param name the import name to find. 1049 * @return group number for given import name. 1050 */ 1051 private int getGroupNumber(boolean isStatic, String name) { 1052 final Pattern[] patterns; 1053 if (isStatic) { 1054 patterns = staticGroups; 1055 } 1056 else { 1057 patterns = groups; 1058 } 1059 1060 int number = getGroupNumber(patterns, name); 1061 1062 if (isStatic && option == ImportOrderOption.BOTTOM) { 1063 number += groups.length + 1; 1064 } 1065 else if (!isStatic && option == ImportOrderOption.TOP) { 1066 number += staticGroups.length + 1; 1067 } 1068 return number; 1069 } 1070 1071 /** 1072 * Finds out what group the specified import belongs to. 1073 * 1074 * @param patterns groups to check. 1075 * @param name the import name to find. 1076 * @return group number for given import name. 1077 */ 1078 private static int getGroupNumber(Pattern[] patterns, String name) { 1079 int bestIndex = patterns.length; 1080 int bestEnd = -1; 1081 int bestPos = Integer.MAX_VALUE; 1082 1083 // find out what group this belongs in 1084 // loop over patterns and get index 1085 for (int i = 0; i < patterns.length; i++) { 1086 final Matcher matcher = patterns[i].matcher(name); 1087 if (matcher.find()) { 1088 if (matcher.start() < bestPos) { 1089 bestIndex = i; 1090 bestEnd = matcher.end(); 1091 bestPos = matcher.start(); 1092 } 1093 else if (matcher.start() == bestPos && matcher.end() > bestEnd) { 1094 bestIndex = i; 1095 bestEnd = matcher.end(); 1096 } 1097 } 1098 } 1099 return bestIndex; 1100 } 1101 1102 /** 1103 * Compares two strings. 1104 * 1105 * @param string1 1106 * the first string 1107 * @param string2 1108 * the second string 1109 * @param caseSensitive 1110 * whether the comparison is case-sensitive 1111 * @return the value {@code 0} if string1 is equal to string2; a value 1112 * less than {@code 0} if string1 is lexicographically less 1113 * than the string2; and a value greater than {@code 0} if 1114 * string1 is lexicographically greater than string2 1115 */ 1116 private static int compare(String string1, String string2, 1117 boolean caseSensitive) { 1118 final int result; 1119 if (caseSensitive) { 1120 result = string1.compareTo(string2); 1121 } 1122 else { 1123 result = string1.compareToIgnoreCase(string2); 1124 } 1125 1126 return result; 1127 } 1128 1129 /** 1130 * Compiles the list of package groups and the order they should occur in the file. 1131 * 1132 * @param packageGroups a comma-separated list of package names/prefixes. 1133 * @return array of compiled patterns. 1134 * @throws IllegalArgumentException if any of the package groups are not valid. 1135 */ 1136 private static Pattern[] compilePatterns(String... packageGroups) { 1137 final Pattern[] patterns = new Pattern[packageGroups.length]; 1138 1139 for (int i = 0; i < packageGroups.length; i++) { 1140 String pkg = packageGroups[i]; 1141 final Pattern grp; 1142 1143 // if the pkg name is the wildcard, make it match zero chars 1144 // from any name, so it will always be used as last resort. 1145 if (WILDCARD_GROUP_NAME.equals(pkg)) { 1146 // matches any package 1147 grp = Pattern.compile(""); 1148 } 1149 else if (CommonUtil.startsWithChar(pkg, '/')) { 1150 if (!CommonUtil.endsWithChar(pkg, '/')) { 1151 throw new IllegalArgumentException("Invalid group: " + pkg); 1152 } 1153 pkg = pkg.substring(1, pkg.length() - 1); 1154 grp = Pattern.compile(pkg); 1155 } 1156 else { 1157 final StringBuilder pkgBuilder = new StringBuilder(pkg); 1158 if (!CommonUtil.endsWithChar(pkg, '.')) { 1159 pkgBuilder.append('.'); 1160 } 1161 grp = Pattern.compile("^" + Pattern.quote(pkgBuilder.toString())); 1162 } 1163 1164 patterns[i] = grp; 1165 } 1166 return patterns; 1167 } 1168 1169}