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