001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.Set; 023import java.util.StringTokenizer; 024 025import antlr.collections.AST; 026 027import com.google.common.collect.Sets; 028import com.puppycrawl.tools.checkstyle.api.Check; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.FullIdent; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 033 034/** 035 * <p> 036 * Checks for illegal instantiations where a factory method is preferred. 037 * </p> 038 * <p> 039 * Rationale: Depending on the project, for some classes it might be 040 * preferable to create instances through factory methods rather than 041 * calling the constructor. 042 * </p> 043 * <p> 044 * A simple example is the java.lang.Boolean class, to save memory and CPU 045 * cycles it is preferable to use the predefined constants TRUE and FALSE. 046 * Constructor invocations should be replaced by calls to Boolean.valueOf(). 047 * </p> 048 * <p> 049 * Some extremely performance sensitive projects may require the use of factory 050 * methods for other classes as well, to enforce the usage of number caches or 051 * object pools. 052 * </p> 053 * <p> 054 * Limitations: It is currently not possible to specify array classes. 055 * </p> 056 * <p> 057 * An example of how to configure the check is: 058 * </p> 059 * <pre> 060 * <module name="IllegalInstantiation"/> 061 * </pre> 062 * @author lkuehne 063 */ 064public class IllegalInstantiationCheck 065 extends Check { 066 067 /** 068 * A key is pointing to the warning message text in "messages.properties" 069 * file. 070 */ 071 public static final String MSG_KEY = "instantiation.avoid"; 072 073 /** {@link java.lang} package as string */ 074 private static final String JAVA_LANG = "java.lang."; 075 076 /** Set of fully qualified class names. E.g. "java.lang.Boolean" */ 077 private final Set<String> illegalClasses = Sets.newHashSet(); 078 079 /** Name of the package. */ 080 private String pkgName; 081 082 /** The imports for the file. */ 083 private final Set<FullIdent> imports = Sets.newHashSet(); 084 085 /** The class names defined in the file. */ 086 private final Set<String> classNames = Sets.newHashSet(); 087 088 /** The instantiations in the file. */ 089 private final Set<DetailAST> instantiations = Sets.newHashSet(); 090 091 @Override 092 public int[] getDefaultTokens() { 093 return getAcceptableTokens(); 094 } 095 096 @Override 097 public int[] getAcceptableTokens() { 098 return new int[] { 099 TokenTypes.IMPORT, 100 TokenTypes.LITERAL_NEW, 101 TokenTypes.PACKAGE_DEF, 102 TokenTypes.CLASS_DEF, 103 }; 104 } 105 106 @Override 107 public int[] getRequiredTokens() { 108 return new int[] { 109 TokenTypes.IMPORT, 110 TokenTypes.LITERAL_NEW, 111 TokenTypes.PACKAGE_DEF, 112 }; 113 } 114 115 @Override 116 public void beginTree(DetailAST rootAST) { 117 super.beginTree(rootAST); 118 pkgName = null; 119 imports.clear(); 120 instantiations.clear(); 121 classNames.clear(); 122 } 123 124 @Override 125 public void visitToken(DetailAST ast) { 126 switch (ast.getType()) { 127 case TokenTypes.LITERAL_NEW: 128 processLiteralNew(ast); 129 break; 130 case TokenTypes.PACKAGE_DEF: 131 processPackageDef(ast); 132 break; 133 case TokenTypes.IMPORT: 134 processImport(ast); 135 break; 136 case TokenTypes.CLASS_DEF: 137 processClassDef(ast); 138 break; 139 default: 140 throw new IllegalArgumentException("Unknown type " + ast); 141 } 142 } 143 144 @Override 145 public void finishTree(DetailAST rootAST) { 146 for (DetailAST literalNewAST : instantiations) { 147 postProcessLiteralNew(literalNewAST); 148 } 149 } 150 151 /** 152 * Collects classes defined in the source file. Required 153 * to avoid false alarms for local vs. java.lang classes. 154 * 155 * @param ast the class def token. 156 */ 157 private void processClassDef(DetailAST ast) { 158 final DetailAST identToken = ast.findFirstToken(TokenTypes.IDENT); 159 final String className = identToken.getText(); 160 classNames.add(className); 161 } 162 163 /** 164 * Perform processing for an import token. 165 * @param ast the import token 166 */ 167 private void processImport(DetailAST ast) { 168 final FullIdent name = FullIdent.createFullIdentBelow(ast); 169 // Note: different from UnusedImportsCheck.processImport(), 170 // '.*' imports are also added here 171 imports.add(name); 172 } 173 174 /** 175 * Perform processing for an package token. 176 * @param ast the package token 177 */ 178 private void processPackageDef(DetailAST ast) { 179 final DetailAST packageNameAST = ast.getLastChild() 180 .getPreviousSibling(); 181 final FullIdent packageIdent = 182 FullIdent.createFullIdent(packageNameAST); 183 pkgName = packageIdent.getText(); 184 } 185 186 /** 187 * Collects a "new" token. 188 * @param ast the "new" token 189 */ 190 private void processLiteralNew(DetailAST ast) { 191 if (ast.getParent().getType() != TokenTypes.METHOD_REF) { 192 instantiations.add(ast); 193 } 194 } 195 196 /** 197 * Processes one of the collected "new" tokens when walking tree 198 * has finished. 199 * @param newTokenAst the "new" token. 200 */ 201 private void postProcessLiteralNew(DetailAST newTokenAst) { 202 final DetailAST typeNameAst = newTokenAst.getFirstChild(); 203 final AST nameSibling = typeNameAst.getNextSibling(); 204 if (nameSibling.getType() == TokenTypes.ARRAY_DECLARATOR) { 205 // ast == "new Boolean[]" 206 return; 207 } 208 209 final FullIdent typeIdent = FullIdent.createFullIdent(typeNameAst); 210 final String typeName = typeIdent.getText(); 211 final int lineNo = newTokenAst.getLineNo(); 212 final int colNo = newTokenAst.getColumnNo(); 213 final String fqClassName = getIllegalInstantiation(typeName); 214 if (fqClassName != null) { 215 log(lineNo, colNo, MSG_KEY, fqClassName); 216 } 217 } 218 219 /** 220 * Checks illegal instantiations. 221 * @param className instantiated class, may or may not be qualified 222 * @return the fully qualified class name of className 223 * or null if instantiation of className is OK 224 */ 225 private String getIllegalInstantiation(String className) { 226 String fullClassName = null; 227 228 if (illegalClasses.contains(className)) { 229 fullClassName = className; 230 } 231 else { 232 final int pkgNameLen; 233 234 if (pkgName == null) { 235 pkgNameLen = 0; 236 } 237 else { 238 pkgNameLen = pkgName.length(); 239 } 240 241 for (String illegal : illegalClasses) { 242 if (isStandardClass(className, illegal) 243 || isSamePackage(className, pkgNameLen, illegal)) { 244 fullClassName = illegal; 245 } 246 else { 247 fullClassName = checkImportStatements(className); 248 } 249 250 if (fullClassName != null) { 251 break; 252 } 253 } 254 } 255 return fullClassName; 256 } 257 258 /** 259 * Check import statements. 260 * @param className name of the class 261 * @return value of illegal instantiated type 262 * @noinspection StringContatenationInLoop 263 */ 264 private String checkImportStatements(String className) { 265 String illegalType = null; 266 // import statements 267 for (FullIdent importLineText : imports) { 268 String importArg = importLineText.getText(); 269 if (importArg.endsWith(".*")) { 270 importArg = importArg.substring(0, importArg.length() - 1) 271 + className; 272 } 273 if (CommonUtils.baseClassName(importArg).equals(className) 274 && illegalClasses.contains(importArg)) { 275 illegalType = importArg; 276 break; 277 } 278 } 279 return illegalType; 280 } 281 282 /** 283 * Check that type is of the same package. 284 * @param className class name 285 * @param pkgNameLen package name 286 * @param illegal illegal value 287 * @return true if type of the same package 288 */ 289 private boolean isSamePackage(String className, int pkgNameLen, String illegal) { 290 // class from same package 291 292 // the top level package (pkgName == null) is covered by the 293 // "illegalInstances.contains(className)" check above 294 295 // the test is the "no garbage" version of 296 // illegal.equals(pkgName + "." + className) 297 return pkgName != null 298 && className.length() == illegal.length() - pkgNameLen - 1 299 && illegal.charAt(pkgNameLen) == '.' 300 && illegal.endsWith(className) 301 && illegal.startsWith(pkgName); 302 } 303 304 /** 305 * Is class of the same package. 306 * @param className class name 307 * @return true if same package class 308 */ 309 private boolean isSamePackage(String className) { 310 boolean isSamePackage = false; 311 try { 312 final ClassLoader classLoader = getClassLoader(); 313 if (classLoader != null) { 314 final String fqName = pkgName + "." + className; 315 classLoader.loadClass(fqName); 316 // no ClassNotFoundException, fqName is a known class 317 isSamePackage = true; 318 } 319 } 320 catch (final ClassNotFoundException ignored) { 321 // not a class from the same package 322 isSamePackage = false; 323 } 324 return isSamePackage; 325 } 326 327 /** 328 * Is Standard Class. 329 * @param className class name 330 * @param illegal illegal value 331 * @return true if type is standard 332 */ 333 private boolean isStandardClass(String className, String illegal) { 334 // class from java.lang 335 if (illegal.length() - JAVA_LANG.length() == className.length() 336 && illegal.endsWith(className) 337 && illegal.startsWith(JAVA_LANG)) { 338 // java.lang needs no import, but a class without import might 339 // also come from the same file or be in the same package. 340 // E.g. if a class defines an inner class "Boolean", 341 // the expression "new Boolean()" refers to that class, 342 // not to java.lang.Boolean 343 344 final boolean isSameFile = classNames.contains(className); 345 final boolean isSamePackage = isSamePackage(className); 346 347 if (!(isSameFile || isSamePackage)) { 348 return true; 349 } 350 } 351 return false; 352 } 353 354 /** 355 * Sets the classes that are illegal to instantiate. 356 * @param names a comma separate list of class names 357 */ 358 public void setClasses(String names) { 359 illegalClasses.clear(); 360 final StringTokenizer tok = new StringTokenizer(names, ","); 361 while (tok.hasMoreTokens()) { 362 illegalClasses.add(tok.nextToken()); 363 } 364 } 365}