View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.symboltable;
5   
6   import java.util.ArrayList;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  
13  import net.sourceforge.pmd.lang.java.typeresolution.PMDASMClassLoader;
14  import net.sourceforge.pmd.util.ClasspathClassLoader;
15  
16  /**
17   * Keeps track of the types encountered in a ASTCompilationUnit
18   */
19  public class TypeSet {
20  
21      private final PMDASMClassLoader pmdClassLoader;
22      private boolean hasAuxclasspath;
23  
24      /**
25       * The {@link TypeSet} provides type resolution for the symbol facade.
26       */
27      public TypeSet() {
28          this(TypeSet.class.getClassLoader());
29      }
30  
31      /**
32       * The {@link TypeSet} provides type resolution for the symbol facade.
33       * @param classLoader the class loader to use to search classes (could be an auxiliary class path)
34       */
35      public TypeSet(ClassLoader classLoader) {
36          ClassLoader cl = classLoader;
37          if (cl == null) {
38              cl = TypeSet.class.getClassLoader();
39          }
40          hasAuxclasspath = cl instanceof ClasspathClassLoader;
41          pmdClassLoader = PMDASMClassLoader.getInstance(cl);
42      }
43  
44      /**
45       * Whether the classloader is using the auxclasspath or not.
46       * @return <code>true</code> if the classloader is using the auxclasspath feature
47       */
48      public boolean hasAuxclasspath() {
49          return hasAuxclasspath;
50      }
51  
52      /**
53       * A resolver that can resolve a class by name. The name can be a simple name or a fully qualified name.
54       */
55      // TODO should Resolver provide a canResolve() and a resolve()? Requiring 2
56      // calls seems clunky... but so does this throwing an exception for flow
57      // control...
58      public interface Resolver {
59          /**
60           * Resolve the class by the given name
61           *
62           * @param name the name of the class, might be fully classified or not.
63           * @return the class
64           * @throws ClassNotFoundException if the class couldn't be found
65           */
66          Class<?> resolve(String name) throws ClassNotFoundException;
67      }
68  
69      /**
70       * Base Resolver class that support a {@link PMDASMClassLoader} class
71       * loader.
72       */
73      public static abstract class AbstractResolver implements Resolver {
74          /** the class loader. */
75          protected final PMDASMClassLoader pmdClassLoader;
76          /**
77           * Creates a new AbstractResolver that uses the given class loader.
78           * @param pmdClassLoader the class loader to use
79           */
80          public AbstractResolver(PMDASMClassLoader pmdClassLoader) {
81              this.pmdClassLoader = pmdClassLoader;
82          }
83      }
84  
85      /**
86       * Resolver that tries to resolve the given simple class name with the
87       * explicit import statements.
88       */
89      public static class ExplicitImportResolver extends AbstractResolver {
90          private Set<String> importStmts;
91          /**
92           * Creates a new {@link ExplicitImportResolver}.
93           * @param pmdClassLoader the class loader to use.
94           * @param importStmts the import statements
95           */
96          public ExplicitImportResolver(PMDASMClassLoader pmdClassLoader, Set<String> importStmts) {
97              super(pmdClassLoader);
98              this.importStmts = importStmts;
99          }
100         @Override
101         public Class<?> resolve(String name) throws ClassNotFoundException {
102             if (name == null) {
103                 throw new ClassNotFoundException();
104             }
105             for (String importStmt : importStmts) {
106                 if (importStmt.endsWith(name)) {
107                     return pmdClassLoader.loadClass(importStmt);
108                 }
109             }
110             throw new ClassNotFoundException("Type " + name + " not found");
111         }
112     }
113 
114     /**
115      * Resolver that uses the current package to resolve a simple class name.
116      */
117     public static class CurrentPackageResolver extends AbstractResolver {
118         private String pkg;
119         /**
120          * Creates a new {@link CurrentPackageResolver}
121          * @param pmdClassLoader the class loader to use
122          * @param pkg the package name
123          */
124         public CurrentPackageResolver(PMDASMClassLoader pmdClassLoader, String pkg) {
125             super(pmdClassLoader);
126             this.pkg = pkg;
127         }
128         @Override
129         public Class<?> resolve(String name) throws ClassNotFoundException {
130             return pmdClassLoader.loadClass(pkg + '.' + name);
131         }
132     }
133 
134     /**
135      * Resolver that resolves simple class names from the implicit import of <code>java.lang.*</code>.
136      */
137     // TODO cite the JLS section on implicit imports
138     public static class ImplicitImportResolver extends AbstractResolver {
139         /**
140          * Creates a {@link ImplicitImportResolver}
141          * @param pmdClassLoader the class loader
142          */
143         public ImplicitImportResolver(PMDASMClassLoader pmdClassLoader) {
144             super(pmdClassLoader);
145         }
146         @Override
147         public Class<?> resolve(String name) throws ClassNotFoundException {
148             return pmdClassLoader.loadClass("java.lang." + name);
149         }
150     }
151 
152     /**
153      * Resolver that uses the "on demand" import statements.
154      */
155     public static class ImportOnDemandResolver extends AbstractResolver {
156         private Set<String> importStmts;
157         /**
158          * Creates a {@link ImportOnDemandResolver}
159          * @param pmdClassLoader the class loader to use
160          * @param importStmts the import statements
161          */
162         public ImportOnDemandResolver(PMDASMClassLoader pmdClassLoader, Set<String> importStmts) {
163             super(pmdClassLoader);
164             this.importStmts = importStmts;
165         }
166         @Override
167         public Class<?> resolve(String name) throws ClassNotFoundException {
168             for (String importStmt : importStmts) {
169                 if (importStmt.endsWith("*")) {
170                     try {
171                         String importPkg = importStmt.substring(0, importStmt.indexOf('*') - 1);
172                         return pmdClassLoader.loadClass(importPkg + '.' + name);
173                     } catch (ClassNotFoundException cnfe) {
174                         // ignored as the class could be imported with the next on demand import...
175                     }
176                 }
177             }
178             throw new ClassNotFoundException("Type " + name + " not found");
179         }
180     }
181 
182     /**
183      * Resolver that resolves primitive types such as int or double.
184      */
185     public static class PrimitiveTypeResolver implements Resolver {
186         private Map<String, Class<?>> primitiveTypes = new HashMap<>();
187         /**
188          * Creates a new {@link PrimitiveTypeResolver}.
189          */
190         @SuppressWarnings("PMD.AvoidUsingShortType")
191         public PrimitiveTypeResolver() {
192             primitiveTypes.put("int", int.class);
193             primitiveTypes.put("float", float.class);
194             primitiveTypes.put("double", double.class);
195             primitiveTypes.put("long", long.class);
196             primitiveTypes.put("boolean", boolean.class);
197             primitiveTypes.put("byte", byte.class);
198             primitiveTypes.put("short", short.class);
199             primitiveTypes.put("char", char.class);
200         }
201         @Override
202         public Class<?> resolve(String name) throws ClassNotFoundException {
203             if (!primitiveTypes.containsKey(name)) {
204                 throw new ClassNotFoundException(name);
205             }
206             return primitiveTypes.get(name);
207         }
208     }
209 
210     /**
211      * Resolver that resolves the "void" type.
212      */
213     public static class VoidResolver implements Resolver {
214         @Override
215         public Class<?> resolve(String name) throws ClassNotFoundException {
216             if ("void".equals(name)) {
217                 return void.class;
218             }
219             throw new ClassNotFoundException(name);
220         }
221     }
222 
223     /**
224      * Resolver that simply loads the class by name. This only works if the class name
225      * is given as a fully qualified name.
226      */
227     public static class FullyQualifiedNameResolver extends AbstractResolver {
228         /**
229          * Creates a {@link FullyQualifiedNameResolver}
230          * @param pmdClassLoader the class loader to use
231          */
232         public FullyQualifiedNameResolver(PMDASMClassLoader pmdClassLoader) {
233             super(pmdClassLoader);
234         }
235         @Override
236         public Class<?> resolve(String name) throws ClassNotFoundException {
237             if (name == null) {
238                 throw new ClassNotFoundException();
239             }
240             return pmdClassLoader.loadClass(name);
241         }
242     }
243 
244     private String pkg;
245     private Set<String> imports = new HashSet<>();
246     private List<Resolver> resolvers = new ArrayList<>();
247 
248     public void setASTCompilationUnitPackage(String pkg) {
249         this.pkg = pkg;
250     }
251 
252     public String getASTCompilationUnitPackage() {
253         return pkg;
254     }
255 
256     /**
257      * Adds a import to the list of imports
258      * @param importString the import to add
259      */
260     public void addImport(String importString) {
261         imports.add(importString);
262     }
263 
264     public int getImportsCount() {
265         return imports.size();
266     }
267 
268     public Set<String> getExplicitImports() {
269         return imports;
270     }
271 
272     /**
273      * Resolves a class by its name using all known resolvers.
274      * @param name the name of the class, can be a simple name or a fully qualified name.
275      * @return the class
276      * @throws ClassNotFoundException if there is no such class
277      */
278     public Class<?> findClass(String name) throws ClassNotFoundException {
279         // we don't build the resolvers until now since we first want to get all
280         // the imports
281         if (resolvers.isEmpty()) {
282             buildResolvers();
283         }
284 
285         for (Resolver resolver : resolvers) {
286             try {
287                 return resolver.resolve(name);
288             } catch (ClassNotFoundException cnfe) {
289                 // ignored, maybe another resolver will find the class
290             }
291         }
292 
293         throw new ClassNotFoundException("Type " + name + " not found");
294     }
295 
296     private void buildResolvers() {
297         resolvers.add(new PrimitiveTypeResolver());
298         resolvers.add(new VoidResolver());
299         resolvers.add(new ExplicitImportResolver(pmdClassLoader, imports));
300         resolvers.add(new CurrentPackageResolver(pmdClassLoader, pkg));
301         resolvers.add(new ImplicitImportResolver(pmdClassLoader));
302         resolvers.add(new ImportOnDemandResolver(pmdClassLoader, imports));
303         resolvers.add(new FullyQualifiedNameResolver(pmdClassLoader));
304     }
305 }