/jEdit/tags/jedit-4-1-pre5/bsh/BshClassManager.java
Java | 352 lines | 114 code | 37 blank | 201 comment | 21 complexity | 648736a259e909c16a538b0c4441c35c MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
/***************************************************************************** * * * This file is part of the BeanShell Java Scripting distribution. * * Documentation and updates may be found at http://www.beanshell.org/ * * * * Sun Public License Notice: * * * * The contents of this file are subject to the Sun Public License Version * * 1.0 (the "License"); you may not use this file except in compliance with * * the License. A copy of the License is available at http://www.sun.com * * * * The Original Code is BeanShell. The Initial Developer of the Original * * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright * * (C) 2000. All Rights Reserved. * * * * GNU Public License Notice: * * * * Alternatively, the contents of this file may be used under the terms of * * the GNU Lesser General Public License (the "LGPL"), in which case the * * provisions of LGPL are applicable instead of those above. If you wish to * * allow use of your version of this file only under the terms of the LGPL * * and not to allow others to use your version of this file under the SPL, * * indicate your decision by deleting the provisions above and replace * * them with the notice and other provisions required by the LGPL. If you * * do not delete the provisions above, a recipient may use your version of * * this file under either the SPL or the LGPL. * * * * Patrick Niemeyer (pat@pat.net) * * Author of Learning Java, O'Reilly & Associates * * http://www.pat.net/~pat/ * * * *****************************************************************************/ package bsh; import java.net.*; import java.util.*; import java.io.IOException; import java.io.*; /** BshClassManager manages all classloading in BeanShell. It also supports a dynamically loaded extension (bsh.classpath package) which allows classpath extension and class file reloading. Currently the extension relies on 1.2 for BshClassLoader and weak references. See http://www.beanshell.org/manual/classloading.html for details on the bsh classloader architecture. <p> Bsh has a multi-tiered class loading architecture. No class loader is used unless/until the classpath is modified or a class is reloaded. <p> Note: currently class loading features affect all instances of the Interpreter. However the basic design of this class will allow for per instance class management in the future if it is desired. */ /* Implementation notes: Note: we may need some synchronization in here Note on jdk1.2 dependency: <p> We are forced to use weak references here to accomodate all of the fleeting namespace listeners as they fall out of scope. (NameSpaces must be informed if the class space changes so that they can un-cache names). I had the thought that a way around this would be to implement BeanShell's own garbage collector... Then I came to my senses. <p> Perhaps a simpler idea would be to have entities that reference cached types always perform a light weight check with a counter / reference value and use that to detect changes in the namespace. This puts the burden on the consumer to check at appropriate times, but could eliminate the need for the listener system in many places and the necessity of weak references in this package. <p> */ public abstract class BshClassManager { /** Singleton class manager */ private static BshClassManager manager; private static boolean checkedForManager; // Use a hashtable as a Set... private static Object NOVALUE = new Object(); /** An external classloader supplied by the setClassLoader() command. */ private static ClassLoader externalClassLoader; /** Global cache for things we know are classes. Note: these should probably be re-implemented with Soft references. (as opposed to strong or Weak) */ protected transient static Hashtable absoluteClassCache = new Hashtable(); /** Global cache for things we know are *not* classes. Note: these should probably be re-implemented with Soft references. (as opposed to strong or Weak) */ protected transient static Hashtable absoluteNonClasses = new Hashtable(); // Begin static methods /** @return the BshClassManager singleton or null, indicating no class manager is available. */ // Note: this should probably throw Capabilities.Unavailable instead of // returning null public static BshClassManager getClassManager() { // Bootstrap the class manager if it exists // have we loaded it before? if ( !checkedForManager && manager == null ) // Do we have the necessary jdk1.2 packages? try { if ( plainClassForName("java.lang.ref.WeakReference") != null && plainClassForName("java.util.HashMap") != null ) { // try to load the implementation Class bcm = plainClassForName( "bsh.classpath.ClassManagerImpl"); manager = (BshClassManager)bcm.newInstance(); } } catch ( ClassNotFoundException e ) { //System.err.println("No class manager available."); } catch ( Exception e ) { System.err.println("Error loading classmanager: "+e); } checkedForManager = true; return manager; } public static boolean classExists( String name ) { return ( classForName( name ) != null ); } /** Load the specified class by name, taking into account added classpath and reloaded classes, etc. @return the class or null */ public static Class classForName( String name ) { BshClassManager manager = getClassManager(); // prime the singleton if ( manager != null ) return manager.getClassForName( name ); else try { return plainClassForName( name ); } catch ( ClassNotFoundException e ) { return null; } } /** Perform a plain Class.forName() or call the externally provided class loader. If a BshClassManager implementation is loaded the call will be delegated to it, to allow for additional hooks. This simply wraps that bottom level class lookup call and provides a central point for monitoring and handling certain Java version dependent bugs, etc. @see #getPlainClassForName( String ) @return the class */ public static Class plainClassForName( String name ) throws ClassNotFoundException { try { Class c; if ( externalClassLoader != null ) c = externalClassLoader.loadClass( name ); else { // If BCM exists, delegate to it. BshClassManager bcm = manager; // Don't create if not there yet. if ( bcm != null ) c = bcm.getPlainClassForName( name ); else c = Class.forName( name ); } cacheClassInfo( name, c ); return c; /* This is weird... jdk under Win is throwing these to warn about lower case / upper case possible mismatch. e.g. bsh.console bsh.Console */ } catch ( NoClassDefFoundError e ) { cacheClassInfo( name, null ); // non-class throw new ClassNotFoundException( e.toString() ); } } /** Cache info about whether name is a class or not. @param value if value is non-null, cache the class if value is null, set the flag that it is *not* a class to speed later resolution */ public static void cacheClassInfo( String name, Class value ) { if ( value != null ) absoluteClassCache.put( name, value ); else absoluteNonClasses.put( name, NOVALUE ); } /** Clear the static caches in BshClassManager */ protected void clearCaches() { absoluteNonClasses = new Hashtable(); absoluteClassCache = new Hashtable(); } /** Add a BshClassManager.Listener to the class manager. The listener is informed upon changes to the classpath. This is a static convenience form of BshClassManager addListener(). If there is no class manager the listener will be ignored. */ public static void addCMListener( Listener l ) { getClassManager(); // prime it if ( manager != null ) manager.addListener( l ); } /** Set an external class loader. BeanShell will use this at the same point it would otherwise use the plain Class.forName(). i.e. if no explicit classpath management is done from the script (addClassPath(), setClassPath(), reloadClasses()) then BeanShell will only use the supplied classloader. If additional classpath management is done then BeanShell will perform that in addition to the supplied external classloader. However BeanShell is not currently able to reload classes supplied through the external classloader. */ public static void setClassLoader( ClassLoader externalCL ) { externalClassLoader = externalCL; BshClassManager bcm = getClassManager(); if ( bcm != null ) bcm.classLoaderChanged(); } // end static methods public static interface Listener { public void classLoaderChanged(); } // Begin interface methods /** @see #classForName( String ) */ public abstract Class getClassForName( String name ); /** Delegate for plainClassForName. @see #plainClassForName( String ) */ public abstract Class getPlainClassForName( String name ) throws ClassNotFoundException ; public abstract ClassLoader getBaseLoader(); public abstract ClassLoader getLoaderForClass( String name ); public abstract void addClassPath( URL path ) throws IOException; /** Clear all loaders and start over. No class loading. */ public abstract void reset(); /** Set a new base classpath and create a new base classloader. This means all types change. */ public abstract void setClassPath( URL [] cp ); /** Overlay the entire path with a new class loader. Set the base path to the user path + base path. No point in including the boot class path (can't reload thos). */ public abstract void reloadAllClasses() throws ClassPathException; /** Reloading classes means creating a new classloader and using it whenever we are asked for classes in the appropriate space. For this we use a DiscreteFilesClassLoader */ public abstract void reloadClasses( String [] classNames ) throws ClassPathException; /** Reload all classes in the specified package: e.g. "com.sun.tools" The special package name "<unpackaged>" can be used to refer to unpackaged classes. */ public abstract void reloadPackage( String pack ) throws ClassPathException ; /** This has been removed from the interface to shield the core from the rest of the classpath package. If you need the classpath you will have to cast the classmanager to its impl. public abstract BshClassPath getClassPath() throws ClassPathException; */ /** Support for "import *;" Hide details in here as opposed to NameSpace. Note: this used to be package private... */ public abstract void doSuperImport() throws EvalError; /** Return the name or null if none is found, Throw an ClassPathException containing detail if name is ambigous. Note: this used to be package private... */ public abstract String getClassNameByUnqName( String name ) throws ClassPathException; public abstract void addListener( Listener l ); public abstract void removeListener( Listener l ); public abstract void dump( PrintWriter pw ); protected abstract void classLoaderChanged(); }