View Javadoc

1   package serp.bytecode;
2   
3   import java.io.*;
4   import java.util.*;
5   
6   import serp.bytecode.lowlevel.*;
7   import serp.bytecode.visitor.*;
8   import serp.util.*;
9   
10  /***
11   * The Project represents a working set of classes. It caches parsed
12   * bytecode and is responsible for bytecode class creation. Currently
13   * changes made in one class are <strong>not</strong> reflected in other
14   * classes, though this will be an option in the future.
15   *
16   * <p>Bytecode that has been parsed is held in a cache so that retrieving
17   * a class with the same name multiple times always returns the same
18   * {@link BCClass} instance.</p>
19   *
20   * <p>A future goal is to eventually have facilities for traversing jars
21   * or directory structures to find classes that meet a given criteria (such
22   * as implementing a given interface, etc) and to perform operations on entire
23   * projects, similar to aspect-oriented programming.</p>
24   *
25   * @author Abe White
26   */
27  public class Project implements VisitAcceptor {
28      private final String _name;
29      private final HashMap _cache = new HashMap();
30      private final NameCache _names = new NameCache();
31  
32      /***
33       * Default constructor.
34       */
35      public Project() {
36          this(null);
37      }
38  
39      /***
40       * Construct a named project.
41       */
42      public Project(String name) {
43          _name = name;
44      }
45  
46      /***
47       * Return the project name, or null if unset.
48       */
49      public String getName() {
50          return _name;
51      }
52  
53      /***
54       * Return the name cache, which includes utilities for converting names
55       * from internal to external form and vice versa.
56       */
57      public NameCache getNameCache() {
58          return _names;
59      }
60  
61      /***
62       * Load a class with the given name.
63       *
64       * @see #loadClass(String,ClassLoader)
65       */
66      public BCClass loadClass(String name) {
67          return loadClass(name, null);
68      }
69  
70      /***
71       * Load the bytecode for the class with the given name.
72       * If a {@link BCClass} with the given name already exists in this project,
73       * it will be returned. Otherwise, a new {@link BCClass} will be created
74       * with the given name and returned. If the name represents an existing
75       * type, the returned instance will contain the parsed bytecode for
76       * that type. If the name is of a primitive or array type, the returned
77       * instance will act accordingly.
78       *
79       * @param name the name of the class, including package
80       * @param loader the class loader to use to search for an existing
81       * class with the given name; if null defaults to the
82       * context loader of the current thread
83       * @throws RuntimeException on parse error
84       */
85      public BCClass loadClass(String name, ClassLoader loader) {
86          // convert to proper Class.forName() form
87          name = _names.getExternalForm(name, false);
88  
89          BCClass cached = checkCache(name);
90          if (cached != null)
91              return cached;
92  
93          // check for existing type
94          if (loader == null)
95              loader = Thread.currentThread().getContextClassLoader();
96          try {
97              return loadClass(Strings.toClass(name, loader));
98          } catch (Exception e) {
99          }
100 
101         String componentName = _names.getComponentName(name);
102         BCClass ret = new BCClass(this);
103         if (componentName != null)
104             ret.setState(new ArrayState(name, componentName));
105         else {
106             ret.setState(new ObjectState(_names));
107             ret.setName(name);
108             ret.setSuperclass(Object.class);
109         }
110         cache(name, ret);
111         return ret;
112     }
113 
114     /***
115      * Load the bytecode for the given class.
116      * If a {@link BCClass} with the name of the given class already exists in
117      * this project, it will be returned. Otherwise, the bytecode of the given
118      * class will be parsed and returned as a new {@link BCClass}. If the
119      * given class is an array or primitive type, the returned instance will
120      * act accordingly.
121      *
122      * @param type the class to parse
123      * @throws RuntimeException on parse error
124      */
125     public BCClass loadClass(Class type) {
126         BCClass cached = checkCache(type.getName());
127         if (cached != null)
128             return cached;
129 
130         BCClass ret = new BCClass(this);
131         if (type.isPrimitive())
132             ret.setState(new PrimitiveState(type, _names));
133         else if (type.isArray())
134             ret.setState(new ArrayState(type.getName(), _names.getExternalForm
135                 (type.getComponentType().getName(), false)));
136         else {
137             ret.setState(new ObjectState(_names));
138             try {
139                 ret.read(type);
140             } catch (IOException ioe) {
141                 throw new RuntimeException(ioe.toString());
142             }
143         }
144         cache(type.getName(), ret);
145         return ret;
146     }
147 
148     /***
149      * Load the bytecode from the given class file.
150      * If this project already contains the class in the given file, it will
151      * be returned. Otherwise a new {@link BCClass} will be created from the
152      * given bytecode.
153      *
154      * @throws RuntimeException on parse error
155      */
156     public BCClass loadClass(File classFile) {
157         return loadClass(classFile, null);
158     }
159 
160     /***
161      * Load the bytecode from the given class file.
162      * If this project already contains the class in the given file, it will
163      * be returned. Otherwise a new {@link BCClass} will be created from the
164      * given bytecode.
165      *
166      * @throws RuntimeException on parse error
167      */
168     public BCClass loadClass(File classFile, ClassLoader loader) {
169         // parse the bytecode from the file
170         BCClass ret = new BCClass(this);
171         ret.setState(new ObjectState(_names));
172         try {
173             ret.read(classFile, loader);
174         } catch (IOException ioe) {
175             throw new RuntimeException(ioe.toString());
176         }
177 
178         String name = ret.getName();
179         BCClass cached = checkCache(name);
180         if (cached != null)
181             return cached;
182 
183         cache(name, ret);
184         return ret;
185     }
186 
187     /***
188      * Load the bytecode from the given stream.
189      * If this project already contains the class in the given stream,
190      * it will be returned. Otherwise a new {@link BCClass} will be created
191      * from the given bytecode.
192      *
193      * @throws RuntimeException on parse error
194      */
195     public BCClass loadClass(InputStream in) {
196         return loadClass(in, null);
197     }
198 
199     /***
200      * Load the bytecode from the given stream.
201      * If this project already contains the class in the given stream,
202      * it will be returned. Otherwise a new {@link BCClass} will be created
203      * from the given bytecode.
204      *
205      * @throws RuntimeException on parse error
206      */
207     public BCClass loadClass(InputStream in, ClassLoader loader) {
208         BCClass ret = new BCClass(this);
209         ret.setState(new ObjectState(_names));
210         try {
211             ret.read(in, loader);
212         } catch (IOException ioe) {
213             throw new RuntimeException(ioe.toString());
214         }
215 
216         String name = ret.getName();
217         BCClass cached = checkCache(name);
218         if (cached != null)
219             return cached;
220 
221         cache(name, ret);
222         return ret;
223     }
224 
225     /***
226      * Import the given bytecode from another project. If a {@link BCClass}
227      * with the same name already exists in this project, it will be returned.
228      * Otherwise, a new {@link BCClass} will be created from the
229      * information in the given class.
230      */
231     public BCClass loadClass(BCClass bc) {
232         String name = bc.getName();
233         BCClass cached = checkCache(name);
234         if (cached != null)
235             return cached;
236 
237         BCClass ret = new BCClass(this);
238         if (bc.isPrimitive())
239             ret.setState(new PrimitiveState(bc.getType(), _names));
240         else if (bc.isArray())
241             ret.setState(new ArrayState(bc.getName(), bc.getComponentName()));
242         else {
243             ret.setState(new ObjectState(_names));
244             ret.read(bc);
245         }
246 
247         cache(name, ret);
248         return ret;
249     }
250 
251     /***
252      * Clears all classes from this project.
253      */
254     public void clear() {
255         Collection values = _cache.values();
256         BCClass bc;
257         for (Iterator itr = values.iterator(); itr.hasNext();) {
258             bc = (BCClass) itr.next();
259             itr.remove();
260             bc.invalidate();
261         }
262         _names.clear();
263     }
264 
265     /***
266      * Remove a class from this project. After removal, the result of any
267      * further operations on the class is undefined.
268      *
269      * @return true if the class belonged to this project, false otherwise
270      */
271     public boolean removeClass(String type) {
272         return removeClass(checkCache(type));
273     }
274 
275     /***
276      * Remove a class from this project. After removal, the result of any
277      * further operations on the class is undefined.
278      *
279      * @return true if the class belonged to this project, false otherwise
280      */
281     public boolean removeClass(Class type) {
282         if (type == null)
283             return false;
284         return removeClass(checkCache(type.getName()));
285     }
286 
287     /***
288      * Remove a class from this project. After removal, the result of any
289      * further operations on the class is undefined.
290      *
291      * @return true if the class belonged to this project, false otherwise
292      */
293     public boolean removeClass(BCClass type) {
294         if (type == null)
295             return false;
296         if (!removeFromCache(type.getName(), type))
297             return false;
298         type.invalidate();
299         return true;
300     }
301 
302     /***
303      * Return all loaded classes in the project.
304      */
305     public BCClass[] getClasses() {
306         Collection values = _cache.values();
307         return (BCClass[]) values.toArray(new BCClass[values.size()]);
308     }
309 
310     /***
311      * Return true if the project already contains the given class.
312      */
313     public boolean containsClass(String type) {
314         return _cache.containsKey(type);
315     }
316 
317     /***
318      * Return true if the project already contains the given class.
319      */
320     public boolean containsClass(Class type) {
321         return (type == null) ? false : containsClass(type.getName());
322     }
323 
324     /***
325      * Return true if the project already contains the given class.
326      */
327     public boolean containsClass(BCClass type) {
328         return (type == null) ? false : containsClass(type.getName());
329     }
330 
331     public void acceptVisit(BCVisitor visit) {
332         visit.enterProject(this);
333         BCClass[] classes = getClasses();
334         for (int i = 0; i < classes.length; i++)
335             classes[i].acceptVisit(visit);
336         visit.exitProject(this);
337     }
338 
339     /***
340      * Renames the given class within this project. Used internally by
341      * {@link BCClass} instances when their name is modified.
342      *
343      * @throws IllegalStateException if a class with the new name already exists
344      */
345     void renameClass(String oldName, String newName, BCClass bc) {
346         if (oldName.equals(newName))
347             return;
348 
349         BCClass cached = (BCClass) checkCache(newName);
350         if (cached != null)
351             throw new IllegalStateException("A class with name " + newName +
352                 " already exists in this project");
353 
354         removeFromCache(oldName, bc);
355         cache(newName, bc);
356     }
357 
358     /***
359      * Check the cache for a loaded type.
360      */
361     private BCClass checkCache(String name) {
362         return (BCClass) _cache.get(name);
363     }
364 
365     /***
366      * Cache a class.
367      */
368     private void cache(String name, BCClass bc) {
369         _cache.put(name, bc);
370     }
371 
372     /***
373      * Remove a cached class.
374      */
375     private boolean removeFromCache(String name, BCClass bc) {
376         BCClass rem = (BCClass) checkCache(name);
377         if (rem != bc)
378             return false;
379         _cache.remove(name);
380         return true;
381     }
382 }