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
87 name = _names.getExternalForm(name, false);
88
89 BCClass cached = checkCache(name);
90 if (cached != null)
91 return cached;
92
93
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
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 }