Source code: com/puppycrawl/tools/checkstyle/checks/AbstractTypeAwareCheck.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2003 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 package com.puppycrawl.tools.checkstyle.checks;
20
21 import com.puppycrawl.tools.checkstyle.api.Check;
22 import com.puppycrawl.tools.checkstyle.api.DetailAST;
23 import com.puppycrawl.tools.checkstyle.api.FullIdent;
24 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
25 import com.puppycrawl.tools.checkstyle.api.Utils;
26 import java.util.HashSet;
27 import java.util.Set;
28
29 /**
30 * Abstract class that endeavours to maintain type information for the Java
31 * file being checked. It provides helper methods for performing type
32 * information functions.
33 *
34 * @author Oliver Burn
35 * @version 1.0
36 */
37 public abstract class AbstractTypeAwareCheck
38 extends Check
39 {
40 /** imports details **/
41 private Set mImports = new HashSet();
42
43 /** full identifier for package of the method **/
44 private FullIdent mPackageFullIdent;
45
46 /** <code>ClassResolver</code> instance for current tree. */
47 private ClassResolver mClassResolver;
48
49 /**
50 * Called to process an AST when visiting it.
51 * @param aAST the AST to process. Guaranteed to not be PACKAGE_DEF or
52 * IMPORT tokens.
53 */
54 protected abstract void processAST(DetailAST aAST);
55
56 /** @see com.puppycrawl.tools.checkstyle.api.Check */
57 public void beginTree(DetailAST aRootAST)
58 {
59 mPackageFullIdent = FullIdent.createFullIdent(null);
60 mImports.clear();
61 mClassResolver = null;
62 }
63
64 /** @see com.puppycrawl.tools.checkstyle.api.Check */
65 public final void visitToken(DetailAST aAST)
66 {
67 if (aAST.getType() == TokenTypes.PACKAGE_DEF) {
68 processPackage(aAST);
69 }
70 else if (aAST.getType() == TokenTypes.IMPORT) {
71 processImport(aAST);
72 }
73 else {
74 processAST(aAST);
75 }
76 }
77
78 /**
79 * Calculate if one type name is a shortname for another type name.
80 * @param aShortName a shorthand, such as <code>IOException</code>
81 * @param aFullName a full name, such as <code>java.io.IOException</code>
82 * @return true iff aShortName represents the same type as aFullName
83 */
84 protected boolean isShortName(String aShortName, String aFullName)
85 {
86 if (aShortName.length() >= aFullName.length()) {
87 return false;
88 }
89
90 final String base = Utils.baseClassname(aFullName);
91 if (aShortName.length() >= aFullName.length()
92 || !base.equals(aShortName))
93 {
94 return false;
95 }
96
97 // check fully qualified import
98 if (mImports.contains(aFullName)) {
99 return true;
100 }
101
102 // check .* import
103 final int endIndex = aFullName.length() - base.length() - 1;
104 final String packageName = aFullName.substring(0, endIndex);
105 final String starImport = packageName + ".*";
106 if (mImports.contains(starImport)) {
107 return true;
108 }
109
110 // check fully qualified class from same package
111 return packageName.equals(mPackageFullIdent.getText());
112 }
113
114 /**
115 * Is exception is unchecked (subclass of <code>RuntimeException</code>
116 * or <code>Error</code>
117 *
118 * @param aException <code>Class</code> of exception to check
119 * @return true if exception is unchecked
120 * false if exception is checked
121 */
122 protected boolean isUnchecked(Class aException)
123 {
124 return isSubclass(aException, RuntimeException.class)
125 || isSubclass(aException, Error.class);
126 }
127
128 /**
129 * Checks if one class is subclass of another
130 *
131 * @param aChild <code>Class</code> of class
132 * which should be child
133 * @param aParent <code>Class</code> of class
134 * which should be parent
135 * @return true if aChild is subclass of aParent
136 * false otherwise
137 */
138 protected boolean isSubclass(Class aChild, Class aParent)
139 {
140 return (aParent != null) && (aChild != null)
141 && aParent.isAssignableFrom(aChild);
142 }
143
144 /**
145 * Return if two Strings represent the same type, inspecting the
146 * import statements if necessary
147 *
148 * @param aFirst first type declared in throws clause
149 * @param aSecond second type declared in thros tag
150 * @return true iff type names represent the same type
151 */
152 protected boolean isSameType(String aFirst, String aSecond)
153 {
154 return aFirst.equals(aSecond)
155 || isShortName(aFirst, aSecond)
156 || isShortName(aSecond, aFirst);
157 }
158
159 /** @return <code>ClassResolver</code> for current tree. */
160 private ClassResolver getClassResolver()
161 {
162 if (mClassResolver == null) {
163 mClassResolver =
164 new ClassResolver(getClassLoader(),
165 mPackageFullIdent.getText(),
166 mImports);
167 }
168 return mClassResolver;
169 }
170
171 /**
172 * Attempts to resolve the Class for a specified name.
173 * @param aClassName name of the class to resolve
174 * @return the resolved class or <code>null</code>
175 * if unable to resolve the class.
176 */
177 protected final Class resolveClass(String aClassName)
178 {
179 try {
180 return getClassResolver().resolve(aClassName);
181 }
182 catch (ClassNotFoundException e) {
183 return null;
184 }
185 }
186
187 /**
188 * Tries to load class. Logs error if unable.
189 * @param aIdent name of class which we try to load.
190 * @return <code>Class</code> for a ident.
191 */
192 protected final Class tryLoadClass(FullIdent aIdent)
193 {
194 Class clazz = resolveClass(aIdent.getText());
195 if (clazz == null) {
196 logLoadError(aIdent);
197 }
198 return clazz;
199 }
200
201 /**
202 * Logs error if unable to load class information.
203 * Abstract, should be overrided in subclasses.
204 * @param aIdent class name for which we can no load class.
205 */
206 protected abstract void logLoadError(FullIdent aIdent);
207
208 /**
209 * Collects the details of a package.
210 * @param aAST node containing the package details
211 */
212 private void processPackage(DetailAST aAST)
213 {
214 final DetailAST nameAST = (DetailAST) aAST.getFirstChild();
215 mPackageFullIdent = FullIdent.createFullIdent(nameAST);
216 }
217
218 /**
219 * Collects the details of imports.
220 * @param aAST node containing the import details
221 */
222 private void processImport(DetailAST aAST)
223 {
224 final FullIdent name = FullIdent.createFullIdentBelow(aAST);
225 if (name != null) {
226 mImports.add(name.getText());
227 }
228 }
229
230 /**
231 * Contains class's <code>FullIdent</code>
232 * and <code>Class</code> object if we can load it.
233 */
234 protected class ClassInfo
235 {
236 /** <code>FullIdent</code> associated with this class. */
237 private FullIdent mName;
238 /** <code>Class</code> object of this class if it's loadable. */
239 private Class mClass;
240 /** is class loadable. */
241 private boolean mIsLoadable;
242
243 /**
244 * Creates new instance of of class information object.
245 * @param aName <code>FullIdent</code> associated with new object.
246 * @param aClass <code>Class</code> associated with new object
247 * or null id class is not loadable.
248 */
249 public ClassInfo(FullIdent aName, Class aClass)
250 {
251 if (aName == null && aClass == null) {
252 throw new NullPointerException(
253 "ClassInfo's name or class should be non-null");
254 }
255 mName = aName;
256 setClazz(aClass);
257 }
258
259 /**
260 * Creates new instance of of class information object.
261 * @param aName <code>FullIdent</code> associated with new object.
262 */
263 public ClassInfo(FullIdent aName)
264 {
265 if (aName == null) {
266 throw new NullPointerException(
267 "ClassInfo's name should be non-null");
268 }
269 mName = aName;
270 mIsLoadable = true;
271 }
272
273 /** @return class name */
274 public final FullIdent getName()
275 {
276 return mName;
277 }
278
279 /** @return if class is loadable ot not. */
280 public final boolean isLoadable()
281 {
282 return mIsLoadable;
283 }
284
285 /** @return <code>Class</code> associated with an object. */
286 public final Class getClazz()
287 {
288 if (isLoadable() && mClass == null) {
289 setClazz(AbstractTypeAwareCheck.this.tryLoadClass(getName()));
290 }
291 return mClass;
292 }
293
294 /**
295 * Associates <code> Class</code> with an object.
296 * @param aClass <code>Class</code> to associate with.
297 */
298 public final void setClazz(Class aClass)
299 {
300 mClass = aClass;
301 mIsLoadable = (mClass != null);
302 }
303 }
304 }