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[] groups = new Pattern[0]; 101 /** Require imports in group be separated. */ 102 private boolean separated; 103 /** Require imports in group. */ 104 private boolean ordered = true; 105 /** Should comparison be case sensitive. */ 106 private boolean caseSensitive = true; 107 108 /** Last imported group. */ 109 private int lastGroup; 110 /** Line number of last import. */ 111 private int lastImportLine; 112 /** Name of last import. */ 113 private String lastImport; 114 /** If last import was static. */ 115 private boolean lastImportStatic; 116 /** Whether there was any imports. */ 117 private boolean beforeFirstImport; 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 packageGroups a comma-separated list of package names/prefixes. 132 */ 133 public void setGroups(String[] packageGroups) 134 { 135 groups = new Pattern[packageGroups.length]; 136 137 for (int i = 0; i < packageGroups.length; i++) { 138 String pkg = packageGroups[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 groups[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 ordered 169 * whether lexicographic ordering of imports within a group 170 * required or not. 171 */ 172 public void setOrdered(boolean ordered) 173 { 174 this.ordered = ordered; 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 separated 182 * whether groups should be separated by oen blank line. 183 */ 184 public void setSeparated(boolean separated) 185 { 186 this.separated = separated; 187 } 188 189 /** 190 * Sets whether string comparison should be case sensitive or not. 191 * 192 * @param caseSensitive 193 * whether string comparison should be case sensitive. 194 */ 195 public void setCaseSensitive(boolean caseSensitive) 196 { 197 this.caseSensitive = caseSensitive; 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 rootAST) 208 { 209 lastGroup = Integer.MIN_VALUE; 210 lastImportLine = Integer.MIN_VALUE; 211 lastImport = ""; 212 lastImportStatic = false; 213 beforeFirstImport = true; 214 } 215 216 @Override 217 public void visitToken(DetailAST ast) 218 { 219 final FullIdent ident; 220 final boolean isStatic; 221 222 if (ast.getType() == TokenTypes.IMPORT) { 223 ident = FullIdent.createFullIdentBelow(ast); 224 isStatic = false; 225 } 226 else { 227 ident = FullIdent.createFullIdent(ast.getFirstChild() 228 .getNextSibling()); 229 isStatic = true; 230 } 231 232 switch (getAbstractOption()) { 233 case TOP: 234 if (!isStatic && lastImportStatic) { 235 lastGroup = Integer.MIN_VALUE; 236 lastImport = ""; 237 } 238 // no break; 239 240 case ABOVE: 241 // previous non-static but current is static 242 doVisitToken(ident, isStatic, (!lastImportStatic && 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 && !lastImportStatic) { 252 lastGroup = Integer.MIN_VALUE; 253 lastImport = ""; 254 } 255 // no break; 256 257 case UNDER: 258 // previous static but current is non-static 259 doVisitToken(ident, isStatic, (lastImportStatic && !isStatic)); 260 break; 261 262 default: 263 break; 264 } 265 266 lastImportLine = ast.findFirstToken(TokenTypes.SEMI).getLineNo(); 267 lastImportStatic = isStatic; 268 beforeFirstImport = false; 269 } 270 271 /** 272 * Shares processing... 273 * 274 * @param ident the import to process. 275 * @param isStatic whether the token is static or not. 276 * @param previous previous non-static but current is static (above), or 277 * previous static but current is non-static (under). 278 */ 279 private void doVisitToken(FullIdent ident, boolean isStatic, 280 boolean previous) 281 { 282 if (ident != null) { 283 final String name = ident.getText(); 284 final int groupIdx = getGroupNumber(name); 285 final int line = ident.getLineNo(); 286 287 if (groupIdx > lastGroup) { 288 if (!beforeFirstImport && separated) { 289 // This check should be made more robust to handle 290 // comments and imports that span more than one line. 291 if ((line - lastImportLine) < 2) { 292 log(line, "import.separation", name); 293 } 294 } 295 } 296 else if (groupIdx == lastGroup) { 297 doVisitTokenInSameGroup(isStatic, previous, name, line); 298 } 299 else { 300 log(line, "import.ordering", name); 301 } 302 303 lastGroup = groupIdx; 304 lastImport = name; 305 } 306 } 307 308 /** 309 * Shares processing... 310 * 311 * @param isStatic whether the token is static or not. 312 * @param previous previous non-static but current is static (above), or 313 * previous static but current is non-static (under). 314 * @param name the name of the current import. 315 * @param line the line of the current import. 316 */ 317 private void doVisitTokenInSameGroup(boolean isStatic, 318 boolean previous, String name, int line) 319 { 320 if (!ordered) { 321 return; 322 } 323 324 if (getAbstractOption().equals(ImportOrderOption.INFLOW)) { 325 // out of lexicographic order 326 if (compare(lastImport, name, caseSensitive) > 0) { 327 log(line, "import.ordering", name); 328 } 329 } 330 else { 331 final boolean shouldFireError = 332 // current and previous static or current and 333 // previous non-static 334 (!(lastImportStatic ^ isStatic) 335 && 336 // and out of lexicographic order 337 (compare(lastImport, name, caseSensitive) > 0)) 338 || 339 // previous non-static but current is static (above) 340 // or 341 // previous static but current is non-static (under) 342 previous; 343 344 if (shouldFireError) { 345 log(line, "import.ordering", name); 346 } 347 } 348 } 349 350 /** 351 * Finds out what group the specified import belongs to. 352 * 353 * @param name the import name to find. 354 * @return group number for given import name. 355 */ 356 private int getGroupNumber(String name) 357 { 358 int bestIndex = groups.length; 359 int bestLength = -1; 360 int bestPos = 0; 361 362 // find out what group this belongs in 363 // loop over groups and get index 364 for (int i = 0; i < groups.length; i++) { 365 final Matcher matcher = groups[i].matcher(name); 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 string1 385 * the first string. 386 * @param string2 387 * the second string. 388 * @param caseSensitive 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 string1, String string2, 396 boolean caseSensitive) 397 { 398 if (caseSensitive) { 399 return string1.compareTo(string2); 400 } 401 402 return string1.compareToIgnoreCase(string2); 403 } 404}