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