Coverage Report - com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
ImportOrderCheck
100%
144/144
100%
120/120
3.364
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.imports;
 21  
 
 22  
 import java.util.Locale;
 23  
 import java.util.regex.Matcher;
 24  
 import java.util.regex.Pattern;
 25  
 
 26  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 28  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 29  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 30  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 31  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 32  
 
 33  
 /**
 34  
  * <ul>
 35  
  * <li>groups imports: ensures that groups of imports come in a specific order
 36  
  * (e.g., java. comes first, javax. comes second, then everything else)</li>
 37  
  * <li>adds a separation between groups : ensures that a blank line sit between
 38  
  * each group</li>
 39  
  * <li>import groups aren't separated internally: ensures that
 40  
  * each group aren't separated internally by blank line or comment</li>
 41  
  * <li>sorts imports inside each group: ensures that imports within each group
 42  
  * are in lexicographic order</li>
 43  
  * <li>sorts according to case: ensures that the comparison between import is
 44  
  * case sensitive</li>
 45  
  * <li>groups static imports: ensures that static imports are at the top (or the
 46  
  * bottom) of all the imports, or above (or under) each group, or are treated
 47  
  * like non static imports (@see {@link ImportOrderOption}</li>
 48  
  * </ul>.
 49  
  *
 50  
  * <pre>
 51  
  * Properties:
 52  
  * </pre>
 53  
  * <table summary="Properties" border="1">
 54  
  *   <tr><th>name</th><th>Description</th><th>type</th><th>default value</th></tr>
 55  
  *   <tr><td>option</td><td>policy on the relative order between regular imports and static
 56  
  *       imports</td><td>{@link ImportOrderOption}</td><td>under</td></tr>
 57  
  *   <tr><td>groups</td><td>list of imports groups (every group identified either by a common
 58  
  *       prefix string, or by a regular expression enclosed in forward slashes (e.g. /regexp/)</td>
 59  
  *       <td>list of strings</td><td>empty list</td></tr>
 60  
  *   <tr><td>ordered</td><td>whether imports within group should be sorted</td>
 61  
  *       <td>Boolean</td><td>true</td></tr>
 62  
  *   <tr><td>separated</td><td>whether imports groups should be separated by, at least,
 63  
  *       one blank line or comment and aren't separated internally
 64  
  *       </td><td>Boolean</td><td>false</td></tr>
 65  
  *   <tr><td>caseSensitive</td><td>whether string comparison should be case sensitive or not.
 66  
  *       Case sensitive sorting is in ASCII sort order</td><td>Boolean</td><td>true</td></tr>
 67  
  *   <tr><td>sortStaticImportsAlphabetically</td><td>whether static imports grouped by top or
 68  
  *       bottom option are sorted alphabetically or not</td><td>Boolean</td><td>false</td></tr>
 69  
  *   <tr><td>useContainerOrderingForStatic</td><td>whether to use container ordering
 70  
  *       (Eclipse IDE term) for static imports or not</td><td>Boolean</td><td>false</td></tr>
 71  
  * </table>
 72  
  *
 73  
  * <p>
 74  
  * Example:
 75  
  * </p>
 76  
  * <p>To configure the check so that it matches default Eclipse formatter configuration
 77  
  *    (tested on Kepler, Luna and Mars):</p>
 78  
  * <ul>
 79  
  *     <li>group of static imports is on the top</li>
 80  
  *     <li>groups of non-static imports: &quot;java&quot; then &quot;javax&quot;
 81  
  *         packages first, then &quot;org&quot; and then all other imports</li>
 82  
  *     <li>imports will be sorted in the groups</li>
 83  
  *     <li>groups are separated by, at least, one blank line and aren't separated internally</li>
 84  
  * </ul>
 85  
  *
 86  
  * <pre>
 87  
  * &lt;module name=&quot;ImportOrder&quot;&gt;
 88  
  *    &lt;property name=&quot;groups&quot; value=&quot;/^javax?\./,org&quot;/&gt;
 89  
  *    &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
 90  
  *    &lt;property name=&quot;separated&quot; value=&quot;true&quot;/&gt;
 91  
  *    &lt;property name=&quot;option&quot; value=&quot;above&quot;/&gt;
 92  
  *    &lt;property name=&quot;sortStaticImportsAlphabetically&quot; value=&quot;true&quot;/&gt;
 93  
  * &lt;/module&gt;
 94  
  * </pre>
 95  
  *
 96  
  * <p>To configure the check so that it matches default IntelliJ IDEA formatter configuration
 97  
  *    (tested on v14):</p>
 98  
  * <ul>
 99  
  *     <li>group of static imports is on the bottom</li>
 100  
  *     <li>groups of non-static imports: all imports except of &quot;javax&quot; and
 101  
  *         &quot;java&quot;, then &quot;javax&quot; and &quot;java&quot;</li>
 102  
  *     <li>imports will be sorted in the groups</li>
 103  
  *     <li>groups are separated by, at least, one blank line and aren't separated internally</li>
 104  
  * </ul>
 105  
  *
 106  
  *         <p>
 107  
  *         Note: &quot;separated&quot; option is disabled because IDEA default has blank line
 108  
  *         between &quot;java&quot; and static imports, and no blank line between
 109  
  *         &quot;javax&quot; and &quot;java&quot;
 110  
  *         </p>
 111  
  *
 112  
  * <pre>
 113  
  * &lt;module name=&quot;ImportOrder&quot;&gt;
 114  
  *     &lt;property name=&quot;groups&quot; value=&quot;*,javax,java&quot;/&gt;
 115  
  *     &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
 116  
  *     &lt;property name=&quot;separated&quot; value=&quot;false&quot;/&gt;
 117  
  *     &lt;property name=&quot;option&quot; value=&quot;bottom&quot;/&gt;
 118  
  *     &lt;property name=&quot;sortStaticImportsAlphabetically&quot; value=&quot;true&quot;/&gt;
 119  
  * &lt;/module&gt;
 120  
  * </pre>
 121  
  *
 122  
  * <p>To configure the check so that it matches default NetBeans formatter configuration
 123  
  *    (tested on v8):</p>
 124  
  * <ul>
 125  
  *     <li>groups of non-static imports are not defined, all imports will be sorted
 126  
  *         as a one group</li>
 127  
  *     <li>static imports are not separated, they will be sorted along with other imports</li>
 128  
  * </ul>
 129  
  *
 130  
  * <pre>
 131  
  * &lt;module name=&quot;ImportOrder&quot;&gt;
 132  
  *     &lt;property name=&quot;option&quot; value=&quot;inflow&quot;/&gt;
 133  
  * &lt;/module&gt;
 134  
  * </pre>
 135  
  *
 136  
  * <p>
 137  
  * Group descriptions enclosed in slashes are interpreted as regular
 138  
  * expressions. If multiple groups match, the one matching a longer
 139  
  * substring of the imported name will take precedence, with ties
 140  
  * broken first in favor of earlier matches and finally in favor of
 141  
  * the first matching group.
 142  
  * </p>
 143  
  *
 144  
  * <p>
 145  
  * There is always a wildcard group to which everything not in a named group
 146  
  * belongs. If an import does not match a named group, the group belongs to
 147  
  * this wildcard group. The wildcard group position can be specified using the
 148  
  * {@code *} character.
 149  
  * </p>
 150  
  *
 151  
  * <p>Check also has on option making it more flexible:
 152  
  * <b>sortStaticImportsAlphabetically</b> - sets whether static imports grouped by
 153  
  * <b>top</b> or <b>bottom</b> option should be sorted alphabetically or
 154  
  * not, default value is <b>false</b>. It is applied to static imports grouped
 155  
  * with <b>top</b> or <b>bottom</b> options.<br>
 156  
  * This option is helping in reconciling of this Check and other tools like
 157  
  * Eclipse's Organize Imports feature.
 158  
  * </p>
 159  
  * <p>
 160  
  * To configure the Check allows static imports grouped to the <b>top</b>
 161  
  * being sorted alphabetically:
 162  
  * </p>
 163  
  *
 164  
  * <pre>
 165  
  * {@code
 166  
  * import static java.lang.Math.abs;
 167  
  * import static org.abego.treelayout.Configuration.AlignmentInLevel; // OK, alphabetical order
 168  
  *
 169  
  * import org.abego.*;
 170  
  *
 171  
  * import java.util.Set;
 172  
  *
 173  
  * public class SomeClass { ... }
 174  
  * }
 175  
  * </pre>
 176  
  *
 177  
  *
 178  
  * @author Bill Schneider
 179  
  * @author o_sukhodolsky
 180  
  * @author David DIDIER
 181  
  * @author Steve McKay
 182  
  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
 183  
  * @author Andrei Selkin
 184  
  */
 185  
 @FileStatefulCheck
 186  58
 public class ImportOrderCheck
 187  
     extends AbstractCheck {
 188  
 
 189  
     /**
 190  
      * A key is pointing to the warning message text in "messages.properties"
 191  
      * file.
 192  
      */
 193  
     public static final String MSG_SEPARATION = "import.separation";
 194  
 
 195  
     /**
 196  
      * A key is pointing to the warning message text in "messages.properties"
 197  
      * file.
 198  
      */
 199  
     public static final String MSG_ORDERING = "import.ordering";
 200  
 
 201  
     /**
 202  
      * A key is pointing to the warning message text in "messages.properties"
 203  
      * file.
 204  
      */
 205  
     public static final String MSG_SEPARATED_IN_GROUP = "import.groups.separated.internally";
 206  
 
 207  
     /** The special wildcard that catches all remaining groups. */
 208  
     private static final String WILDCARD_GROUP_NAME = "*";
 209  
 
 210  
     /** Empty array of pattern type needed to initialize check. */
 211  2
     private static final Pattern[] EMPTY_PATTERN_ARRAY = new Pattern[0];
 212  
 
 213  
     /** List of import groups specified by the user. */
 214  58
     private Pattern[] groups = EMPTY_PATTERN_ARRAY;
 215  
     /** Require imports in group be separated. */
 216  
     private boolean separated;
 217  
     /** Require imports in group. */
 218  58
     private boolean ordered = true;
 219  
     /** Should comparison be case sensitive. */
 220  58
     private boolean caseSensitive = true;
 221  
 
 222  
     /** Last imported group. */
 223  
     private int lastGroup;
 224  
     /** Line number of last import. */
 225  
     private int lastImportLine;
 226  
     /** Name of last import. */
 227  
     private String lastImport;
 228  
     /** If last import was static. */
 229  
     private boolean lastImportStatic;
 230  
     /** Whether there was any imports. */
 231  
     private boolean beforeFirstImport;
 232  
     /** Whether static imports should be sorted alphabetically or not. */
 233  
     private boolean sortStaticImportsAlphabetically;
 234  
     /** Whether to use container ordering (Eclipse IDE term) for static imports or not. */
 235  
     private boolean useContainerOrderingForStatic;
 236  
 
 237  
     /** The policy to enforce. */
 238  58
     private ImportOrderOption option = ImportOrderOption.UNDER;
 239  
 
 240  
     /**
 241  
      * Set the option to enforce.
 242  
      * @param optionStr string to decode option from
 243  
      * @throws IllegalArgumentException if unable to decode
 244  
      */
 245  
     public void setOption(String optionStr) {
 246  
         try {
 247  36
             option = ImportOrderOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
 248  
         }
 249  1
         catch (IllegalArgumentException iae) {
 250  1
             throw new IllegalArgumentException("unable to parse " + optionStr, iae);
 251  35
         }
 252  35
     }
 253  
 
 254  
     /**
 255  
      * Sets the list of package groups and the order they should occur in the
 256  
      * file.
 257  
      *
 258  
      * @param packageGroups a comma-separated list of package names/prefixes.
 259  
      */
 260  
     public void setGroups(String... packageGroups) {
 261  36
         groups = new Pattern[packageGroups.length];
 262  
 
 263  128
         for (int i = 0; i < packageGroups.length; i++) {
 264  93
             String pkg = packageGroups[i];
 265  
             final Pattern grp;
 266  
 
 267  
             // if the pkg name is the wildcard, make it match zero chars
 268  
             // from any name, so it will always be used as last resort.
 269  93
             if (WILDCARD_GROUP_NAME.equals(pkg)) {
 270  
                 // matches any package
 271  2
                 grp = Pattern.compile("");
 272  
             }
 273  91
             else if (CommonUtils.startsWithChar(pkg, '/')) {
 274  16
                 if (!CommonUtils.endsWithChar(pkg, '/')) {
 275  1
                     throw new IllegalArgumentException("Invalid group");
 276  
                 }
 277  15
                 pkg = pkg.substring(1, pkg.length() - 1);
 278  15
                 grp = Pattern.compile(pkg);
 279  
             }
 280  
             else {
 281  75
                 final StringBuilder pkgBuilder = new StringBuilder(pkg);
 282  75
                 if (!CommonUtils.endsWithChar(pkg, '.')) {
 283  74
                     pkgBuilder.append('.');
 284  
                 }
 285  75
                 grp = Pattern.compile("^" + Pattern.quote(pkgBuilder.toString()));
 286  
             }
 287  
 
 288  92
             groups[i] = grp;
 289  
         }
 290  35
     }
 291  
 
 292  
     /**
 293  
      * Sets whether or not imports should be ordered within any one group of
 294  
      * imports.
 295  
      *
 296  
      * @param ordered
 297  
      *            whether lexicographic ordering of imports within a group
 298  
      *            required or not.
 299  
      */
 300  
     public void setOrdered(boolean ordered) {
 301  17
         this.ordered = ordered;
 302  17
     }
 303  
 
 304  
     /**
 305  
      * Sets whether or not groups of imports must be separated from one another
 306  
      * by at least one blank line.
 307  
      *
 308  
      * @param separated
 309  
      *            whether groups should be separated by oen blank line.
 310  
      */
 311  
     public void setSeparated(boolean separated) {
 312  18
         this.separated = separated;
 313  18
     }
 314  
 
 315  
     /**
 316  
      * Sets whether string comparison should be case sensitive or not.
 317  
      *
 318  
      * @param caseSensitive
 319  
      *            whether string comparison should be case sensitive.
 320  
      */
 321  
     public void setCaseSensitive(boolean caseSensitive) {
 322  6
         this.caseSensitive = caseSensitive;
 323  6
     }
 324  
 
 325  
     /**
 326  
      * Sets whether static imports (when grouped using 'top' and 'bottom' option)
 327  
      * are sorted alphabetically or according to the package groupings.
 328  
      * @param sortAlphabetically true or false.
 329  
      */
 330  
     public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) {
 331  21
         sortStaticImportsAlphabetically = sortAlphabetically;
 332  21
     }
 333  
 
 334  
     /**
 335  
      * Sets whether to use container ordering (Eclipse IDE term) for static imports or not.
 336  
      * @param useContainerOrdering whether to use container ordering for static imports or not.
 337  
      */
 338  
     public void setUseContainerOrderingForStatic(boolean useContainerOrdering) {
 339  7
         useContainerOrderingForStatic = useContainerOrdering;
 340  7
     }
 341  
 
 342  
     @Override
 343  
     public int[] getDefaultTokens() {
 344  104
         return getAcceptableTokens();
 345  
     }
 346  
 
 347  
     @Override
 348  
     public int[] getAcceptableTokens() {
 349  110
         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
 350  
     }
 351  
 
 352  
     @Override
 353  
     public int[] getRequiredTokens() {
 354  107
         return new int[] {TokenTypes.IMPORT};
 355  
     }
 356  
 
 357  
     @Override
 358  
     public void beginTree(DetailAST rootAST) {
 359  43
         lastGroup = Integer.MIN_VALUE;
 360  43
         lastImportLine = Integer.MIN_VALUE;
 361  43
         lastImport = "";
 362  43
         lastImportStatic = false;
 363  43
         beforeFirstImport = true;
 364  43
     }
 365  
 
 366  
     // -@cs[CyclomaticComplexity] SWITCH was transformed into IF-ELSE.
 367  
     @Override
 368  
     public void visitToken(DetailAST ast) {
 369  
         final FullIdent ident;
 370  
         final boolean isStatic;
 371  
 
 372  283
         if (ast.getType() == TokenTypes.IMPORT) {
 373  165
             ident = FullIdent.createFullIdentBelow(ast);
 374  165
             isStatic = false;
 375  
         }
 376  
         else {
 377  236
             ident = FullIdent.createFullIdent(ast.getFirstChild()
 378  118
                     .getNextSibling());
 379  118
             isStatic = true;
 380  
         }
 381  
 
 382  283
         final boolean isStaticAndNotLastImport = isStatic && !lastImportStatic;
 383  283
         final boolean isLastImportAndNonStatic = lastImportStatic && !isStatic;
 384  
 
 385  
         // using set of IF instead of SWITCH to analyze Enum options to satisfy coverage.
 386  
         // https://github.com/checkstyle/checkstyle/issues/1387
 387  283
         if (option == ImportOrderOption.TOP) {
 388  
 
 389  100
             if (isLastImportAndNonStatic) {
 390  9
                 lastGroup = Integer.MIN_VALUE;
 391  9
                 lastImport = "";
 392  
             }
 393  100
             doVisitToken(ident, isStatic, isStaticAndNotLastImport);
 394  
 
 395  
         }
 396  183
         else if (option == ImportOrderOption.BOTTOM) {
 397  
 
 398  37
             if (isStaticAndNotLastImport) {
 399  7
                 lastGroup = Integer.MIN_VALUE;
 400  7
                 lastImport = "";
 401  
             }
 402  37
             doVisitToken(ident, isStatic, isLastImportAndNonStatic);
 403  
 
 404  
         }
 405  146
         else if (option == ImportOrderOption.ABOVE) {
 406  
             // previous non-static but current is static
 407  22
             doVisitToken(ident, isStatic, isStaticAndNotLastImport);
 408  
 
 409  
         }
 410  124
         else if (option == ImportOrderOption.UNDER) {
 411  108
             doVisitToken(ident, isStatic, isLastImportAndNonStatic);
 412  
 
 413  
         }
 414  16
         else if (option == ImportOrderOption.INFLOW) {
 415  
             // "previous" argument is useless here
 416  15
             doVisitToken(ident, isStatic, true);
 417  
 
 418  
         }
 419  
         else {
 420  1
             throw new IllegalStateException(
 421  
                     "Unexpected option for static imports: " + option);
 422  
         }
 423  
 
 424  282
         lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo();
 425  282
         lastImportStatic = isStatic;
 426  282
         beforeFirstImport = false;
 427  282
     }
 428  
 
 429  
     /**
 430  
      * Shares processing...
 431  
      *
 432  
      * @param ident the import to process.
 433  
      * @param isStatic whether the token is static or not.
 434  
      * @param previous previous non-static but current is static (above), or
 435  
      *                  previous static but current is non-static (under).
 436  
      */
 437  
     private void doVisitToken(FullIdent ident, boolean isStatic,
 438  
             boolean previous) {
 439  282
         final String name = ident.getText();
 440  282
         final int groupIdx = getGroupNumber(name);
 441  282
         final int line = ident.getLineNo();
 442  
 
 443  282
         if (isInSameGroup(groupIdx, isStatic)) {
 444  178
             doVisitTokenInSameGroup(isStatic, previous, name, line);
 445  
         }
 446  104
         else if (groupIdx > lastGroup) {
 447  91
             if (!beforeFirstImport && separated && line - lastImportLine < 2) {
 448  4
                 log(line, MSG_SEPARATION, name);
 449  
             }
 450  
         }
 451  
         else {
 452  13
             log(line, MSG_ORDERING, name);
 453  
         }
 454  282
         if (isSeparatorInGroup(groupIdx, isStatic, line)) {
 455  41
             log(line, MSG_SEPARATED_IN_GROUP, name);
 456  
         }
 457  
 
 458  282
         lastGroup = groupIdx;
 459  282
         lastImport = name;
 460  282
     }
 461  
 
 462  
     /**
 463  
      * Checks whether imports group separated internally.
 464  
      * @param groupIdx group number.
 465  
      * @param isStatic whether the token is static or not.
 466  
      * @param line the line of the current import.
 467  
      * @return true if imports group are separated internally.
 468  
      */
 469  
     private boolean isSeparatorInGroup(int groupIdx, boolean isStatic, int line) {
 470  282
         final boolean inSameGroup = isInSameGroup(groupIdx, isStatic);
 471  282
         return (!separated || inSameGroup) && isSeparatorBeforeImport(line);
 472  
     }
 473  
 
 474  
     /**
 475  
      * Checks whether there is any separator before current import.
 476  
      * @param line the line of the current import.
 477  
      * @return true if there is separator before current import which isn't the first import.
 478  
      */
 479  
     private boolean isSeparatorBeforeImport(int line) {
 480  247
         return !beforeFirstImport && line - lastImportLine > 1;
 481  
     }
 482  
 
 483  
     /**
 484  
      * Checks whether imports are in same group.
 485  
      * @param groupIdx group number.
 486  
      * @param isStatic whether the token is static or not.
 487  
      * @return true if imports are in same group.
 488  
      */
 489  
     private boolean isInSameGroup(int groupIdx, boolean isStatic) {
 490  564
         final boolean isStaticImportGroupIndependent =
 491  
             option == ImportOrderOption.TOP || option == ImportOrderOption.BOTTOM;
 492  
         final boolean result;
 493  564
         if (isStaticImportGroupIndependent) {
 494  274
             result = isStatic && lastImportStatic
 495  
                 || groupIdx == lastGroup && isStatic == lastImportStatic;
 496  
         }
 497  
         else {
 498  290
             result = groupIdx == lastGroup;
 499  
         }
 500  564
         return result;
 501  
     }
 502  
 
 503  
     /**
 504  
      * Shares processing...
 505  
      *
 506  
      * @param isStatic whether the token is static or not.
 507  
      * @param previous previous non-static but current is static (above), or
 508  
      *     previous static but current is non-static (under).
 509  
      * @param name the name of the current import.
 510  
      * @param line the line of the current import.
 511  
      */
 512  
     private void doVisitTokenInSameGroup(boolean isStatic,
 513  
             boolean previous, String name, int line) {
 514  178
         if (ordered) {
 515  157
             if (option == ImportOrderOption.INFLOW) {
 516  14
                 if (isWrongOrder(name, isStatic)) {
 517  6
                     log(line, MSG_ORDERING, name);
 518  
                 }
 519  
             }
 520  
             else {
 521  143
                 final boolean shouldFireError =
 522  
                     // previous non-static but current is static (above)
 523  
                     // or
 524  
                     // previous static but current is non-static (under)
 525  
                     previous
 526  
                         ||
 527  
                         // current and previous static or current and
 528  
                         // previous non-static
 529  
                         lastImportStatic == isStatic
 530  127
                     && isWrongOrder(name, isStatic);
 531  
 
 532  143
                 if (shouldFireError) {
 533  25
                     log(line, MSG_ORDERING, name);
 534  
                 }
 535  
             }
 536  
         }
 537  178
     }
 538  
 
 539  
     /**
 540  
      * Checks whether import name is in wrong order.
 541  
      * @param name import name.
 542  
      * @param isStatic whether it is a static import name.
 543  
      * @return true if import name is in wrong order.
 544  
      */
 545  
     private boolean isWrongOrder(String name, boolean isStatic) {
 546  
         final boolean result;
 547  141
         if (isStatic) {
 548  67
             if (useContainerOrderingForStatic) {
 549  14
                 result = compareContainerOrder(lastImport, name, caseSensitive) >= 0;
 550  
             }
 551  53
             else if (option == ImportOrderOption.TOP
 552  
                 || option == ImportOrderOption.BOTTOM) {
 553  38
                 result = sortStaticImportsAlphabetically
 554  21
                     && compare(lastImport, name, caseSensitive) >= 0;
 555  
             }
 556  
             else {
 557  15
                 result = compare(lastImport, name, caseSensitive) >= 0;
 558  
             }
 559  
         }
 560  
         else {
 561  
             // out of lexicographic order
 562  74
             result = compare(lastImport, name, caseSensitive) >= 0;
 563  
         }
 564  141
         return result;
 565  
     }
 566  
 
 567  
     /**
 568  
      * Compares two import strings.
 569  
      * We first compare the container of the static import, container being the type enclosing
 570  
      * the static element being imported. When this returns 0, we compare the qualified
 571  
      * import name. For e.g. this is what is considered to be container names:
 572  
      * <p>
 573  
      * import static HttpConstants.COLON     => HttpConstants
 574  
      * import static HttpHeaders.addHeader   => HttpHeaders
 575  
      * import static HttpHeaders.setHeader   => HttpHeaders
 576  
      * import static HttpHeaders.Names.DATE  => HttpHeaders.Names
 577  
      * </p>
 578  
      * <p>
 579  
      * According to this logic, HttpHeaders.Names would come after HttpHeaders.
 580  
      *
 581  
      * For more details, see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=473629#c3">
 582  
      * static imports comparison method</a> in Eclipse.
 583  
      * </p>
 584  
      *
 585  
      * @param importName1 first import name.
 586  
      * @param importName2 second import name.
 587  
      * @param caseSensitive whether the comparison of fully qualified import names is case
 588  
      *                      sensitive.
 589  
      * @return the value {@code 0} if str1 is equal to str2; a value
 590  
      *         less than {@code 0} if str is less than the str2 (container order
 591  
      *         or lexicographical); and a value greater than {@code 0} if str1 is greater than str2
 592  
      *         (container order or lexicographically).
 593  
      */
 594  
     private static int compareContainerOrder(String importName1, String importName2,
 595  
                                              boolean caseSensitive) {
 596  14
         final String container1 = getImportContainer(importName1);
 597  14
         final String container2 = getImportContainer(importName2);
 598  
         final int compareContainersOrderResult;
 599  14
         if (caseSensitive) {
 600  6
             compareContainersOrderResult = container1.compareTo(container2);
 601  
         }
 602  
         else {
 603  8
             compareContainersOrderResult = container1.compareToIgnoreCase(container2);
 604  
         }
 605  
         final int result;
 606  14
         if (compareContainersOrderResult == 0) {
 607  8
             result = compare(importName1, importName2, caseSensitive);
 608  
         }
 609  
         else {
 610  6
             result = compareContainersOrderResult;
 611  
         }
 612  14
         return result;
 613  
     }
 614  
 
 615  
     /**
 616  
      * Extracts import container name from fully qualified import name.
 617  
      * An import container name is the type which encloses the static element being imported.
 618  
      * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names:
 619  
      * <p>
 620  
      * import static HttpConstants.COLON     => HttpConstants
 621  
      * import static HttpHeaders.addHeader   => HttpHeaders
 622  
      * import static HttpHeaders.setHeader   => HttpHeaders
 623  
      * import static HttpHeaders.Names.DATE  => HttpHeaders.Names
 624  
      * </p>
 625  
      * @param qualifiedImportName fully qualified import name.
 626  
      * @return import container name.
 627  
      */
 628  
     private static String getImportContainer(String qualifiedImportName) {
 629  28
         final int lastDotIndex = qualifiedImportName.lastIndexOf('.');
 630  28
         return qualifiedImportName.substring(0, lastDotIndex);
 631  
     }
 632  
 
 633  
     /**
 634  
      * Finds out what group the specified import belongs to.
 635  
      *
 636  
      * @param name the import name to find.
 637  
      * @return group number for given import name.
 638  
      */
 639  
     private int getGroupNumber(String name) {
 640  282
         int bestIndex = groups.length;
 641  282
         int bestEnd = -1;
 642  282
         int bestPos = Integer.MAX_VALUE;
 643  
 
 644  
         // find out what group this belongs in
 645  
         // loop over groups and get index
 646  794
         for (int i = 0; i < groups.length; i++) {
 647  512
             final Matcher matcher = groups[i].matcher(name);
 648  512
             if (matcher.find()) {
 649  167
                 if (matcher.start() < bestPos) {
 650  156
                     bestIndex = i;
 651  156
                     bestEnd = matcher.end();
 652  156
                     bestPos = matcher.start();
 653  
                 }
 654  11
                 else if (matcher.start() == bestPos && matcher.end() > bestEnd) {
 655  6
                     bestIndex = i;
 656  6
                     bestEnd = matcher.end();
 657  
                 }
 658  
             }
 659  
         }
 660  
 
 661  282
         return bestIndex;
 662  
     }
 663  
 
 664  
     /**
 665  
      * Compares two strings.
 666  
      *
 667  
      * @param string1
 668  
      *            the first string.
 669  
      * @param string2
 670  
      *            the second string.
 671  
      * @param caseSensitive
 672  
      *            whether the comparison is case sensitive.
 673  
      * @return the value {@code 0} if string1 is equal to string2; a value
 674  
      *         less than {@code 0} if string1 is lexicographically less
 675  
      *         than the string2; and a value greater than {@code 0} if
 676  
      *         string1 is lexicographically greater than string2.
 677  
      */
 678  
     private static int compare(String string1, String string2,
 679  
             boolean caseSensitive) {
 680  
         final int result;
 681  118
         if (caseSensitive) {
 682  106
             result = string1.compareTo(string2);
 683  
         }
 684  
         else {
 685  12
             result = string1.compareToIgnoreCase(string2);
 686  
         }
 687  
 
 688  118
         return result;
 689  
     }
 690  
 }