Source code: com/puppycrawl/tools/checkstyle/checks/IllegalInstantiationCheck.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2002 Oliver Burn
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks;
21
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.Set;
25 import java.util.StringTokenizer;
26
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.FullIdent;
30 import antlr.collections.AST;
31
32 // TODO: Clean up potential duplicate code here and in UnusedImportsCheck
33 /**
34 * <p>
35 * Checks for illegal instantiations where a factory method is preferred.
36 * </p>
37 * <p>
38 * Rationale: Depending on the project, for some classes it might be
39 * preferable to create instances through factory methods rather than
40 * calling the constructor.
41 * </p>
42 * <p>
43 * A simple example is the java.lang.Boolean class, to save memory and CPU
44 * cycles it is preferable to use the predeifined constants TRUE and FALSE.
45 * Constructor invocations should be replaced by calls to Boolean.valueOf().
46 * </p>
47 * <p>
48 * Some extremely performance sensitive projects may require the use of factory
49 * methods for other classes as well, to enforce the usage of number caches or
50 * object pools.
51 * </p>
52 * <p>
53 * Limitations: It is currently not possible to specify array classes.
54 * </p>
55 * <p>
56 * An example of how to configure the check is:
57 * </p>
58 * <pre>
59 * <module name="IllegalInstantiation"/>
60 * </pre>
61 * @author lkuehne
62 */
63 public class IllegalInstantiationCheck
64 extends AbstractImportCheck
65 {
66 /** Set of fully qualified classnames. E.g. "java.lang.Boolean" */
67 private final Set mIllegalClasses = new HashSet();
68
69 /** name of the package */
70 private String mPkgName = null;
71
72 /** the imports for the file */
73 private final Set mImports = new HashSet();
74
75 /** @see com.puppycrawl.tools.checkstyle.api.Check */
76 public int[] getDefaultTokens()
77 {
78 return new int[] {
79 TokenTypes.IMPORT,
80 TokenTypes.LITERAL_NEW,
81 TokenTypes.PACKAGE_DEF
82 };
83 }
84
85 /**
86 * Prevent user from changing tokens in the configuration.
87 * @see com.puppycrawl.tools.checkstyle.api.Check
88 */
89 public int[] getAcceptableTokens()
90 {
91 return new int[] {};
92 }
93
94 /** @see com.puppycrawl.tools.checkstyle.api.Check */
95 public int[] getRequiredTokens()
96 {
97 return new int[] {
98 TokenTypes.IMPORT,
99 TokenTypes.LITERAL_NEW,
100 TokenTypes.PACKAGE_DEF
101 };
102 }
103
104 /** @see com.puppycrawl.tools.checkstyle.api.Check */
105 public void beginTree()
106 {
107 super.beginTree();
108 mPkgName = null;
109 mImports.clear();
110 }
111
112 /** @see com.puppycrawl.tools.checkstyle.api.Check */
113 public void visitToken(DetailAST aAST)
114 {
115 switch (aAST.getType()) {
116 case TokenTypes.LITERAL_NEW:
117 processLiteralNew(aAST);
118 break;
119 case TokenTypes.PACKAGE_DEF:
120 processPackageDef(aAST);
121 break;
122 case TokenTypes.IMPORT:
123 processImport(aAST);
124 break;
125 }
126 }
127
128 /**
129 * Perform processing for an import token
130 * @param aAST the import token
131 */
132 private void processImport(DetailAST aAST)
133 {
134 final FullIdent name = getImportText(aAST);
135 if (name != null) {
136 // Note: different from UnusedImportsCheck.processImport(),
137 // '.*' imports are also added here
138 mImports.add(name);
139 }
140 }
141
142 /**
143 * Perform processing for an package token
144 * @param aAST the package token
145 */
146 private void processPackageDef(DetailAST aAST)
147 {
148 final DetailAST packageNameAST = (DetailAST) aAST.getFirstChild();
149 final FullIdent packageIdent =
150 FullIdent.createFullIdent(packageNameAST);
151 mPkgName = packageIdent.getText();
152 }
153
154 /**
155 * Perform processing for an "new" token
156 * @param aAST the "new" token
157 */
158 private void processLiteralNew(DetailAST aAST)
159 {
160 final DetailAST typeNameAST = (DetailAST) aAST.getFirstChild();
161
162 final AST nameSibling = typeNameAST.getNextSibling();
163 if (nameSibling != null
164 && nameSibling.getType() == TokenTypes.ARRAY_DECLARATOR)
165 {
166 // aAST == "new Boolean[]"
167 return;
168 }
169
170 FullIdent typeIdent = FullIdent.createFullIdent(typeNameAST);
171 final String typeName = typeIdent.getText();
172 final int lineNo = aAST.getLineNo();
173 final int colNo = aAST.getColumnNo();
174 final String fqClassName = getIllegalInstantiation(typeName);
175 if (fqClassName != null) {
176 log(lineNo, colNo, "instantiation.avoid", fqClassName);
177 }
178 }
179
180 /**
181 * Checks illegal instantiations.
182 * @param aClassName instantiated class, may or may not be qualified
183 * @return the fully qualified class name of aClassName
184 * or null if instantiation of aClassName is OK
185 */
186 private String getIllegalInstantiation(String aClassName)
187 {
188 final String javaLang = "java.lang.";
189
190 if (mIllegalClasses.contains(aClassName)) {
191 return aClassName;
192 }
193
194 final int clsNameLen = aClassName.length();
195 final int pkgNameLen = (mPkgName == null) ? 0 : mPkgName.length();
196
197 final Iterator illIter = mIllegalClasses.iterator();
198 while (illIter.hasNext()) {
199 final String illegal = (String) illIter.next();
200 final int illegalLen = illegal.length();
201
202 // class from java.lang
203 if (((illegalLen - javaLang.length()) == clsNameLen)
204 && illegal.endsWith(aClassName)
205 && illegal.startsWith(javaLang))
206 {
207 return illegal;
208 }
209
210 // class from same package
211
212 // the toplevel package (mPkgName == null) is covered by the
213 // "illegalInsts.contains(aClassName)" check above
214
215 // the test is the "no garbage" version of
216 // illegal.equals(mPkgName + "." + aClassName)
217 if (mPkgName != null
218 && clsNameLen == illegalLen - pkgNameLen - 1
219 && illegal.charAt(pkgNameLen) == '.'
220 && illegal.endsWith(aClassName)
221 && illegal.startsWith(mPkgName))
222 {
223 return illegal;
224 }
225 // import statements
226 final Iterator importIter = mImports.iterator();
227 while (importIter.hasNext()) {
228 final FullIdent importLineText = (FullIdent) importIter.next();
229 final String importArg = importLineText.getText();
230 if (importArg.endsWith(".*")) {
231 final String fqClass =
232 importArg.substring(0, importArg.length() - 1)
233 + aClassName;
234 // assume that illegalInsts only contain existing classes
235 // or else we might create a false alarm here
236 if (mIllegalClasses.contains(fqClass)) {
237 return fqClass;
238 }
239 }
240 else {
241 if (basename(importArg).equals(aClassName)
242 && mIllegalClasses.contains(importArg))
243 {
244 return importArg;
245 }
246 }
247 }
248 }
249 return null;
250 }
251
252 /**
253 * @return the class name from a fully qualified name
254 * @param aType the fully qualified name
255 */
256 private static String basename(String aType)
257 {
258 final int i = aType.lastIndexOf(".");
259 return (i == -1) ? aType : aType.substring(i + 1);
260 }
261
262 /**
263 * Sets the classes that are illegal to instantiate.
264 * @param aClassNames a comma seperate list of class names
265 */
266 public void setClasses(String aClassNames)
267 {
268 mIllegalClasses.clear();
269 final StringTokenizer tok = new StringTokenizer(aClassNames, ",");
270 while (tok.hasMoreTokens()) {
271 mIllegalClasses.add(tok.nextToken());
272 }
273 }
274 }