PageRenderTime 38ms CodeModel.GetById 24ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-2-pre4/bsh/BshClassManager.java

#
Java | 507 lines | 227 code | 47 blank | 233 comment | 40 complexity | bdf7a6a8c732fa9d8d5f9c03b1580e5f 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 bsh;
 35
 36import java.net.*;
 37import java.util.*;
 38import java.io.IOException;
 39import java.io.*;
 40import java.lang.reflect.Method;
 41import java.lang.reflect.Modifier;
 42
 43/**
 44	BshClassManager manages all classloading in BeanShell.
 45	It also supports a dynamically loaded extension (bsh.classpath package)
 46	which allows classpath extension and class file reloading.
 47
 48	Currently the extension relies on 1.2 for BshClassLoader and weak 
 49	references.  
 50
 51	See http://www.beanshell.org/manual/classloading.html for details
 52	on the bsh classloader architecture.
 53	<p>
 54
 55	Bsh has a multi-tiered class loading architecture.  No class loader is
 56	used unless/until the classpath is modified or a class is reloaded.
 57	<p>
 58*/
 59/*
 60	Implementation notes:
 61
 62	Note: we may need some synchronization in here
 63
 64	Note on version dependency:  This base class is JDK 1.1 compatible,
 65	however we are forced to use weak references in the full featured
 66	implementation (the optional bsh.classpath package) to accomodate all of
 67	the fleeting namespace listeners as they fall out of scope.  (NameSpaces
 68	must be informed if the class space changes so that they can un-cache
 69	names).  
 70	<p>
 71
 72	Perhaps a simpler idea would be to have entities that reference cached
 73	types always perform a light weight check with a counter / reference
 74	value and use that to detect changes in the namespace.  This puts the 
 75	burden on the consumer to check at appropriate times, but could eliminate
 76	the need for the listener system in many places and the necessity of weak 
 77	references in this package.
 78	<p>
 79*/
 80public class BshClassManager
 81{
 82	/** Identifier for no value item.  Use a hashtable as a Set. */
 83	private static Object NOVALUE = new Object(); 
 84	
 85	/**
 86		An external classloader supplied by the setClassLoader() command.
 87	*/
 88	private ClassLoader externalClassLoader;
 89
 90	/**
 91		Global cache for things we know are classes.
 92		Note: these should probably be re-implemented with Soft references.
 93		(as opposed to strong or Weak)
 94	*/
 95    protected transient Hashtable absoluteClassCache = new Hashtable();
 96	/**
 97		Global cache for things we know are *not* classes.
 98		Note: these should probably be re-implemented with Soft references.
 99		(as opposed to strong or Weak)
100	*/
101    protected transient Hashtable absoluteNonClasses = new Hashtable();
102
103	/**
104		Caches for resolved object and static methods.
105		We keep these maps separate to support fast lookup in the general case
106		where the method may be either.
107	*/
108	protected transient Hashtable resolvedObjectMethods = new Hashtable();
109	protected transient Hashtable resolvedStaticMethods = new Hashtable();
110
111	/**
112		Create a new instance of the class manager.  
113		Class manager instnaces are now associated with the interpreter.
114
115		@see bsh.Interpreter.getClassManager()
116		@see bsh.Interpreter.setClassLoader( ClassLoader )
117	*/
118	public static BshClassManager createClassManager() 
119	{
120		BshClassManager manager;
121
122		// Do we have the necessary jdk1.2 packages and optional package?
123		if ( Capabilities.classExists("java.lang.ref.WeakReference") 
124			&& Capabilities.classExists("java.util.HashMap") 
125			&& Capabilities.classExists("bsh.classpath.ClassManagerImpl") 
126		) 
127			try {
128				// Try to load the module
129				// don't refer to it directly here or we're dependent upon it
130				Class clas = Class.forName( "bsh.classpath.ClassManagerImpl" );
131				manager = (BshClassManager)clas.newInstance();
132			} catch ( Exception e ) {
133				throw new InterpreterError("Error loading classmanager: "+e);
134			}
135		else 
136			manager = new BshClassManager();
137
138		return manager;
139	}
140
141	public boolean classExists( String name ) {
142		return ( classForName( name ) != null );
143	}
144
145	/**
146		Load the specified class by name, taking into account added classpath
147		and reloaded classes, etc.
148		@return the class or null
149	*/
150	public Class classForName( String name ) {
151		try {
152			return plainClassForName( name );
153		} catch ( ClassNotFoundException e ) {
154			return null;
155		}
156	}
157
158	/**
159		Perform a plain Class.forName() or call the externally provided
160		classloader.
161		If a BshClassManager implementation is loaded the call will be 
162		delegated to it, to allow for additional hooks.
163		<p/>
164
165		This simply wraps that bottom level class lookup call and provides a 
166		central point for monitoring and handling certain Java version 
167		dependent bugs, etc.
168
169		@see #classForName( String )
170		@return the class
171	*/
172	public Class plainClassForName( String name ) 
173		throws ClassNotFoundException
174	{
175		Class c = null;
176
177		try {
178			if ( externalClassLoader != null )
179				c = externalClassLoader.loadClass( name );
180			else
181				c = Class.forName( name );
182
183			cacheClassInfo( name, c );
184
185		/*
186			Original note: Jdk under Win is throwing these to
187			warn about lower case / upper case possible mismatch.
188			e.g. bsh.console bsh.Console
189	
190			Update: Prior to 1.3 we were squeltching NoClassDefFoundErrors 
191			which was very annoying.  I cannot reproduce the original problem 
192			and this was never a valid solution.  If there are legacy VMs that
193			have problems we can include a more specific test for them here.
194		*/
195		} catch ( NoClassDefFoundError e ) {
196			throw noClassDefFound( name, e );
197		}
198
199		return c;
200	}
201
202	/**
203		Get a resource URL using the BeanShell classpath
204		@param path should be an absolute path
205	*/
206	public URL getResource( String path ) 
207	{
208		if ( externalClassLoader != null )
209		{
210			// classloader wants no leading slash
211			return externalClassLoader.getResource( path.substring(1) );
212		} else
213			return Interpreter.class.getResource( path );
214	}
215	/**
216		Get a resource stream using the BeanShell classpath
217		@param path should be an absolute path
218	*/
219	public InputStream getResourceAsStream( String path ) 
220	{
221		if ( externalClassLoader != null )
222		{
223			// classloader wants no leading slash
224			return externalClassLoader.getResourceAsStream( path.substring(1) );
225		} else
226			return Interpreter.class.getResourceAsStream( path );
227	}
228
229	/**
230		Cache info about whether name is a class or not.
231		@param value 
232			if value is non-null, cache the class
233			if value is null, set the flag that it is *not* a class to
234			speed later resolution
235	*/
236	public void cacheClassInfo( String name, Class value ) {
237		if ( value != null )
238			absoluteClassCache.put( name, value );
239		else
240			absoluteNonClasses.put( name, NOVALUE );
241	}
242
243	/**
244		Cache a resolved (possibly overloaded) method based on the 
245		argument types used to invoke it, subject to classloader change.
246		Static and Object methods are cached separately to support fast lookup
247		in the general case where either will do.
248	*/
249	public void cacheResolvedMethod( 
250		Class clas, Object [] args, Method method ) 
251	{
252		if ( Interpreter.DEBUG )
253			Interpreter.debug(
254				"cacheResolvedMethod putting: " + clas +" "+ method );
255		
256		SignatureKey sk = new SignatureKey( clas, method.getName(), args );
257		if ( Modifier.isStatic( method.getModifiers() ) )
258			resolvedStaticMethods.put( sk, method );
259		else
260			resolvedObjectMethods.put( sk, method );
261	}
262
263	/**
264		Return a previously cached resolved method.
265		@param onlyStatic specifies that only a static method may be returned.
266		@return the Method or null
267	*/
268	public Method getResolvedMethod( 
269		Class clas, String methodName, Object [] args, boolean onlyStatic  ) 
270	{
271		SignatureKey sk = new SignatureKey( clas, methodName, args );
272
273		// Try static and then object, if allowed
274		// Note that the Java compiler should not allow both.
275		Method method = (Method)resolvedStaticMethods.get( sk );
276		if ( method == null && !onlyStatic)
277			method = (Method)resolvedObjectMethods.get( sk );
278
279		if ( Interpreter.DEBUG )
280		{
281			if ( method == null )
282				Interpreter.debug(
283					"getResolvedMethod cache MISS: " + clas +" - "+methodName );
284			else
285				Interpreter.debug(
286					"getResolvedMethod cache HIT: " + clas +" - " +method );
287		}
288		return method;
289	}
290
291	/**
292		Clear the caches in BshClassManager
293		@see public void #reset() for external usage
294	*/
295	protected void clearCaches() 
296	{
297    	absoluteNonClasses = new Hashtable();
298    	absoluteClassCache = new Hashtable();
299    	resolvedObjectMethods = new Hashtable();
300    	resolvedStaticMethods = new Hashtable();
301	}
302
303	/**
304		Set an external class loader.  BeanShell will use this at the same 
305		point it would otherwise use the plain Class.forName().
306		i.e. if no explicit classpath management is done from the script
307		(addClassPath(), setClassPath(), reloadClasses()) then BeanShell will
308		only use the supplied classloader.  If additional classpath management
309		is done then BeanShell will perform that in addition to the supplied
310		external classloader.
311		However BeanShell is not currently able to reload
312		classes supplied through the external classloader.
313	*/
314	public void setClassLoader( ClassLoader externalCL ) 
315	{
316		externalClassLoader = externalCL;
317		classLoaderChanged();
318	}
319
320	public void addClassPath( URL path )
321		throws IOException {
322	}
323
324	/**
325		Clear all loaders and start over.  No class loading.
326	*/
327	public void reset() { 
328		clearCaches();
329	}
330
331	/**
332		Set a new base classpath and create a new base classloader.
333		This means all types change. 
334	*/
335	public void setClassPath( URL [] cp ) 
336		throws UtilEvalError
337	{
338		throw cmUnavailable();
339	}
340
341	/**
342		Overlay the entire path with a new class loader.
343		Set the base path to the user path + base path.
344
345		No point in including the boot class path (can't reload thos).
346	*/
347	public void reloadAllClasses() throws UtilEvalError {
348		throw cmUnavailable();
349	}
350
351	/**
352		Reloading classes means creating a new classloader and using it
353		whenever we are asked for classes in the appropriate space.
354		For this we use a DiscreteFilesClassLoader
355	*/
356	public void reloadClasses( String [] classNames )
357		throws UtilEvalError 
358	{
359		throw cmUnavailable();
360	}
361
362	/**
363		Reload all classes in the specified package: e.g. "com.sun.tools"
364
365		The special package name "<unpackaged>" can be used to refer 
366		to unpackaged classes.
367	*/
368	public void reloadPackage( String pack ) 
369		throws UtilEvalError 
370	{
371		throw cmUnavailable();
372	}
373
374	/**
375		This has been removed from the interface to shield the core from the
376		rest of the classpath package. If you need the classpath you will have
377		to cast the classmanager to its impl.
378
379		public BshClassPath getClassPath() throws ClassPathException;
380	*/
381
382	/**
383		Support for "import *;"
384		Hide details in here as opposed to NameSpace.
385	*/
386	protected void doSuperImport() 
387		throws UtilEvalError 
388	{
389		throw cmUnavailable();
390	}
391
392	/**
393		A "super import" ("import *") operation has been performed.
394	*/
395	protected boolean hasSuperImport() 
396	{
397		return false;
398	}
399
400	/**
401		Return the name or null if none is found,
402		Throw an ClassPathException containing detail if name is ambigous.
403	*/
404	protected String getClassNameByUnqName( String name ) 
405		throws UtilEvalError 
406	{
407		throw cmUnavailable();
408	}
409
410	public void addListener( Listener l ) { }
411
412	public void removeListener( Listener l ) { }
413
414	public void dump( PrintWriter pw ) { 
415		pw.println("BshClassManager: no class manager."); 
416	}
417
418	protected void classLoaderChanged() { }
419
420	/**
421		Annotate the NoClassDefFoundError with some info about the class
422		we were trying to load.
423	*/
424	protected static Error noClassDefFound( String className, Error e ) {
425		return new NoClassDefFoundError(
426			"A class required by class: "+className +" could not be loaded:\n"
427			+e.toString() );
428	}
429
430	protected static UtilEvalError cmUnavailable() {
431		return new Capabilities.Unavailable(
432			"ClassLoading features unavailable.");
433	}
434
435	public static interface Listener 
436	{
437		public void classLoaderChanged();
438	}
439
440	/**
441		SignatureKey serves as a hash of a method signature on a class 
442		for fast lookup of overloaded and general resolved Java methods. 
443		<p>
444	*/
445	/*
446		Note: is using SignatureKey in this way dangerous?  In the pathological
447		case a user could eat up memory caching every possible combination of
448		argument types to an untyped method.  Maybe we could be smarter about
449		it by ignoring the types of untyped parameter positions?  The method
450		resolver could return a set of "hints" for the signature key caching?
451
452		There is also the overhead of creating one of these for every method
453		dispatched.  What is the alternative?
454	*/
455	static class SignatureKey
456	{
457		Class clas;
458		Class [] types;
459		String methodName;
460		int hashCode = 0;
461
462		SignatureKey( Class clas, String methodName, Object [] args ) {
463			this.clas = clas;
464			this.methodName = methodName;
465			this.types = Reflect.getTypes( args );
466		}
467
468		public int hashCode() 
469		{ 
470			if ( hashCode == 0 ) 
471			{
472				hashCode = clas.hashCode() * methodName.hashCode();
473				if ( types == null ) // no args method
474					return hashCode; 
475				for( int i =0; i < types.length; i++ ) {
476					int hc = types[i] == null ? 21 : types[i].hashCode();
477					hashCode = hashCode*(i+1) + hc;
478				}
479			}
480			return hashCode;
481		}
482
483		public boolean equals( Object o ) { 
484			SignatureKey target = (SignatureKey)o;
485			if ( types == null )
486				return target.types == null;
487			if ( clas != target.clas )
488				return false;
489			if ( !methodName.equals( target.methodName ) )
490				return false;
491			if ( types.length != target.types.length )
492				return false;
493			for( int i =0; i< types.length; i++ )
494			{
495				if ( types[i]==null ) 
496				{
497					if ( !(target.types[i]==null) )
498						return false;
499				} else 
500					if ( !types[i].equals( target.types[i] ) )
501						return false;
502			}
503
504			return true;
505		}
506	}
507}