PageRenderTime 289ms CodeModel.GetById 270ms app.highlight 15ms RepoModel.GetById 0ms app.codeStats 1ms

/jEdit/tags/jedit-4-3-pre5/bsh/BshClassManager.java

#
Java | 634 lines | 307 code | 58 blank | 269 comment | 52 complexity | 9f58ee23870741f0ce071b2ff08ef701 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		The interpreter which created the class manager 
 86		This is used to load scripted classes from source files.
 87	*/
 88	private Interpreter declaringInterpreter;
 89	
 90	/**
 91		An external classloader supplied by the setClassLoader() command.
 92	*/
 93	private ClassLoader externalClassLoader;
 94
 95	/**
 96		Global cache for things we know are classes.
 97		Note: these should probably be re-implemented with Soft references.
 98		(as opposed to strong or Weak)
 99	*/
100    protected transient Hashtable absoluteClassCache = new Hashtable();
101	/**
102		Global cache for things we know are *not* classes.
103		Note: these should probably be re-implemented with Soft references.
104		(as opposed to strong or Weak)
105	*/
106    protected transient Hashtable absoluteNonClasses = new Hashtable();
107
108	/**
109		Caches for resolved object and static methods.
110		We keep these maps separate to support fast lookup in the general case
111		where the method may be either.
112	*/
113	protected transient Hashtable resolvedObjectMethods = new Hashtable();
114	protected transient Hashtable resolvedStaticMethods = new Hashtable();
115
116	protected transient Hashtable definingClasses = new Hashtable();
117	protected transient Hashtable definingClassesBaseNames = new Hashtable();
118
119	/**
120		Create a new instance of the class manager.  
121		Class manager instnaces are now associated with the interpreter.
122
123		@see bsh.Interpreter.getClassManager()
124		@see bsh.Interpreter.setClassLoader( ClassLoader )
125	*/
126	public static BshClassManager createClassManager( Interpreter interpreter ) 
127	{
128		BshClassManager manager;
129
130		// Do we have the necessary jdk1.2 packages and optional package?
131		if ( Capabilities.classExists("java.lang.ref.WeakReference") 
132			&& Capabilities.classExists("java.util.HashMap") 
133			&& Capabilities.classExists("bsh.classpath.ClassManagerImpl") 
134		) 
135			try {
136				// Try to load the module
137				// don't refer to it directly here or we're dependent upon it
138				Class clas = Class.forName( "bsh.classpath.ClassManagerImpl" );
139				manager = (BshClassManager)clas.newInstance();
140			} catch ( Exception e ) {
141				throw new InterpreterError("Error loading classmanager: "+e);
142			}
143		else 
144			manager = new BshClassManager();
145
146		if ( interpreter == null )
147			interpreter = new Interpreter();
148		manager.declaringInterpreter = interpreter;
149		return manager;
150	}
151
152	public boolean classExists( String name ) {
153		return ( classForName( name ) != null );
154	}
155
156	/**
157		Load the specified class by name, taking into account added classpath
158		and reloaded classes, etc.
159		@return the class or null
160	*/
161	public Class classForName( String name ) 
162	{
163		if ( isClassBeingDefined( name ) )
164			throw new InterpreterError(
165				"Attempting to load class in the process of being defined: "
166				+name );
167
168		Class clas = null;
169		try {
170			clas = plainClassForName( name );
171		} catch ( ClassNotFoundException e ) { /*ignore*/ }
172
173		// try scripted class
174		if ( clas == null ) 
175			clas = loadSourceClass( name );
176
177		return clas;
178	}
179	
180	// Move me to classpath/ClassManagerImpl???
181	protected Class loadSourceClass( String name )
182	{
183		String fileName = "/"+name.replace('.','/')+".java";
184		InputStream in = getResourceAsStream( fileName );
185		if ( in == null )
186			return null;
187
188		try {
189			System.out.println("Loading class from source file: "+fileName);
190			declaringInterpreter.eval( new InputStreamReader(in) );
191		} catch ( EvalError e ) {
192			// ignore
193			System.err.println( e );
194		}
195		try {
196			return plainClassForName( name );
197		} catch ( ClassNotFoundException e ) {
198			System.err.println("Class not found in source file: "+name );
199			return null;
200		}
201	}
202
203	/**
204		Perform a plain Class.forName() or call the externally provided
205		classloader.
206		If a BshClassManager implementation is loaded the call will be 
207		delegated to it, to allow for additional hooks.
208		<p/>
209
210		This simply wraps that bottom level class lookup call and provides a 
211		central point for monitoring and handling certain Java version 
212		dependent bugs, etc.
213
214		@see #classForName( String )
215		@return the class
216	*/
217	public Class plainClassForName( String name ) 
218		throws ClassNotFoundException
219	{
220		Class c = null;
221
222		try {
223			if ( externalClassLoader != null )
224				c = externalClassLoader.loadClass( name );
225			else
226				c = Class.forName( name );
227
228			cacheClassInfo( name, c );
229
230		/*
231			Original note: Jdk under Win is throwing these to
232			warn about lower case / upper case possible mismatch.
233			e.g. bsh.console bsh.Console
234	
235			Update: Prior to 1.3 we were squeltching NoClassDefFoundErrors 
236			which was very annoying.  I cannot reproduce the original problem 
237			and this was never a valid solution.  If there are legacy VMs that
238			have problems we can include a more specific test for them here.
239		*/
240		} catch ( NoClassDefFoundError e ) {
241			throw noClassDefFound( name, e );
242		}
243
244		return c;
245	}
246
247	/**
248		Get a resource URL using the BeanShell classpath
249		@param path should be an absolute path
250	*/
251	public URL getResource( String path ) 
252	{
253		if ( externalClassLoader != null )
254		{
255			// classloader wants no leading slash
256			return externalClassLoader.getResource( path.substring(1) );
257		} else
258			return Interpreter.class.getResource( path );
259	}
260	/**
261		Get a resource stream using the BeanShell classpath
262		@param path should be an absolute path
263	*/
264	public InputStream getResourceAsStream( String path ) 
265	{
266		if ( externalClassLoader != null )
267		{
268			// classloader wants no leading slash
269			return externalClassLoader.getResourceAsStream( path.substring(1) );
270		} else
271			return Interpreter.class.getResourceAsStream( path );
272	}
273
274	/**
275		Cache info about whether name is a class or not.
276		@param value 
277			if value is non-null, cache the class
278			if value is null, set the flag that it is *not* a class to
279			speed later resolution
280	*/
281	public void cacheClassInfo( String name, Class value ) {
282		if ( value != null )
283			absoluteClassCache.put( name, value );
284		else
285			absoluteNonClasses.put( name, NOVALUE );
286	}
287
288	/**
289		Cache a resolved (possibly overloaded) method based on the 
290		argument types used to invoke it, subject to classloader change.
291		Static and Object methods are cached separately to support fast lookup
292		in the general case where either will do.
293	*/
294	public void cacheResolvedMethod( 
295		Class clas, Class [] types, Method method ) 
296	{
297		if ( Interpreter.DEBUG )
298			Interpreter.debug(
299				"cacheResolvedMethod putting: " + clas +" "+ method );
300		
301		SignatureKey sk = new SignatureKey( clas, method.getName(), types );
302		if ( Modifier.isStatic( method.getModifiers() ) )
303			resolvedStaticMethods.put( sk, method );
304		else
305			resolvedObjectMethods.put( sk, method );
306	}
307
308	/**
309		Return a previously cached resolved method.
310		@param onlyStatic specifies that only a static method may be returned.
311		@return the Method or null
312	*/
313	protected Method getResolvedMethod( 
314		Class clas, String methodName, Class [] types, boolean onlyStatic  ) 
315	{
316		SignatureKey sk = new SignatureKey( clas, methodName, types );
317
318		// Try static and then object, if allowed
319		// Note that the Java compiler should not allow both.
320		Method method = (Method)resolvedStaticMethods.get( sk );
321		if ( method == null && !onlyStatic)
322			method = (Method)resolvedObjectMethods.get( sk );
323
324		if ( Interpreter.DEBUG )
325		{
326			if ( method == null )
327				Interpreter.debug(
328					"getResolvedMethod cache MISS: " + clas +" - "+methodName );
329			else
330				Interpreter.debug(
331					"getResolvedMethod cache HIT: " + clas +" - " +method );
332		}
333		return method;
334	}
335
336	/**
337		Clear the caches in BshClassManager
338		@see public void #reset() for external usage
339	*/
340	protected void clearCaches() 
341	{
342    	absoluteNonClasses = new Hashtable();
343    	absoluteClassCache = new Hashtable();
344    	resolvedObjectMethods = new Hashtable();
345    	resolvedStaticMethods = new Hashtable();
346	}
347
348	/**
349		Set an external class loader.  BeanShell will use this at the same 
350		point it would otherwise use the plain Class.forName().
351		i.e. if no explicit classpath management is done from the script
352		(addClassPath(), setClassPath(), reloadClasses()) then BeanShell will
353		only use the supplied classloader.  If additional classpath management
354		is done then BeanShell will perform that in addition to the supplied
355		external classloader.
356		However BeanShell is not currently able to reload
357		classes supplied through the external classloader.
358	*/
359	public void setClassLoader( ClassLoader externalCL ) 
360	{
361		externalClassLoader = externalCL;
362		classLoaderChanged();
363	}
364
365	public void addClassPath( URL path )
366		throws IOException {
367	}
368
369	/**
370		Clear all loaders and start over.  No class loading.
371	*/
372	public void reset() { 
373		clearCaches();
374	}
375
376	/**
377		Set a new base classpath and create a new base classloader.
378		This means all types change. 
379	*/
380	public void setClassPath( URL [] cp ) 
381		throws UtilEvalError
382	{
383		throw cmUnavailable();
384	}
385
386	/**
387		Overlay the entire path with a new class loader.
388		Set the base path to the user path + base path.
389
390		No point in including the boot class path (can't reload thos).
391	*/
392	public void reloadAllClasses() throws UtilEvalError {
393		throw cmUnavailable();
394	}
395
396	/**
397		Reloading classes means creating a new classloader and using it
398		whenever we are asked for classes in the appropriate space.
399		For this we use a DiscreteFilesClassLoader
400	*/
401	public void reloadClasses( String [] classNames )
402		throws UtilEvalError 
403	{
404		throw cmUnavailable();
405	}
406
407	/**
408		Reload all classes in the specified package: e.g. "com.sun.tools"
409
410		The special package name "<unpackaged>" can be used to refer 
411		to unpackaged classes.
412	*/
413	public void reloadPackage( String pack ) 
414		throws UtilEvalError 
415	{
416		throw cmUnavailable();
417	}
418
419	/**
420		This has been removed from the interface to shield the core from the
421		rest of the classpath package. If you need the classpath you will have
422		to cast the classmanager to its impl.
423
424		public BshClassPath getClassPath() throws ClassPathException;
425	*/
426
427	/**
428		Support for "import *;"
429		Hide details in here as opposed to NameSpace.
430	*/
431	protected void doSuperImport() 
432		throws UtilEvalError 
433	{
434		throw cmUnavailable();
435	}
436
437	/**
438		A "super import" ("import *") operation has been performed.
439	*/
440	protected boolean hasSuperImport() 
441	{
442		return false;
443	}
444
445	/**
446		Return the name or null if none is found,
447		Throw an ClassPathException containing detail if name is ambigous.
448	*/
449	protected String getClassNameByUnqName( String name ) 
450		throws UtilEvalError 
451	{
452		throw cmUnavailable();
453	}
454
455	public void addListener( Listener l ) { }
456
457	public void removeListener( Listener l ) { }
458
459	public void dump( PrintWriter pw ) { 
460		pw.println("BshClassManager: no class manager."); 
461	}
462
463	/**
464		Flag the class name as being in the process of being defined.
465		The class manager will not attempt to load it.
466	*/
467	/*
468		Note: this implementation is temporary. We currently keep a flat
469		namespace of the base name of classes.  i.e. BeanShell cannot be in the
470		process of defining two classes in different packages with the same
471		base name.  To remove this limitation requires that we work through
472		namespace imports in an analogous (or using the same path) as regular
473		class import resolution.  This workaround should handle most cases 
474		so we'll try it for now.
475	*/
476	protected void definingClass( String className ) {
477		String baseName = Name.suffix(className,1);
478		int i = baseName.indexOf("$");
479		if ( i != -1 )
480			baseName = baseName.substring(i+1);
481		String cur = (String)definingClassesBaseNames.get( baseName );
482		if ( cur != null )
483			throw new InterpreterError("Defining class problem: "+className 
484				+": BeanShell cannot yet simultaneously define two or more "
485				+"dependant classes of the same name.  Attempt to define: "
486				+ className +" while defining: "+cur 
487			);
488		definingClasses.put( className, NOVALUE );
489		definingClassesBaseNames.put( baseName, className );
490	}
491
492	protected boolean isClassBeingDefined( String className ) {
493		return definingClasses.get( className ) != null;
494	}
495
496	/**
497		This method is a temporary workaround used with definingClass.
498		It is to be removed at some point.
499	*/
500	protected String getClassBeingDefined( String className ) {
501		String baseName = Name.suffix(className,1);
502		return (String)definingClassesBaseNames.get( baseName );
503	}
504
505	/**
506		Indicate that the specified class name has been defined and may be
507		loaded normally.
508	*/
509	protected void doneDefiningClass( String className ) {
510		String baseName = Name.suffix(className,1);
511		definingClasses.remove( className );
512		definingClassesBaseNames.remove( baseName );
513	}
514
515	/*
516		Issues to resolve here...
517		1) In which classloader should we define the class?
518		if there is a BshClassLoader should we define it there?
519		2) should we use reflection to set it in a non-bsh classloader
520		if there is one or should we always create a bsh classloader
521		(and expose its defineClass)?
522	*/
523	public Class defineClass( String name, byte [] code ) 
524	{
525		ClassLoader cl = this.getClass().getClassLoader();
526		Class clas;
527		try {
528			clas = (Class)Reflect.invokeObjectMethod( 
529				cl, "defineClass", 
530				new Object [] { 
531					name, code, 
532					new Primitive( (int)0 )/*offset*/, 
533					new Primitive( code.length )/*len*/ 
534				}, 
535				(Interpreter)null, (CallStack)null, (SimpleNode)null 
536			);
537		} catch ( Exception e ) {
538			e.printStackTrace();
539			throw new InterpreterError("Unable to define class: "+ e );
540		}
541		absoluteNonClasses.remove( name ); // may have been axed previously
542		return clas;
543	}
544
545	protected void classLoaderChanged() { }
546
547	/**
548		Annotate the NoClassDefFoundError with some info about the class
549		we were trying to load.
550	*/
551	protected static Error noClassDefFound( String className, Error e ) {
552		return new NoClassDefFoundError(
553			"A class required by class: "+className +" could not be loaded:\n"
554			+e.toString() );
555	}
556
557	protected static UtilEvalError cmUnavailable() {
558		return new Capabilities.Unavailable(
559			"ClassLoading features unavailable.");
560	}
561
562	public static interface Listener 
563	{
564		public void classLoaderChanged();
565	}
566
567	/**
568		SignatureKey serves as a hash of a method signature on a class 
569		for fast lookup of overloaded and general resolved Java methods. 
570		<p>
571	*/
572	/*
573		Note: is using SignatureKey in this way dangerous?  In the pathological
574		case a user could eat up memory caching every possible combination of
575		argument types to an untyped method.  Maybe we could be smarter about
576		it by ignoring the types of untyped parameter positions?  The method
577		resolver could return a set of "hints" for the signature key caching?
578
579		There is also the overhead of creating one of these for every method
580		dispatched.  What is the alternative?
581	*/
582	static class SignatureKey
583	{
584		Class clas;
585		Class [] types;
586		String methodName;
587		int hashCode = 0;
588
589		SignatureKey( Class clas, String methodName, Class [] types ) {
590			this.clas = clas;
591			this.methodName = methodName;
592			this.types = types;
593		}
594
595		public int hashCode() 
596		{ 
597			if ( hashCode == 0 ) 
598			{
599				hashCode = clas.hashCode() * methodName.hashCode();
600				if ( types == null ) // no args method
601					return hashCode; 
602				for( int i =0; i < types.length; i++ ) {
603					int hc = types[i] == null ? 21 : types[i].hashCode();
604					hashCode = hashCode*(i+1) + hc;
605				}
606			}
607			return hashCode;
608		}
609
610		public boolean equals( Object o ) { 
611			SignatureKey target = (SignatureKey)o;
612			if ( types == null )
613				return target.types == null;
614			if ( clas != target.clas )
615				return false;
616			if ( !methodName.equals( target.methodName ) )
617				return false;
618			if ( types.length != target.types.length )
619				return false;
620			for( int i =0; i< types.length; i++ )
621			{
622				if ( types[i]==null ) 
623				{
624					if ( !(target.types[i]==null) )
625						return false;
626				} else 
627					if ( !types[i].equals( target.types[i] ) )
628						return false;
629			}
630
631			return true;
632		}
633	}
634}