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