PageRenderTime 213ms CodeModel.GetById 180ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/bsh/classpath/ClassManagerImpl.java

#
Java | 575 lines | 270 code | 71 blank | 234 comment | 61 complexity | d1eb3fb6075d31d5ce0e6b523677fe05 MD5 | raw file
  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
 34package org.gjt.sp.jedit.bsh.classpath;
 35
 36import java.net.*;
 37import java.util.*;
 38import java.lang.ref.*;
 39import java.io.IOException;
 40import java.io.*;
 41import org.gjt.sp.jedit.bsh.classpath.BshClassPath.ClassSource;
 42import org.gjt.sp.jedit.bsh.classpath.BshClassPath.JarClassSource;
 43import org.gjt.sp.jedit.bsh.classpath.BshClassPath.GeneratedClassSource;
 44import org.gjt.sp.jedit.bsh.BshClassManager;
 45import org.gjt.sp.jedit.bsh.ClassPathException;
 46import org.gjt.sp.jedit.bsh.Interpreter;  // for debug()
 47import org.gjt.sp.jedit.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*/
 95public class ClassManagerImpl extends BshClassManager
 96{
 97	static final String BSH_PACKAGE = "org.gjt.sp.jedit.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 classForName( String name )
150	{
151		// check positive cache
152		Class c = (Class)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 overlayLoader = getLoaderForClass( name );
168		if ( overlayLoader != null )
169		{
170			try {
171				c = overlayLoader.loadClass(name);
172			} catch ( Exception e ) {
173			// used to squeltch this... changed for 1.3
174			// see BshClassManager
175			} catch ( NoClassDefFoundError 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 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 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 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 contextClassLoader = 
215					Thread.currentThread().getContextClassLoader();
216				if ( contextClassLoader != null )
217					c = Class.forName( name, true, contextClassLoader );
218			} catch ( ClassNotFoundException e ) { // fall through
219			} catch ( SecurityException e ) { } // fall through
220		}
221
222		// try plain class forName()
223		if ( c == null )
224			try {
225				c = plainClassForName( name );
226			} catch ( ClassNotFoundException 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 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 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 getLoaderForClass( String name ) {
275		return (ClassLoader)loaderMap.get( name );
276	}
277
278	// Classpath mutators
279
280	/**
281	*/
282	public void addClassPath( URL path ) 
283		throws IOException 
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 [] 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 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 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)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 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[])classes.toArray( new String[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 getClassNameByUnqName( String 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("unimplemented");
508	}
509
510	public ClassLoader 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 defineClass( String name, byte [] code ) 
529	{
530		baseClassPath.setClassSource( name, new GeneratedClassSource( code ) );
531		try {
532			reloadClasses( new String [] { name } );
533		} catch ( ClassPathException e ) {
534			throw new org.gjt.sp.jedit.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}