001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package org.gjt.sp.jedit.bsh.classpath;
033:
034: import java.net.*;
035: import java.util.*;
036: import java.lang.ref.*;
037: import java.io.IOException;
038: import java.io.*;
039: import org.gjt.sp.jedit.bsh.classpath.BshClassPath.ClassSource;
040: import org.gjt.sp.jedit.bsh.classpath.BshClassPath.JarClassSource;
041: import org.gjt.sp.jedit.bsh.classpath.BshClassPath.GeneratedClassSource;
042: import org.gjt.sp.jedit.bsh.BshClassManager;
043: import org.gjt.sp.jedit.bsh.ClassPathException;
044: import org.gjt.sp.jedit.bsh.Interpreter; // for debug()
045: import org.gjt.sp.jedit.bsh.UtilEvalError;
046:
047: /**
048: <pre>
049: Manage all classloading in BeanShell.
050: Allows classpath extension and class file reloading.
051:
052: This class holds the implementation of the BshClassManager so that it
053: can be separated from the core package.
054:
055: This class currently relies on 1.2 for BshClassLoader and weak references.
056: Is there a workaround for weak refs? If so we could make this work
057: with 1.1 by supplying our own classloader code...
058:
059: See "http://www.beanshell.org/manual/classloading.html" for details
060: on the bsh classloader architecture.
061:
062: Bsh has a multi-tiered class loading architecture. No class loader is
063: created unless/until a class is generated, the classpath is modified,
064: or a class is reloaded.
065:
066: Note: we may need some synchronization in here
067:
068: Note on jdk1.2 dependency:
069:
070: We are forced to use weak references here to accomodate all of the
071: fleeting namespace listeners. (NameSpaces must be informed if the class
072: space changes so that they can un-cache names). I had the interesting
073: thought that a way around this would be to implement BeanShell's own
074: garbage collector... Then I came to my senses and said - screw it,
075: class re-loading will require 1.2.
076:
077: ---------------------
078:
079: Classloading precedence:
080:
081: in-script evaluated class (scripted class)
082: in-script added / modified classpath
083:
084: optionally, external classloader
085: optionally, thread context classloader
086:
087: plain Class.forName()
088: source class (.java file in classpath)
089:
090: </pre>
091:
092: */
093: public class ClassManagerImpl extends BshClassManager {
094: static final String BSH_PACKAGE = "org.gjt.sp.jedit.bsh";
095: /**
096: The classpath of the base loader. Initially and upon reset() this is
097: an empty instance of BshClassPath. It grows as paths are added or is
098: reset when the classpath is explicitly set. This could also be called
099: the "extension" class path, but is not strictly confined to added path
100: (could be set arbitrarily by setClassPath())
101: */
102: private BshClassPath baseClassPath;
103: private boolean super Import;
104:
105: /**
106: This is the full blown classpath including baseClassPath (extensions),
107: user path, and java bootstrap path (rt.jar)
108:
109: This is lazily constructed and further (and more importantly) lazily
110: intialized in components because mapping the full path could be
111: expensive.
112:
113: The full class path is a composite of:
114: baseClassPath (user extension) : userClassPath : bootClassPath
115: in that order.
116: */
117: private BshClassPath fullClassPath;
118:
119: // ClassPath Change listeners
120: private Vector listeners = new Vector();
121: private ReferenceQueue refQueue = new ReferenceQueue();
122:
123: /**
124: This handles extension / modification of the base classpath
125: The loader to use where no mapping of reloaded classes exists.
126:
127: The baseLoader is initially null meaning no class loader is used.
128: */
129: private BshClassLoader baseLoader;
130:
131: /**
132: Map by classname of loaders to use for reloaded classes
133: */
134: private Map loaderMap;
135:
136: /**
137: Used by BshClassManager singleton constructor
138: */
139: public ClassManagerImpl() {
140: reset();
141: }
142:
143: /**
144: @return the class or null
145: */
146: public Class classForName(String name) {
147: // check positive cache
148: Class c = (Class) absoluteClassCache.get(name);
149: if (c != null)
150: return c;
151:
152: // check negative cache
153: if (absoluteNonClasses.get(name) != null) {
154: if (Interpreter.DEBUG)
155: Interpreter.debug("absoluteNonClass list hit: " + name);
156: return null;
157: }
158:
159: if (Interpreter.DEBUG)
160: Interpreter.debug("Trying to load class: " + name);
161:
162: // Check explicitly mapped (reloaded) class...
163: ClassLoader overlayLoader = getLoaderForClass(name);
164: if (overlayLoader != null) {
165: try {
166: c = overlayLoader.loadClass(name);
167: } catch (Exception e) {
168: // used to squeltch this... changed for 1.3
169: // see BshClassManager
170: } catch (NoClassDefFoundError e2) {
171: throw noClassDefFound(name, e2);
172: }
173:
174: // Should be there since it was explicitly mapped
175: // throw an error?
176: }
177:
178: // insure that core classes are loaded from the same loader
179: if (c == null) {
180: if (name.startsWith(BSH_PACKAGE))
181: try {
182: c = Interpreter.class.getClassLoader().loadClass(
183: name);
184: } catch (ClassNotFoundException e) {
185: }
186: }
187:
188: // Check classpath extension / reloaded classes
189: if (c == null) {
190: if (baseLoader != null)
191: try {
192: c = baseLoader.loadClass(name);
193: } catch (ClassNotFoundException e) {
194: }
195: }
196:
197: // Optionally try external classloader
198: if (c == null) {
199: if (externalClassLoader != null)
200: try {
201: c = externalClassLoader.loadClass(name);
202: } catch (ClassNotFoundException e) {
203: }
204: }
205:
206: // Optionally try context classloader
207: // Note that this might be a security violation
208: // is catching the SecurityException sufficient for all environments?
209: // or do we need a way to turn this off completely?
210: if (c == null) {
211: try {
212: ClassLoader contextClassLoader = Thread.currentThread()
213: .getContextClassLoader();
214: if (contextClassLoader != null)
215: c = Class.forName(name, true, contextClassLoader);
216: } catch (ClassNotFoundException e) { // fall through
217: } catch (SecurityException e) {
218: } // fall through
219: }
220:
221: // try plain class forName()
222: if (c == null)
223: try {
224: c = plainClassForName(name);
225: } catch (ClassNotFoundException e) {
226: }
227:
228: // Try scripted class
229: if (c == null)
230: c = loadSourceClass(name);
231:
232: // Cache result (or null for not found)
233: // Note: plainClassForName already caches, so it will be redundant
234: // in that case, however this process only happens once
235: cacheClassInfo(name, c);
236:
237: return c;
238: }
239:
240: /**
241: Get a resource URL using the BeanShell classpath
242: @param path should be an absolute path
243: */
244: public URL getResource(String path) {
245: URL url = null;
246: if (baseLoader != null)
247: // classloader wants no leading slash
248: url = baseLoader.getResource(path.substring(1));
249: if (url == null)
250: url = super .getResource(path);
251: return url;
252: }
253:
254: /**
255: Get a resource stream using the BeanShell classpath
256: @param path should be an absolute path
257: */
258: public InputStream getResourceAsStream(String path) {
259: InputStream in = null;
260: if (baseLoader != null) {
261: // classloader wants no leading slash
262: in = baseLoader.getResourceAsStream(path.substring(1));
263: }
264: if (in == null) {
265: in = super .getResourceAsStream(path);
266: }
267: return in;
268: }
269:
270: ClassLoader getLoaderForClass(String name) {
271: return (ClassLoader) loaderMap.get(name);
272: }
273:
274: // Classpath mutators
275:
276: /**
277: */
278: public void addClassPath(URL path) throws IOException {
279: if (baseLoader == null)
280: setClassPath(new URL[] { path });
281: else {
282: // opportunity here for listener in classpath
283: baseLoader.addURL(path);
284: baseClassPath.add(path);
285: classLoaderChanged();
286: }
287: }
288:
289: /**
290: Clear all classloading behavior and class caches and reset to
291: initial state.
292: */
293: public void reset() {
294: baseClassPath = new BshClassPath("baseClassPath");
295: baseLoader = null;
296: loaderMap = new HashMap();
297: classLoaderChanged(); // calls clearCaches() for us.
298: }
299:
300: /**
301: Set a new base classpath and create a new base classloader.
302: This means all types change.
303: */
304: public void setClassPath(URL[] cp) {
305: baseClassPath.setPath(cp);
306: initBaseLoader();
307: loaderMap = new HashMap();
308: classLoaderChanged();
309: }
310:
311: /**
312: Overlay the entire path with a new class loader.
313: Set the base path to the user path + base path.
314:
315: No point in including the boot class path (can't reload thos).
316: */
317: public void reloadAllClasses() throws ClassPathException {
318: BshClassPath bcp = new BshClassPath("temp");
319: bcp.addComponent(baseClassPath);
320: bcp.addComponent(BshClassPath.getUserClassPath());
321: setClassPath(bcp.getPathComponents());
322: }
323:
324: /**
325: init the baseLoader from the baseClassPath
326: */
327: private void initBaseLoader() {
328: baseLoader = new BshClassLoader(this , baseClassPath);
329: }
330:
331: // class reloading
332:
333: /**
334: Reloading classes means creating a new classloader and using it
335: whenever we are asked for classes in the appropriate space.
336: For this we use a DiscreteFilesClassLoader
337: */
338: public void reloadClasses(String[] classNames)
339: throws ClassPathException {
340: // validate that it is a class here?
341:
342: // init base class loader if there is none...
343: if (baseLoader == null)
344: initBaseLoader();
345:
346: DiscreteFilesClassLoader.ClassSourceMap map = new DiscreteFilesClassLoader.ClassSourceMap();
347:
348: for (int i = 0; i < classNames.length; i++) {
349: String name = classNames[i];
350:
351: // look in baseLoader class path
352: ClassSource classSource = baseClassPath
353: .getClassSource(name);
354:
355: // look in user class path
356: if (classSource == null) {
357: BshClassPath.getUserClassPath().insureInitialized();
358: classSource = BshClassPath.getUserClassPath()
359: .getClassSource(name);
360: }
361:
362: // No point in checking boot class path, can't reload those.
363: // else we could have used fullClassPath above.
364:
365: if (classSource == null)
366: throw new ClassPathException(
367: "Nothing known about class: " + name);
368:
369: // JarClassSource is not working... just need to implement it's
370: // getCode() method or, if we decide to, allow the BshClassManager
371: // to handle it... since it is a URLClassLoader and can handle JARs
372: if (classSource instanceof JarClassSource)
373: throw new ClassPathException("Cannot reload class: "
374: + name + " from source: " + classSource);
375:
376: map.put(name, classSource);
377: }
378:
379: // Create classloader for the set of classes
380: ClassLoader cl = new DiscreteFilesClassLoader(this , map);
381:
382: // map those classes the loader in the overlay map
383: Iterator it = map.keySet().iterator();
384: while (it.hasNext())
385: loaderMap.put((String) it.next(), cl);
386:
387: classLoaderChanged();
388: }
389:
390: /**
391: Reload all classes in the specified package: e.g. "com.sun.tools"
392:
393: The special package name "<unpackaged>" can be used to refer
394: to unpackaged classes.
395: */
396: public void reloadPackage(String pack) throws ClassPathException {
397: Collection classes = baseClassPath.getClassesForPackage(pack);
398:
399: if (classes == null)
400: classes = BshClassPath.getUserClassPath()
401: .getClassesForPackage(pack);
402:
403: // no point in checking boot class path, can't reload those
404:
405: if (classes == null)
406: throw new ClassPathException(
407: "No classes found for package: " + pack);
408:
409: reloadClasses((String[]) classes.toArray(new String[0]));
410: }
411:
412: /**
413: Unimplemented
414: For this we'd have to store a map by location as well as name...
415:
416: public void reloadPathComponent( URL pc ) throws ClassPathException {
417: throw new ClassPathException("Unimplemented!");
418: }
419: */
420:
421: // end reloading
422: /**
423: Get the full blown classpath.
424: */
425: public BshClassPath getClassPath() throws ClassPathException {
426: if (fullClassPath != null)
427: return fullClassPath;
428:
429: fullClassPath = new BshClassPath("BeanShell Full Class Path");
430: fullClassPath.addComponent(BshClassPath.getUserClassPath());
431: try {
432: fullClassPath.addComponent(BshClassPath.getBootClassPath());
433: } catch (ClassPathException e) {
434: System.err.println("Warning: can't get boot class path");
435: }
436: fullClassPath.addComponent(baseClassPath);
437:
438: return fullClassPath;
439: }
440:
441: /**
442: Support for "import *;"
443: Hide details in here as opposed to NameSpace.
444: */
445: public void doSuperImport() throws UtilEvalError {
446: // Should we prevent it from happening twice?
447:
448: try {
449: getClassPath().insureInitialized();
450: // prime the lookup table
451: getClassNameByUnqName("");
452:
453: // always true now
454: //getClassPath().setNameCompletionIncludeUnqNames(true);
455:
456: } catch (ClassPathException e) {
457: throw new UtilEvalError("Error importing classpath " + e);
458: }
459:
460: super Import = true;
461: }
462:
463: protected boolean hasSuperImport() {
464: return super Import;
465: }
466:
467: /**
468: Return the name or null if none is found,
469: Throw an ClassPathException containing detail if name is ambigous.
470: */
471: public String getClassNameByUnqName(String name)
472: throws ClassPathException {
473: return getClassPath().getClassNameByUnqName(name);
474: }
475:
476: public void addListener(Listener l) {
477: listeners.addElement(new WeakReference(l, refQueue));
478:
479: // clean up old listeners
480: Reference deadref;
481: while ((deadref = refQueue.poll()) != null) {
482: boolean ok = listeners.removeElement(deadref);
483: if (ok) {
484: //System.err.println("cleaned up weak ref: "+deadref);
485: } else {
486: if (Interpreter.DEBUG)
487: Interpreter
488: .debug("tried to remove non-existent weak ref: "
489: + deadref);
490: }
491: }
492: }
493:
494: public void removeListener(Listener l) {
495: throw new Error("unimplemented");
496: }
497:
498: public ClassLoader getBaseLoader() {
499: return baseLoader;
500: }
501:
502: /**
503: Get the BeanShell classloader.
504: public ClassLoader getClassLoader() {
505: }
506: */
507:
508: /*
509: Impl Notes:
510: We add the bytecode source and the "reload" the class, which causes the
511: BshClassLoader to be initialized and create a DiscreteFilesClassLoader
512: for the bytecode.
513:
514: @exception ClassPathException can be thrown by reloadClasses
515: */
516: public Class defineClass(String name, byte[] code) {
517: baseClassPath.setClassSource(name, new GeneratedClassSource(
518: code));
519: try {
520: reloadClasses(new String[] { name });
521: } catch (ClassPathException e) {
522: throw new org.gjt.sp.jedit.bsh.InterpreterError(
523: "defineClass: " + e);
524: }
525: return classForName(name);
526: }
527:
528: /**
529: Clear global class cache and notify namespaces to clear their
530: class caches.
531:
532: The listener list is implemented with weak references so that we
533: will not keep every namespace in existence forever.
534: */
535: protected void classLoaderChanged() {
536: // clear the static caches in BshClassManager
537: clearCaches();
538:
539: Vector toRemove = new Vector(); // safely remove
540: for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
541: WeakReference wr = (WeakReference) e.nextElement();
542: Listener l = (Listener) wr.get();
543: if (l == null) // garbage collected
544: toRemove.add(wr);
545: else
546: l.classLoaderChanged();
547: }
548: for (Enumeration e = toRemove.elements(); e.hasMoreElements();)
549: listeners.removeElement(e.nextElement());
550: }
551:
552: public void dump(PrintWriter i) {
553: i.println("Bsh Class Manager Dump: ");
554: i.println("----------------------- ");
555: i.println("baseLoader = " + baseLoader);
556: i.println("loaderMap= " + loaderMap);
557: i.println("----------------------- ");
558: i.println("baseClassPath = " + baseClassPath);
559: }
560:
561: }
|