KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > bsh > classpath > ClassManagerImpl


1 /*****************************************************************************
2  * *
3  * This file is part of the BeanShell Java Scripting distribution. *
4  * Documentation and updates may be found at http://www.beanshell.org/ *
5  * *
6  * Sun Public License Notice: *
7  * *
8  * The contents of this file are subject to the Sun Public License Version *
9  * 1.0 (the "License"); you may not use this file except in compliance with *
10  * the License. A copy of the License is available at http://www.sun.com *
11  * *
12  * The Original Code is BeanShell. The Initial Developer of the Original *
13  * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
14  * (C) 2000. All Rights Reserved. *
15  * *
16  * GNU Public License Notice: *
17  * *
18  * Alternatively, the contents of this file may be used under the terms of *
19  * the GNU Lesser General Public License (the "LGPL"), in which case the *
20  * provisions of LGPL are applicable instead of those above. If you wish to *
21  * allow use of your version of this file only under the terms of the LGPL *
22  * and not to allow others to use your version of this file under the SPL, *
23  * indicate your decision by deleting the provisions above and replace *
24  * them with the notice and other provisions required by the LGPL. If you *
25  * do not delete the provisions above, a recipient may use your version of *
26  * this file under either the SPL or the LGPL. *
27  * *
28  * Patrick Niemeyer (pat@pat.net) *
29  * Author of Learning Java, O'Reilly & Associates *
30  * http://www.pat.net/~pat/ *
31  * *
32  *****************************************************************************/

33
34 package bsh.classpath;
35
36 import java.net.*;
37 import java.util.*;
38 import java.lang.ref.*;
39 import java.io.IOException JavaDoc;
40 import java.io.*;
41 import bsh.classpath.BshClassPath.ClassSource;
42 import bsh.classpath.BshClassPath.JarClassSource;
43 import bsh.classpath.BshClassPath.GeneratedClassSource;
44 import bsh.BshClassManager;
45 import bsh.ClassPathException;
46 import bsh.Interpreter; // for debug()
47
import bsh.UtilEvalError;
48
49 /**
50     <pre>
51     Manage all classloading in BeanShell.
52     Allows classpath extension and class file reloading.
53
54     This class holds the implementation of the BshClassManager so that it
55     can be separated from the core package.
56
57     This class currently relies on 1.2 for BshClassLoader and weak references.
58     Is there a workaround for weak refs? If so we could make this work
59     with 1.1 by supplying our own classloader code...
60
61     See "http://www.beanshell.org/manual/classloading.html" for details
62     on the bsh classloader architecture.
63
64     Bsh has a multi-tiered class loading architecture. No class loader is
65     created unless/until a class is generated, the classpath is modified,
66     or a class is reloaded.
67
68     Note: we may need some synchronization in here
69
70     Note on jdk1.2 dependency:
71
72     We are forced to use weak references here to accomodate all of the
73     fleeting namespace listeners. (NameSpaces must be informed if the class
74     space changes so that they can un-cache names). I had the interesting
75     thought that a way around this would be to implement BeanShell's own
76     garbage collector... Then I came to my senses and said - screw it,
77     class re-loading will require 1.2.
78
79     ---------------------
80
81     Classloading precedence:
82
83     in-script evaluated class (scripted class)
84     in-script added / modified classpath
85
86     optionally, external classloader
87     optionally, thread context classloader
88
89     plain Class.forName()
90     source class (.java file in classpath)
91
92     </pre>
93
94 */

