001package serp.bytecode;
002
003import java.io.*;
004import java.util.*;
005
006import serp.bytecode.lowlevel.*;
007import serp.bytecode.visitor.*;
008import 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 */
027public 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}