001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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 com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.FullIdent; 024import com.puppycrawl.tools.checkstyle.api.TokenTypes; 025import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029/** 030 * <ul> 031 * <li>groups imports: ensures that groups of imports come in a specific order 032 * (e.g., java. comes first, javax. comes second, then everything else)</li> 033 * <li>adds a separation between groups : ensures that a blank line sit between 034 * each group</li> 035 * <li>sorts imports inside each group: ensures that imports within each group 036 * are in lexicographic order</li> 037 * <li>sorts according to case: ensures that the comparison between import is 038 * case sensitive</li> 039 * <li>groups static imports: ensures that static imports are at the top (or the 040 * bottom) of all the imports, or above (or under) each group, or are treated 041 * like non static imports (@see {@link ImportOrderOption}</li> 042 * </ul> 043 * 044 * <p> 045 * Example: 046 * </p> 047 * 048 * <pre> 049 * <module name="ImportOrder"> 050 * <property name="groups" value="java,javax"/> 051 * <property name="ordered" value="true"/> 052 * <property name="caseSensitive" value="false"/> 053 * <property name="option" value="above"/> 054 * </module> 055 * </pre> 056 * 057 * <p> 058 * Group descriptions enclosed in slashes are interpreted as regular 059 * expressions. If multiple groups match, the one matching a longer 060 * substring of the imported name will take precedence, with ties 061 * broken first in favor of earlier matches and finally in favor of 062 * the first matching group. 063 * </p> 064 * 065 * <p> 066 * There is always a wildcard group to which everything not in a named group 067 * belongs. If an import does not match a named group, the group belongs to 068 * this wildcard group. The wildcard group position can be specified using the 069 * {@code *} character. 070 * </p> 071 * 072 * <p> 073 * Defaults: 074 * </p> 075 * <ul> 076 * <li>import groups: none</li> 077 * <li>separation: false</li> 078 * <li>ordered: true</li> 079 * <li>case sensitive: true</li> 080 * <li>static import: under</li> 081 * </ul> 082 * 083 * <p> 084 * Compatible with Java 1.5 source. 085 * </p> 086 * 087 * @author Bill Schneider 088 * @author o_sukhodolsky 089 * @author David DIDIER 090 * @author Steve McKay 091 */ 092public class ImportOrderCheck 093 extends AbstractOptionCheck<ImportOrderOption> 094{ 095 096 /** the special wildcard that catches all remaining groups. */ 097 private static final String WILDCARD_GROUP_NAME = "*"; 098 099 /** List of import groups specified by the user. */ 100 private Pattern[] mGroups = new Pattern[0]; 101 /** Require imports in group be separated. */ 102 private boolean mSeparated; 103 /** Require imports in group. */ 104 private boolean mOrdered = true; 105 /** Should comparison be case sensitive. */ 106 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 super(ImportOrderOption.UNDER, ImportOrderOption.class); 125 } 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 mGroups = new Pattern[aGroups.length]; 136 137 for (int i = 0; i < aGroups.length; i++) { 138 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 if (WILDCARD_GROUP_NAME.equals(pkg)) { 144 grp = Pattern.compile(""); // matches any package 145 } 146 else if (pkg.startsWith("/")) { 147 if (!pkg.endsWith("/")) { 148 throw new IllegalArgumentException("Invalid group"); 149 } 150 pkg = pkg.substring(1, pkg.length() - 1); 151 grp = Pattern.compile(pkg); 152 } 153 else { 154 if (!pkg.endsWith(".")) { 155 pkg = pkg + "."; 156 } 157 grp = Pattern.compile("^" + Pattern.quote(pkg)); 158 } 159 160 mGroups[i] = grp; 161 } 162 } 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 mOrdered = aOrdered; 175 } 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 mSeparated = aSeparated; 187 } 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 mCaseSensitive = aCaseSensitive; 198 } 199 200 @Override 201 public int[] getDefaultTokens() 202 { 203 return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT}; 204 } 205 206 @Override 207 public void beginTree(DetailAST aRootAST) 208 { 209 mLastGroup = Integer.MIN_VALUE; 210 mLastImportLine = Integer.MIN_VALUE; 211 mLastImport = ""; 212 mLastImportStatic = false; 213 mBeforeFirstImport = true; 214 } 215 216 @Override 217 public void visitToken(DetailAST aAST) 218 { 219 final FullIdent ident; 220 final boolean isStatic; 221 222 if (aAST.getType() == TokenTypes.IMPORT) { 223 ident = FullIdent.createFullIdentBelow(aAST); 224 isStatic = false; 225 } 226 else { 227 ident = FullIdent.createFullIdent(aAST.getFirstChild() 228 .getNextSibling()); 229 isStatic = true; 230 } 231 232 switch (getAbstractOption()) { 233 case TOP: 234 if (!isStatic && mLastImportStatic) { 235 mLastGroup = Integer.MIN_VALUE; 236 mLastImport = ""; 237 } 238 // no break; 239 240 case ABOVE: 241 // previous non-static but current is static 242 doVisitToken(ident, isStatic, (!mLastImportStatic && isStatic)); 243 break; 244 245 case INFLOW: 246 // previous argument is useless here 247 doVisitToken(ident, isStatic, true); 248 break; 249 250 case BOTTOM: 251 if (isStatic && !mLastImportStatic) { 252 mLastGroup = Integer.MIN_VALUE; 253 mLastImport = ""; 254 } 255 // no break; 256 257 case UNDER: 258 // previous static but current is non-static 259 doVisitToken(ident, isStatic, (mLastImportStatic && !isStatic)); 260 break; 261 262 default: 263 break; 264 } 265 266 mLastImportLine = aAST.findFirstToken(TokenTypes.SEMI).getLineNo(); 267 mLastImportStatic = isStatic; 268 mBeforeFirstImport = false; 269 } 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 if (aIdent != null) { 283 final String name = aIdent.getText(); 284 final int groupIdx = getGroupNumber(name); 285 final int line = aIdent.getLineNo(); 286 287 if (groupIdx > mLastGroup) { 288 if (!mBeforeFirstImport && mSeparated) { 289 // This check should be made more robust to handle 290 // comments and imports that span more than one line. 291 if ((line - mLastImportLine) < 2) { 292 log(line, "import.separation", name); 293 } 294 } 295 } 296 else if (groupIdx == mLastGroup) { 297 doVisitTokenInSameGroup(aIsStatic, aPrevious, name, line); 298 } 299 else { 300 log(line, "import.ordering", name); 301 } 302 303 mLastGroup = groupIdx; 304 mLastImport = name; 305 } 306 } 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 if (!mOrdered) { 321 return; 322 } 323 324 if (getAbstractOption().equals(ImportOrderOption.INFLOW)) { 325 // out of lexicographic order 326 if (compare(mLastImport, aName, mCaseSensitive) > 0) { 327 log(aLine, "import.ordering", aName); 328 } 329 } 330 else { 331 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 if (shouldFireError) { 345 log(aLine, "import.ordering", aName); 346 } 347 } 348 } 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 int bestIndex = mGroups.length; 359 int bestLength = -1; 360 int bestPos = 0; 361 362 // find out what group this belongs in 363 // loop over mGroups and get index 364 for (int i = 0; i < mGroups.length; i++) { 365 final Matcher matcher = mGroups[i].matcher(aName); 366 while (matcher.find()) { 367 final int length = matcher.end() - matcher.start(); 368 if ((length > bestLength) 369 || ((length == bestLength) && (matcher.start() < bestPos))) 370 { 371 bestIndex = i; 372 bestLength = length; 373 bestPos = matcher.start(); 374 } 375 } 376 } 377 378 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 if (aCaseSensitive) { 399 return aString1.compareTo(aString2); 400 } 401 402 return aString1.compareToIgnoreCase(aString2); 403 } 404}