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