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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.coding; 020 021import com.google.common.collect.Sets; 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.AbstractFormatCheck; 026import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 027 028import java.util.ArrayList; 029import java.util.List; 030import java.util.Set; 031 032/** 033 * <p> 034 * Checks that particular class are never used as types in variable 035 * declarations, return values or parameters. Includes 036 * a pattern check that by default disallows abstract classes. 037 * </p> 038 * <p> 039 * Rationale: 040 * Helps reduce coupling on concrete classes. In addition abstract 041 * classes should be thought of a convenience base class 042 * implementations of interfaces and as such are not types themselves. 043 * </p> 044 * Check has following properties: 045 * <p> 046 * <b>format</b> - Pattern for illegal class names. 047 * </p> 048 * <p> 049 * <b>legalAbstractClassNames</b> - Abstract classes that may be used as types. 050 * </p> 051 * <p> 052 * <b>illegalClassNames</b> - Classes that should not be used as types in variable 053 declarations, return values or parameters. 054 * It is possible to set illegal class names via short or 055 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 056 * canonical</a> name. 057 * Specifying illegal type invokes analyzing imports and Check puts violations at 058 * corresponding declarations 059 * (of variables, methods or parameters). This helps to avoid ambiguous cases, e.g.: 060 * <p> 061 * <code>java.awt.List</code> was set as illegal class name, then, code like: 062 * <p> 063 * <code> 064 * import java.util.List;<br> 065 * ...<br> 066 * List list; //No violation here 067 * </code> 068 * </p> 069 * will be ok. 070 * </p> 071 * <p> 072 * <b>ignoredMethodNames</b> - Methods that should not be checked. 073 * </p> 074 * <p> 075 * <b>memberModifiers</b> - To check only methods and fields with only specified modifiers. 076 * </p> 077 * <p> 078 * In most cases it's justified to put following classes to <b>illegalClassNames</b>: 079 * <ul> 080 * <li>GregorianCalendar</li> 081 * <li>Hashtable</li> 082 * <li>ArrayList</li> 083 * <li>LinkedList</li> 084 * <li>Vector</li> 085 * </ul> 086 * as methods that are differ from interface methods are rear used, so in most cases user will 087 * benefit from checking for them. 088 * </p> 089 * 090 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 091 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 092 */ 093public final class IllegalTypeCheck extends AbstractFormatCheck 094{ 095 /** Default value of pattern for illegal class name. */ 096 private static final String DEFAULT_FORMAT = "^(.*[\\.])?Abstract.*$"; 097 /** Abstract classes legal by default. */ 098 private static final String[] DEFAULT_LEGAL_ABSTRACT_NAMES = {}; 099 /** Types illegal by default. */ 100 private static final String[] DEFAULT_ILLEGAL_TYPES = { 101 "HashSet", 102 "HashMap", 103 "LinkedHashMap", 104 "LinkedHashSet", 105 "TreeSet", 106 "TreeMap", 107 "java.util.HashSet", 108 "java.util.HashMap", 109 "java.util.LinkedHashMap", 110 "java.util.LinkedHashSet", 111 "java.util.TreeSet", 112 "java.util.TreeMap", 113 }; 114 115 /** Default ignored method names. */ 116 private static final String[] DEFAULT_IGNORED_METHOD_NAMES = { 117 "getInitialContext", 118 "getEnvironment", 119 }; 120 121 /** illegal classes. */ 122 private final Set<String> illegalClassNames = Sets.newHashSet(); 123 /** legal abstract classes. */ 124 private final Set<String> legalAbstractClassNames = Sets.newHashSet(); 125 /** methods which should be ignored. */ 126 private final Set<String> ignoredMethodNames = Sets.newHashSet(); 127 /** check methods and fields with only corresponding modifiers. */ 128 private List<Integer> memberModifiers; 129 130 /** Creates new instance of the check. */ 131 public IllegalTypeCheck() 132 { 133 super(DEFAULT_FORMAT); 134 setIllegalClassNames(DEFAULT_ILLEGAL_TYPES); 135 setLegalAbstractClassNames(DEFAULT_LEGAL_ABSTRACT_NAMES); 136 setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES); 137 } 138 139 @Override 140 public int[] getDefaultTokens() 141 { 142 return new int[] { 143 TokenTypes.VARIABLE_DEF, 144 TokenTypes.PARAMETER_DEF, 145 TokenTypes.METHOD_DEF, 146 TokenTypes.IMPORT, 147 }; 148 } 149 150 @Override 151 public void visitToken(DetailAST ast) 152 { 153 switch (ast.getType()) { 154 case TokenTypes.METHOD_DEF: 155 if (isVerifiable(ast)) { 156 visitMethodDef(ast); 157 } 158 break; 159 case TokenTypes.VARIABLE_DEF: 160 if (isVerifiable(ast)) { 161 visitVariableDef(ast); 162 } 163 break; 164 case TokenTypes.PARAMETER_DEF: 165 visitParameterDef(ast); 166 break; 167 case TokenTypes.IMPORT: 168 visitImport(ast); 169 break; 170 default: 171 throw new IllegalStateException(ast.toString()); 172 } 173 } 174 175 /** 176 * Checks if current method's return type or variable's type is verifiable 177 * according to <b>memberModifiers</b> option. 178 * @param methodOrVariableDef METHOD_DEF or VARIABLE_DEF ast node. 179 * @return true if member is verifiable according to <b>memberModifiers</b> option. 180 */ 181 private boolean isVerifiable(DetailAST methodOrVariableDef) 182 { 183 boolean result = true; 184 if (memberModifiers != null) { 185 result = false; 186 final DetailAST modifiersAst = methodOrVariableDef. 187 findFirstToken(TokenTypes.MODIFIERS); 188 if (modifiersAst.getFirstChild() != null) { 189 for (DetailAST modifier = modifiersAst.getFirstChild(); modifier != null; 190 modifier = modifier.getNextSibling()) 191 { 192 if (memberModifiers.contains(modifier.getType())) { 193 result = true; 194 } 195 } 196 } 197 } 198 return result; 199 } 200 201 /** 202 * Checks return type of a given method. 203 * @param methodDef method for check. 204 */ 205 private void visitMethodDef(DetailAST methodDef) 206 { 207 if (isCheckedMethod(methodDef)) { 208 checkClassName(methodDef); 209 } 210 } 211 212 /** 213 * Checks type of parameters. 214 * @param paradef parameter list for check. 215 */ 216 private void visitParameterDef(DetailAST paradef) 217 { 218 final DetailAST grandParentAST = paradef.getParent().getParent(); 219 220 if ((grandParentAST.getType() == TokenTypes.METHOD_DEF) 221 && isCheckedMethod(grandParentAST)) 222 { 223 checkClassName(paradef); 224 } 225 } 226 227 /** 228 * Checks type of given variable. 229 * @param variableDef variable to check. 230 */ 231 private void visitVariableDef(DetailAST variableDef) 232 { 233 checkClassName(variableDef); 234 } 235 236 /** 237 * Checks imported type (as static and star imports are not supported by Check, 238 * only type is in the consideration).<br> 239 * If this type is illegal due to Check's options - puts violation on it. 240 * @param importAst {@link TokenTypes#IMPORT Import} 241 */ 242 private void visitImport(DetailAST importAst) 243 { 244 if (!isStarImport(importAst)) { 245 final String canonicalName = getCanonicalName(importAst); 246 extendIllegalClassNamesWithShortName(canonicalName); 247 } 248 } 249 250 /** 251 * Checks if current import is star import. E.g.: 252 * <p> 253 * <code> 254 * import java.util.*; 255 * </code> 256 * </p> 257 * @param importAst {@link TokenTypes#IMPORT Import} 258 * @return true if it is star import 259 */ 260 private static boolean isStarImport(DetailAST importAst) 261 { 262 boolean result = false; 263 DetailAST toVisit = importAst; 264 while (toVisit != null) { 265 toVisit = getNextSubTreeNode(toVisit, importAst); 266 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 267 result = true; 268 break; 269 } 270 } 271 return result; 272 } 273 274 /** 275 * Checks type of given method, parameter or variable. 276 * @param ast node to check. 277 */ 278 private void checkClassName(DetailAST ast) 279 { 280 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 281 final FullIdent ident = CheckUtils.createFullType(type); 282 283 if (isMatchingClassName(ident.getText())) { 284 log(ident.getLineNo(), ident.getColumnNo(), 285 "illegal.type", ident.getText()); 286 } 287 } 288 289 /** 290 * @param className class name to check. 291 * @return true if given class name is one of illegal classes 292 * or if it matches to abstract class names pattern. 293 */ 294 private boolean isMatchingClassName(String className) 295 { 296 final String shortName = className.substring(className.lastIndexOf(".") + 1); 297 return (illegalClassNames.contains(className) 298 || illegalClassNames.contains(shortName)) 299 || (!legalAbstractClassNames.contains(className) 300 && getRegexp().matcher(className).find()); 301 } 302 303 /** 304 * Extends illegal class names set via imported short type name. 305 * @param canonicalName 306 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 307 * Canonical</a> name of imported type. 308 */ 309 private void extendIllegalClassNamesWithShortName(String canonicalName) 310 { 311 if (illegalClassNames.contains(canonicalName)) { 312 final String shortName = canonicalName. 313 substring(canonicalName.lastIndexOf(".") + 1); 314 illegalClassNames.add(shortName); 315 } 316 } 317 318 /** 319 * Gets imported type's 320 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.7"> 321 * canonical name</a>. 322 * @param importAst {@link TokenTypes#IMPORT Import} 323 * @return Imported canonical type's name. 324 */ 325 private static String getCanonicalName(DetailAST importAst) 326 { 327 final StringBuilder canonicalNameBuilder = new StringBuilder(); 328 DetailAST toVisit = importAst; 329 while (toVisit != null) { 330 toVisit = getNextSubTreeNode(toVisit, importAst); 331 if (toVisit != null 332 && (toVisit.getType() == TokenTypes.IDENT 333 || toVisit.getType() == TokenTypes.STAR)) 334 { 335 canonicalNameBuilder.append(toVisit.getText()); 336 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, importAst); 337 if (nextSubTreeNode.getType() != TokenTypes.SEMI) { 338 canonicalNameBuilder.append('.'); 339 } 340 } 341 } 342 return canonicalNameBuilder.toString(); 343 } 344 345 /** 346 * Gets the next node of a syntactical tree (child of a current node or 347 * sibling of a current node, or sibling of a parent of a current node) 348 * @param currentNodeAst Current node in considering 349 * @param subTreeRootAst SubTree root 350 * @return Current node after bypassing, if current node reached the root of a subtree 351 * method returns null 352 */ 353 private static DetailAST 354 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) 355 { 356 DetailAST currentNode = currentNodeAst; 357 DetailAST toVisitAst = currentNode.getFirstChild(); 358 while (toVisitAst == null) { 359 toVisitAst = currentNode.getNextSibling(); 360 if (toVisitAst == null) { 361 if (currentNode.getParent().equals(subTreeRootAst)) { 362 break; 363 } 364 currentNode = currentNode.getParent(); 365 } 366 } 367 currentNode = toVisitAst; 368 return currentNode; 369 } 370 371 /** 372 * @param ast method def to check. 373 * @return true if we should check this method. 374 */ 375 private boolean isCheckedMethod(DetailAST ast) 376 { 377 final String methodName = 378 ast.findFirstToken(TokenTypes.IDENT).getText(); 379 return !ignoredMethodNames.contains(methodName); 380 } 381 382 /** 383 * Set the list of illegal variable types. 384 * @param classNames array of illegal variable types 385 */ 386 public void setIllegalClassNames(String[] classNames) 387 { 388 illegalClassNames.clear(); 389 for (String name : classNames) { 390 illegalClassNames.add(name); 391 } 392 } 393 394 /** 395 * Get the list of illegal variable types. 396 * @return array of illegal variable types 397 */ 398 public String[] getIllegalClassNames() 399 { 400 return illegalClassNames.toArray( 401 new String[illegalClassNames.size()]); 402 } 403 404 /** 405 * Set the list of ignore method names. 406 * @param methodNames array of ignored method names 407 */ 408 public void setIgnoredMethodNames(String[] methodNames) 409 { 410 ignoredMethodNames.clear(); 411 for (String element : methodNames) { 412 ignoredMethodNames.add(element); 413 } 414 } 415 416 /** 417 * Get the list of ignored method names. 418 * @return array of ignored method names 419 */ 420 public String[] getIgnoredMethodNames() 421 { 422 return ignoredMethodNames.toArray( 423 new String[ignoredMethodNames.size()]); 424 } 425 426 /** 427 * Set the list of legal abstract class names. 428 * @param classNames array of legal abstract class names 429 */ 430 public void setLegalAbstractClassNames(String[] classNames) 431 { 432 legalAbstractClassNames.clear(); 433 for (String element : classNames) { 434 legalAbstractClassNames.add(element); 435 } 436 } 437 438 /** 439 * Get the list of legal abstract class names. 440 * @return array of legal abstract class names 441 */ 442 public String[] getLegalAbstractClassNames() 443 { 444 return legalAbstractClassNames.toArray( 445 new String[legalAbstractClassNames.size()]); 446 } 447 448 /** 449 * Set the list of member modifiers (of methods and fields) which should be checked. 450 * @param modifiers String contains modifiers. 451 */ 452 public void setMemberModifiers(String modifiers) 453 { 454 final List<Integer> modifiersList = new ArrayList<Integer>(modifiers.length()); 455 for (String modifier : modifiers.split(", ")) { 456 modifiersList.add(TokenTypes.getTokenId(modifier)); 457 } 458 this.memberModifiers = modifiersList; 459 } 460}