95 public class ClassManagerImpl extends BshClassManager
96 {
97     static final String JavaDoc BSH_PACKAGE = "bsh";
98     /**
99         The classpath of the base loader. Initially and upon reset() this is
100         an empty instance of BshClassPath. It grows as paths are added or is
101         reset when the classpath is explicitly set. This could also be called
102         the "extension" class path, but is not strictly confined to added path
103         (could be set arbitrarily by setClassPath())
104     */

105     private BshClassPath baseClassPath;
106     private boolean superImport;
107
108     /**
109         This is the full blown classpath including baseClassPath (extensions),
110         user path, and java bootstrap path (rt.jar)
111
112         This is lazily constructed and further (and more importantly) lazily
113         intialized in components because mapping the full path could be
114         expensive.
115
116         The full class path is a composite of:
117             baseClassPath (user extension) : userClassPath : bootClassPath
118         in that order.
119     */

120     private BshClassPath fullClassPath;
121
122     // ClassPath Change listeners
123
private Vector listeners = new Vector();
124     private ReferenceQueue refQueue = new ReferenceQueue();
125
126     /**
127         This handles extension / modification of the base classpath
128         The loader to use where no mapping of reloaded classes exists.
129
130         The baseLoader is initially null meaning no class loader is used.
131     */

132     private BshClassLoader baseLoader;
133
134     /**
135         Map by classname of loaders to use for reloaded classes
136     */

137     private Map loaderMap;
138
139     /**
140         Used by BshClassManager singleton constructor
141     */

142     public ClassManagerImpl() {
143         reset();
144     }
145
146     /**
147         @return the class or null
148     */

149     public Class JavaDoc classForName( String JavaDoc name )
150     {
151         // check positive cache
152
Class JavaDoc c = (Class JavaDoc)absoluteClassCache.get(name);
153         if (c != null )
154             return c;
155
156         // check negative cache
157
if ( absoluteNonClasses.get(name)!=null ) {
158             if ( Interpreter.DEBUG )
159                 Interpreter.debug("absoluteNonClass list hit: "+name);
160             return null;
161         }
162
163         if ( Interpreter.DEBUG )
164             Interpreter.debug("Trying to load class: "+name);
165
166         // Check explicitly mapped (reloaded) class...
167
ClassLoader JavaDoc overlayLoader = getLoaderForClass( name );
168         if ( overlayLoader != null )
169         {
170             try {
171                 c = overlayLoader.loadClass(name);
172             } catch ( Exception JavaDoc e ) {
173             // used to squeltch this... changed for 1.3
174
// see BshClassManager
175
} catch ( NoClassDefFoundError JavaDoc e2 ) {
176                 throw noClassDefFound( name, e2 );
177             }
178
179             // Should be there since it was explicitly mapped
180
// throw an error?
181
}
182
183         // insure that core classes are loaded from the same loader
184
if ( c == null ) {
185             if ( name.startsWith( BSH_PACKAGE ) )
186                 try {
187                     c = Interpreter.class.getClassLoader().loadClass( name );
188                 } catch ( ClassNotFoundException JavaDoc e ) {}
189         }
190
191         // Check classpath extension / reloaded classes
192
if ( c == null ) {
193             if ( baseLoader != null )
194                 try {
195                     c = baseLoader.loadClass( name );
196                 } catch ( ClassNotFoundException JavaDoc e ) {}
197         }
198
199         // Optionally try external classloader
200
if ( c == null ) {
201             if ( externalClassLoader != null )
202                 try {
203                     c = externalClassLoader.loadClass( name );
204                 } catch ( ClassNotFoundException JavaDoc e ) {}
205         }
206
207         // Optionally try context classloader
208
// Note that this might be a security violation
209
// is catching the SecurityException sufficient for all environments?
210
// or do we need a way to turn this off completely?
211
if ( c == null )
212         {
213             try {
214                 ClassLoader JavaDoc contextClassLoader =
215                     Thread.currentThread().getContextClassLoader();
216                 if ( contextClassLoader != null )
217                     c = Class.forName( name, true, contextClassLoader );
218             } catch ( ClassNotFoundException JavaDoc e ) { // fall through
219
} catch ( SecurityException JavaDoc e ) { } // fall through
220
}
221
222         // try plain class forName()
223
if ( c == null )
224             try {
225                 c = plainClassForName( name );
226             } catch ( ClassNotFoundException JavaDoc e ) {}
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 JavaDoc path )
245     {
246         URL url = null;
247         if ( baseLoader != null )
248             // classloader wants no leading slash
249
url = baseLoader.getResource( path.substring(1) );
250         if ( url == null )
251             url = super.getResource( path );
252         return url;
253     }
254
255     /**
256         Get a resource stream using the BeanShell classpath
257         @param path should be an absolute path
258     */

259     public InputStream getResourceAsStream( String JavaDoc path )
260     {
261         InputStream in = null;
262         if ( baseLoader != null )
263         {
264             // classloader wants no leading slash
265
in = baseLoader.getResourceAsStream( path.substring(1) );
266         }
267         if ( in == null )
268         {
269             in = super.getResourceAsStream( path );
270         }
271         return in;
272     }
273
274     ClassLoader JavaDoc getLoaderForClass( String JavaDoc name ) {
275         return (ClassLoader JavaDoc)loaderMap.get( name );
276     }
277
278     // Classpath mutators
279

280     /**
281     */

282     public void addClassPath( URL path )
283         throws IOException JavaDoc
284     {
285         if ( baseLoader == null )
286             setClassPath( new URL [] { path } );
287         else {
288             // opportunity here for listener in classpath
289
baseLoader.addURL( path );
290             baseClassPath.add( path );
291             classLoaderChanged();
292         }
293     }
294
295     /**
296         Clear all classloading behavior and class caches and reset to
297         initial state.
298     */

299     public void reset()
300     {
301         baseClassPath = new BshClassPath("baseClassPath");
302         baseLoader = null;
303         loaderMap = new HashMap();
304         classLoaderChanged(); // calls clearCaches() for us.
305
}
306
307     /**
308         Set a new base classpath and create a new base classloader.
309         This means all types change.
310     */

311     public void setClassPath( URL [] cp ) {
312         baseClassPath.setPath( cp );
313         initBaseLoader();
314         loaderMap = new HashMap();
315         classLoaderChanged();
316     }
317
318     /**
319         Overlay the entire path with a new class loader.
320         Set the base path to the user path + base path.
321
322         No point in including the boot class path (can't reload thos).
323     */

324     public void reloadAllClasses() throws ClassPathException
325     {
326         BshClassPath bcp = new BshClassPath("temp");
327         bcp.addComponent( baseClassPath );
328         bcp.addComponent( BshClassPath.getUserClassPath() );
329         setClassPath( bcp.getPathComponents() );
330     }
331
332     /**
333         init the baseLoader from the baseClassPath
334     */

335     private void initBaseLoader() {
336         baseLoader = new BshClassLoader( this, baseClassPath );
337     }
338
339     // class reloading
340

341     /**
342         Reloading classes means creating a new classloader and using it
343         whenever we are asked for classes in the appropriate space.
344         For this we use a DiscreteFilesClassLoader
345     */

346     public void reloadClasses( String JavaDoc [] classNames )
347         throws ClassPathException
348     {
349         // validate that it is a class here?
350

351         // init base class loader if there is none...
352
if ( baseLoader == null )
353             initBaseLoader();
354
355         DiscreteFilesClassLoader.ClassSourceMap map =
356             new DiscreteFilesClassLoader.ClassSourceMap();
357
358         for (int i=0; i< classNames.length; i++) {
359             String JavaDoc name = classNames[i];
360
361             // look in baseLoader class path
362
ClassSource classSource = baseClassPath.getClassSource( name );
363
364             // look in user class path
365
if ( classSource == null ) {
366                 BshClassPath.getUserClassPath().insureInitialized();
367                 classSource = BshClassPath.getUserClassPath().getClassSource(
368                     name );
369             }
370
371             // No point in checking boot class path, can't reload those.
372
// else we could have used fullClassPath above.
373

374             if ( classSource == null )
375                 throw new ClassPathException("Nothing known about class: "
376                     +name );
377
378             // JarClassSource is not working... just need to implement it's
379
// getCode() method or, if we decide to, allow the BshClassManager
380
// to handle it... since it is a URLClassLoader and can handle JARs
381
if ( classSource instanceof JarClassSource )
382                 throw new ClassPathException("Cannot reload class: "+name+
383                     " from source: "+ classSource );
384
385             map.put( name, classSource );
386         }
387
388         // Create classloader for the set of classes
389
ClassLoader JavaDoc cl = new DiscreteFilesClassLoader( this, map );
390
391         // map those classes the loader in the overlay map
392
Iterator it = map.keySet().iterator();
393         while ( it.hasNext() )
394             loaderMap.put( (String JavaDoc)it.next(), cl );
395
396         classLoaderChanged();
397     }
398
399     /**
400         Reload all classes in the specified package: e.g. "com.sun.tools"
401
402         The special package name "<unpackaged>" can be used to refer
403         to unpackaged classes.
404     */

405     public void reloadPackage( String JavaDoc pack )
406         throws ClassPathException
407     {
408         Collection classes =
409             baseClassPath.getClassesForPackage( pack );
410
411         if ( classes == null )
412             classes =
413                 BshClassPath.getUserClassPath().getClassesForPackage( pack );
414
415         // no point in checking boot class path, can't reload those
416

417         if ( classes == null )
418             throw new ClassPathException("No classes found for package: "+pack);
419
420         reloadClasses( (String JavaDoc[])classes.toArray( new String JavaDoc[0] ) );
421     }
422
423     /**
424         Unimplemented
425         For this we'd have to store a map by location as well as name...
426
427     public void reloadPathComponent( URL pc ) throws ClassPathException {
428         throw new ClassPathException("Unimplemented!");
429     }
430     */

431
432     // end reloading
433

434     /**
435         Get the full blown classpath.
436     */

437     public BshClassPath getClassPath() throws ClassPathException
438     {
439         if ( fullClassPath != null )
440             return fullClassPath;
441     
442         fullClassPath = new BshClassPath("BeanShell Full Class Path");
443         fullClassPath.addComponent( BshClassPath.getUserClassPath() );
444         try {
445             fullClassPath.addComponent( BshClassPath.getBootClassPath() );
446         } catch ( ClassPathException e ) {
447             System.err.println("Warning: can't get boot class path");
448         }
449         fullClassPath.addComponent( baseClassPath );
450
451         return fullClassPath;
452     }
453
454     /**
455         Support for "import *;"
456         Hide details in here as opposed to NameSpace.
457     */

458     public void doSuperImport()
459         throws UtilEvalError
460     {
461         // Should we prevent it from happening twice?
462

463         try {
464             getClassPath().insureInitialized();
465             // prime the lookup table
466
getClassNameByUnqName( "" ) ;
467
468             // always true now
469
//getClassPath().setNameCompletionIncludeUnqNames(true);
470

471         } catch ( ClassPathException e ) {
472             throw new UtilEvalError("Error importing classpath "+ e );
473         }
474
475         superImport = true;
476     }
477
478     protected boolean hasSuperImport() { return superImport; }
479
480     /**
481         Return the name or null if none is found,
482         Throw an ClassPathException containing detail if name is ambigous.
483     */

484     public String JavaDoc getClassNameByUnqName( String JavaDoc name )
485         throws ClassPathException
486     {
487         return getClassPath().getClassNameByUnqName( name );
488     }
489
490     public void addListener( Listener l ) {
491         listeners.addElement( new WeakReference( l, refQueue) );
492
493         // clean up old listeners
494
Reference deadref;
495         while ( (deadref = refQueue.poll()) != null ) {
496             boolean ok = listeners.removeElement( deadref );
497             if ( ok ) {
498                 //System.err.println("cleaned up weak ref: "+deadref);
499
} else {
500                 if ( Interpreter.DEBUG ) Interpreter.debug(
501                     "tried to remove non-existent weak ref: "+deadref);
502             }
503         }
504     }
505
506     public void removeListener( Listener l ) {
507         throw new Error JavaDoc("unimplemented");
508     }
509
510     public ClassLoader JavaDoc getBaseLoader() {
511         return baseLoader;
512     }
513
514     /**
515         Get the BeanShell classloader.
516     public ClassLoader getClassLoader() {
517     }
518     */

519
520     /*
521         Impl Notes:
522         We add the bytecode source and the "reload" the class, which causes the
523         BshClassLoader to be initialized and create a DiscreteFilesClassLoader
524         for the bytecode.
525
526         @exception ClassPathException can be thrown by reloadClasses
527     */

528     public Class JavaDoc defineClass( String JavaDoc name, byte [] code )
529     {
530         baseClassPath.setClassSource( name, new GeneratedClassSource( code ) );
531         try {
532             reloadClasses( new String JavaDoc [] { name } );
533         } catch ( ClassPathException e ) {
534             throw new bsh.InterpreterError("defineClass: "+e);
535         }
536         return classForName( name );
537     }
538
539     /**
540         Clear global class cache and notify namespaces to clear their
541         class caches.
542
543         The listener list is implemented with weak references so that we
544         will not keep every namespace in existence forever.
545     */

546     protected void classLoaderChanged()
547     {
548         // clear the static caches in BshClassManager
549
clearCaches();
550
551         Vector toRemove = new Vector(); // safely remove
552
for ( Enumeration e = listeners.elements(); e.hasMoreElements(); )
553         {
554             WeakReference wr = (WeakReference)e.nextElement();
555             Listener l = (Listener)wr.get();
556             if ( l == null ) // garbage collected
557
toRemove.add( wr );
558             else
559               l.classLoaderChanged();
560         }
561         for( Enumeration e = toRemove.elements(); e.hasMoreElements(); )
562             listeners.removeElement( e.nextElement() );
563     }
564
565     public void dump( PrintWriter i )
566     {
567         i.println("Bsh Class Manager Dump: ");
568         i.println("----------------------- ");
569         i.println("baseLoader = "+baseLoader);
570         i.println("loaderMap= "+loaderMap);
571         i.println("----------------------- ");
572         i.println("baseClassPath = "+baseClassPath);
573     }
574
575 }
576
Popular Tags