001    package serp.bytecode;
002    
003    import java.io.*;
004    import java.util.*;
005    
006    import serp.bytecode.lowlevel.*;
007    import serp.bytecode.visitor.*;
008    import serp.util.*;
009    
010    /**
011     * The Project represents a working set of classes. It caches parsed
012     * bytecode and is responsible for bytecode class creation. Currently
013     * changes made in one class are <strong>not</strong> reflected in other
014     * classes, though this will be an option in the future.
015     *
016     * <p>Bytecode that has been parsed is held in a cache so that retrieving
017     * a class with the same name multiple times always returns the same
018     * {@link BCClass} instance.</p>
019     *
020     * <p>A future goal is to eventually have facilities for traversing jars
021     * or directory structures to find classes that meet a given criteria (such
022     * as implementing a given interface, etc) and to perform operations on entire
023     * projects, similar to aspect-oriented programming.</p>
024     *
025     * @author Abe White
026     */
027    public class Project implements VisitAcceptor {
028        private final String _name;
029        private final HashMap _cache = new HashMap();
030        private final NameCache _names = new NameCache();
031    
032        /**
033         * Default constructor.
034         */
035        public Project() {
036            this(null);
037        }
038    
039        /**
040         * Construct a named project.
041         */
042        public Project(String name) {
043            _name = name;
044        }
045    
046        /**
047         * Return the project name, or null if unset.
048         */
049        public String getName() {
050            return _name;
051        }
052    
053        /**
054         * Return the name cache, which includes utilities for converting names
055         * from internal to external form and vice versa.
056         */
057        public NameCache getNameCache() {
058            return _names;
059        }
060    
061        /**
062         * Load a class with the given name.
063         *
064         * @see #loadClass(String,ClassLoader)
065         */
066        public BCClass loadClass(String name) {
067            return loadClass(name, null);
068        }
069    
070        /**
071         * Load the bytecode for the class with the given name.
072         * If a {@link BCClass} with the given name already exists in this project,
073         * it will be returned. Otherwise, a new {@link BCClass} will be created
074         * with the given name and returned. If the name represents an existing
075         * type, the returned instance will contain the parsed bytecode for
076         * that type. If the name is of a primitive or array type, the returned
077         * instance will act accordingly.
078         *
079         * @param name the name of the class, including package
080         * @param loader the class loader to use to search for an existing
081         * class with the given name; if null defaults to the
082         * context loader of the current thread
083         * @throws RuntimeException on parse error
084         */
085        public BCClass loadClass(String name, ClassLoader loader) {
086            // convert to proper Class.forName() form
087            name = _names.getExternalForm(name, false);
088    
089            BCClass cached = checkCache(name);
090            if (cached != null)
091                return cached;
092    
093            // check for existing type
094            if (loader == null)
095                loader = Thread.currentThread().getContextClassLoader();
096            try {
097                return loadClass(Strings.toClass(name, loader));
098            } catch (Exception e) {
099            }
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    }