Coverage Report - com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
ImportOrderCheck
98%
93/94
93%
67/72
4.167
ImportOrderCheck$1
100%
1/1
N/A
4.167
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
 23  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 24  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 25  
 import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck;
 26  
 import java.util.regex.Matcher;
 27  
 import java.util.regex.Pattern;
 28  
 
 29  
 /**
 30  
  * <ul>
 31  
  * <li>groups imports: ensures that groups of imports come in a specific order
 32  
  * (e.g., java. comes first, javax. comes second, then everything else)</li>
 33  
  * <li>adds a separation between groups : ensures that a blank line sit between
 34  
  * each group</li>
 35  
  * <li>sorts imports inside each group: ensures that imports within each group
 36  
  * are in lexicographic order</li>
 37  
  * <li>sorts according to case: ensures that the comparison between import is
 38  
  * case sensitive</li>
 39  
  * <li>groups static imports: ensures that static imports are at the top (or the
 40  
  * bottom) of all the imports, or above (or under) each group, or are treated
 41  
  * like non static imports (@see {@link ImportOrderOption}</li>
 42  
  * </ul>
 43  
  *
 44  
  * <p>
 45  
  * Example:
 46  
  * </p>
 47  
  *
 48  
  * <pre>
 49  
  *  &lt;module name=&quot;ImportOrder&quot;&gt;
 50  
  *    &lt;property name=&quot;groups&quot; value=&quot;java,javax&quot;/&gt;
 51  
  *    &lt;property name=&quot;ordered&quot; value=&quot;true&quot;/&gt;
 52  
  *    &lt;property name=&quot;caseSensitive&quot; value=&quot;false&quot;/&gt;
 53  
  *    &lt;property name=&quot;option&quot; value=&quot;above&quot;/&gt;
 54  
  *  &lt;/module&gt;
 55  
  * </pre>
 56  
  *
 57  
  * <p>
 58  
  * Group descriptions enclosed in slashes are interpreted as regular
 59  
  * expressions. If multiple groups match, the one matching a longer
 60  
  * substring of the imported name will take precedence, with ties
 61  
  * broken first in favor of earlier matches and finally in favor of
 62  
  * the first matching group.
 63  
  * </p>
 64  
  *
 65  
  * <p>
 66  
  * There is always a wildcard group to which everything not in a named group
 67  
  * belongs. If an import does not match a named group, the group belongs to
 68  
  * this wildcard group. The wildcard group position can be specified using the
 69  
  * {@code *} character.
 70  
  * </p>
 71  
  *
 72  
  * <p>
 73  
  * Defaults:
 74  
  * </p>
 75  
  * <ul>
 76  
  * <li>import groups: none</li>
 77  
  * <li>separation: false</li>
 78  
  * <li>ordered: true</li>
 79  
  * <li>case sensitive: true</li>
 80  
  * <li>static import: under</li>
 81  
  * </ul>
 82  
  *
 83  
  * <p>
 84  
  * Compatible with Java 1.5 source.
 85  
  * </p>
 86  
  *
 87  
  * @author Bill Schneider
 88  
  * @author o_sukhodolsky
 89  
  * @author David DIDIER
 90  
  * @author Steve McKay
 91  
  */
 92  
 public class ImportOrderCheck
 93  
     extends AbstractOptionCheck<ImportOrderOption>
 94  
 {
 95  
 
 96  
     /** the special wildcard that catches all remaining groups. */
 97  
     private static final String WILDCARD_GROUP_NAME = "*";
 98  
 
 99  
     /** List of import groups specified by the user. */
 100  14
     private Pattern[] mGroups = new Pattern[0];
 101  
     /** Require imports in group be separated. */
 102  
     private boolean mSeparated;
 103  
     /** Require imports in group. */
 104  14
     private boolean mOrdered = true;
 105  
     /** Should comparison be case sensitive. */
 106  14
     private boolean mCaseSensitive = true;
 107  
 
 108  
     /** Last imported group. */
 109  
     private int mLastGroup;
 110  
     /** Line number of last import. */
 111  
     private int mLastImportLine;
 112  
     /** Name of last import. */
 113  
     private String mLastImport;
 114  
     /** If last import was static. */
 115  
     private boolean mLastImportStatic;
 116  
     /** Whether there was any imports. */
 117  
     private boolean mBeforeFirstImport;
 118  
 
 119  
     /**
 120  
      * Groups static imports under each group.
 121  
      */
 122  
     public ImportOrderCheck()
 123  
     {
 124  14
         super(ImportOrderOption.UNDER, ImportOrderOption.class);
 125  14
     }
 126  
 
 127  
     /**
 128  
      * Sets the list of package groups and the order they should occur in the
 129  
      * file.
 130  
      *
 131  
      * @param aGroups a comma-separated list of package names/prefixes.
 132  
      */
 133  
     public void setGroups(String[] aGroups)
 134  
     {
 135  5
         mGroups = new Pattern[aGroups.length];
 136  
 
 137  19
         for (int i = 0; i < aGroups.length; i++) {
 138  14
             String pkg = aGroups[i];
 139  
             Pattern grp;
 140  
 
 141  
             // if the pkg name is the wildcard, make it match zero chars
 142  
             // from any name, so it will always be used as last resort.
 143  14
             if (WILDCARD_GROUP_NAME.equals(pkg)) {
 144  1
                 grp = Pattern.compile(""); // matches any package
 145  
             }
 146  13
             else if (pkg.startsWith("/")) {
 147  1
                 if (!pkg.endsWith("/")) {
 148  0
                     throw new IllegalArgumentException("Invalid group");
 149  
                 }
 150  1
                 pkg = pkg.substring(1, pkg.length() - 1);
 151  1
                 grp = Pattern.compile(pkg);
 152  
             }
 153  
             else {
 154  12
                 if (!pkg.endsWith(".")) {
 155  12
                     pkg = pkg + ".";
 156  
                 }
 157  12
                 grp = Pattern.compile("^" + Pattern.quote(pkg));
 158  
             }
 159  
 
 160  14
             mGroups[i] = grp;
 161  
         }
 162  5
     }
 163  
 
 164  
     /**
 165  
      * Sets whether or not imports should be ordered within any one group of
 166  
      * imports.
 167  
      *
 168  
      * @param aOrdered
 169  
      *            whether lexicographic ordering of imports within a group
 170  
      *            required or not.
 171  
      */
 172  
     public void setOrdered(boolean aOrdered)
 173  
     {
 174  2
         mOrdered = aOrdered;
 175  2
     }
 176  
 
 177  
     /**
 178  
      * Sets whether or not groups of imports must be separated from one another
 179  
      * by at least one blank line.
 180  
      *
 181  
      * @param aSeparated
 182  
      *            whether groups should be separated by oen blank line.
 183  
      */
 184  
     public void setSeparated(boolean aSeparated)
 185  
     {
 186  1
         mSeparated = aSeparated;
 187  1
     }
 188  
 
 189  
     /**
 190  
      * Sets whether string comparison should be case sensitive or not.
 191  
      *
 192  
      * @param aCaseSensitive
 193  
      *            whether string comparison should be case sensitive.
 194  
      */
 195  
     public void setCaseSensitive(boolean aCaseSensitive)
 196  
     {
 197  1
         mCaseSensitive = aCaseSensitive;
 198  1
     }
 199  
 
 200  
     @Override
 201  
     public int[] getDefaultTokens()
 202  
     {
 203  14
         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
 204  
     }
 205  
 
 206  
     @Override
 207  
     public void beginTree(DetailAST aRootAST)
 208  
     {
 209  14
         mLastGroup = Integer.MIN_VALUE;
 210  14
         mLastImportLine = Integer.MIN_VALUE;
 211  14
         mLastImport = "";
 212  14
         mLastImportStatic = false;
 213  14
         mBeforeFirstImport = true;
 214  14
     }
 215  
 
 216  
     @Override
 217  
     public void visitToken(DetailAST aAST)
 218  
     {
 219  
         final FullIdent ident;
 220  
         final boolean isStatic;
 221  
 
 222  141
         if (aAST.getType() == TokenTypes.IMPORT) {
 223  106
             ident = FullIdent.createFullIdentBelow(aAST);
 224  106
             isStatic = false;
 225  
         }
 226  
         else {
 227  35
             ident = FullIdent.createFullIdent(aAST.getFirstChild()
 228  
                     .getNextSibling());
 229  35
             isStatic = true;
 230  
         }
 231  
 
 232  1
         switch (getAbstractOption()) {
 233  
         case TOP:
 234  14
             if (!isStatic && mLastImportStatic) {
 235  2
                 mLastGroup = Integer.MIN_VALUE;
 236  2
                 mLastImport = "";
 237  
             }
 238  
             // no break;
 239  
 
 240  
         case ABOVE:
 241  
             // previous non-static but current is static
 242  28
             doVisitToken(ident, isStatic, (!mLastImportStatic && isStatic));
 243  28
             break;
 244  
 
 245  
         case INFLOW:
 246  
             // previous argument is useless here
 247  15
             doVisitToken(ident, isStatic, true);
 248  15
             break;
 249  
 
 250  
         case BOTTOM:
 251  14
             if (isStatic && !mLastImportStatic) {
 252  2
                 mLastGroup = Integer.MIN_VALUE;
 253  2
                 mLastImport = "";
 254  
             }
 255  
             // no break;
 256  
 
 257  
         case UNDER:
 258  
             // previous static but current is non-static
 259  98
             doVisitToken(ident, isStatic, (mLastImportStatic && !isStatic));
 260  98
             break;
 261  
 
 262  
         default:
 263  
             break;
 264  
         }
 265  
 
 266  141
         mLastImportLine = aAST.findFirstToken(TokenTypes.SEMI).getLineNo();
 267  141
         mLastImportStatic = isStatic;
 268  141
         mBeforeFirstImport = false;
 269  141
     }
 270  
 
 271  
     /**
 272  
      * Shares processing...
 273  
      *
 274  
      * @param aIdent the import to process.
 275  
      * @param aIsStatic whether the token is static or not.
 276  
      * @param aPrevious previous non-static but current is static (above), or
 277  
      *                  previous static but current is non-static (under).
 278  
      */
 279  
     private void doVisitToken(FullIdent aIdent, boolean aIsStatic,
 280  
             boolean aPrevious)
 281  
     {
 282  141
         if (aIdent != null) {
 283  141
             final String name = aIdent.getText();
 284  141
             final int groupIdx = getGroupNumber(name);
 285  141
             final int line = aIdent.getLineNo();
 286  
 
 287  141
             if (groupIdx > mLastGroup) {
 288  26
                 if (!mBeforeFirstImport && mSeparated) {
 289  
                     // This check should be made more robust to handle
 290  
                     // comments and imports that span more than one line.
 291  2
                     if ((line - mLastImportLine) < 2) {
 292  2
                         log(line, "import.separation", name);
 293  
                     }
 294  
                 }
 295  
             }
 296  115
             else if (groupIdx == mLastGroup) {
 297  111
                 doVisitTokenInSameGroup(aIsStatic, aPrevious, name, line);
 298  
             }
 299  
             else {
 300  4
                 log(line, "import.ordering", name);
 301  
             }
 302  
 
 303  141
             mLastGroup = groupIdx;
 304  141
             mLastImport = name;
 305  
         }
 306  141
     }
 307  
 
 308  
     /**
 309  
      * Shares processing...
 310  
      *
 311  
      * @param aIsStatic whether the token is static or not.
 312  
      * @param aPrevious previous non-static but current is static (above), or
 313  
      *    previous static but current is non-static (under).
 314  
      * @param aName the name of the current import.
 315  
      * @param aLine the line of the current import.
 316  
      */
 317  
     private void doVisitTokenInSameGroup(boolean aIsStatic,
 318  
             boolean aPrevious, String aName, int aLine)
 319  
     {
 320  111
         if (!mOrdered) {
 321  19
             return;
 322  
         }
 323  
 
 324  92
         if (getAbstractOption().equals(ImportOrderOption.INFLOW)) {
 325  
             // out of lexicographic order
 326  14
             if (compare(mLastImport, aName, mCaseSensitive) > 0) {
 327  6
                 log(aLine, "import.ordering", aName);
 328  
             }
 329  
         }
 330  
         else {
 331  78
             final boolean shouldFireError =
 332  
                 // current and previous static or current and
 333  
                 // previous non-static
 334  
                 (!(mLastImportStatic ^ aIsStatic)
 335  
                 &&
 336  
                 // and out of lexicographic order
 337  
                 (compare(mLastImport, aName, mCaseSensitive) > 0))
 338  
                 ||
 339  
                 // previous non-static but current is static (above)
 340  
                 // or
 341  
                 // previous static but current is non-static (under)
 342  
                 aPrevious;
 343  
 
 344  78
             if (shouldFireError) {
 345  19
                 log(aLine, "import.ordering", aName);
 346  
             }
 347  
         }
 348  92
     }
 349  
 
 350  
     /**
 351  
      * Finds out what group the specified import belongs to.
 352  
      *
 353  
      * @param aName the import name to find.
 354  
      * @return group number for given import name.
 355  
      */
 356  
     private int getGroupNumber(String aName)
 357  
     {
 358  141
         int bestIndex = mGroups.length;
 359  141
         int bestLength = -1;
 360  141
         int bestPos = 0;
 361  
 
 362  
         // find out what group this belongs in
 363  
         // loop over mGroups and get index
 364  269
         for (int i = 0; i < mGroups.length; i++) {
 365  128
             final Matcher matcher = mGroups[i].matcher(aName);
 366  308
             while (matcher.find()) {
 367  180
                 final int length = matcher.end() - matcher.start();
 368  180
                 if ((length > bestLength)
 369  
                     || ((length == bestLength) && (matcher.start() < bestPos)))
 370  
                 {
 371  52
                     bestIndex = i;
 372  52
                     bestLength = length;
 373  52
                     bestPos = matcher.start();
 374  
                 }
 375  180
             }
 376  
         }
 377  
 
 378  141
         return bestIndex;
 379  
     }
 380  
 
 381  
     /**
 382  
      * Compares two strings.
 383  
      *
 384  
      * @param aString1
 385  
      *            the first string.
 386  
      * @param aString2
 387  
      *            the second string.
 388  
      * @param aCaseSensitive
 389  
      *            whether the comparison is case sensitive.
 390  
      * @return the value <code>0</code> if string1 is equal to string2; a value
 391  
      *         less than <code>0</code> if string1 is lexicographically less
 392  
      *         than the string2; and a value greater than <code>0</code> if
 393  
      *         string1 is lexicographically greater than string2.
 394  
      */
 395  
     private int compare(String aString1, String aString2,
 396  
             boolean aCaseSensitive)
 397  
     {
 398  75
         if (aCaseSensitive) {
 399  71
             return aString1.compareTo(aString2);
 400  
         }
 401  
 
 402  4
         return aString1.compareToIgnoreCase(aString2);
 403  
     }
 404  
 }