PageRenderTime 30ms CodeModel.GetById 0ms RepoModel.GetById 0ms 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
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
  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. package org.gjt.sp.jedit.bsh.classpath;
  34. import java.net.*;
  35. import java.util.*;
  36. import java.lang.ref.*;
  37. import java.io.IOException;
  38. import java.io.*;
  39. import org.gjt.sp.jedit.bsh.classpath.BshClassPath.ClassSource;
  40. import org.gjt.sp.jedit.bsh.classpath.BshClassPath.JarClassSource;
  41. import org.gjt.sp.jedit.bsh.classpath.BshClassPath.GeneratedClassSource;
  42. import org.gjt.sp.jedit.bsh.BshClassManager;
  43. import org.gjt.sp.jedit.bsh.ClassPathException;
  44. import org.gjt.sp.jedit.bsh.Interpreter; // for debug()
  45. import org.gjt.sp.jedit.bsh.UtilEvalError;
  46. /**
  47. <pre>
  48. Manage all classloading in BeanShell.
  49. Allows classpath extension and class file reloading.
  50. This class holds the implementation of the BshClassManager so that it
  51. can be separated from the core package.
  52. This class currently relies on 1.2 for BshClassLoader and weak references.
  53. Is there a workaround for weak refs? If so we could make this work
  54. with 1.1 by supplying our own classloader code...
  55. See "http://www.beanshell.org/manual/classloading.html" for details
  56. on the bsh classloader architecture.
  57. Bsh has a multi-tiered class loading architecture. No class loader is
  58. created unless/until a class is generated, the classpath is modified,
  59. or a class is reloaded.
  60. Note: we may need some synchronization in here
  61. Note on jdk1.2 dependency:
  62. We are forced to use weak references here to accomodate all of the
  63. fleeting namespace listeners. (NameSpaces must be informed if the class
  64. space changes so that they can un-cache names). I had the interesting
  65. thought that a way around this would be to implement BeanShell's own
  66. garbage collector... Then I came to my senses and said - screw it,
  67. class re-loading will require 1.2.
  68. ---------------------
  69. Classloading precedence:
  70. in-script evaluated class (scripted class)
  71. in-script added / modified classpath
  72. optionally, external classloader
  73. optionally, thread context classloader
  74. plain Class.forName()
  75. source class (.java file in classpath)
  76. </pre>
  77. */
  78. public class ClassManagerImpl extends BshClassManager
  79. {
  80. static final String BSH_PACKAGE = "org.gjt.sp.jedit.bsh";
  81. /**
  82. The classpath of the base loader. Initially and upon reset() this is
  83. an empty instance of BshClassPath. It grows as paths are added or is
  84. reset when the classpath is explicitly set. This could also be called
  85. the "extension" class path, but is not strictly confined to added path
  86. (could be set arbitrarily by setClassPath())
  87. */
  88. private BshClassPath baseClassPath;
  89. private boolean superImport;
  90. /**
  91. This is the full blown classpath including baseClassPath (extensions),
  92. user path, and java bootstrap path (rt.jar)
  93. This is lazily constructed and further (and more importantly) lazily
  94. intialized in components because mapping the full path could be
  95. expensive.
  96. The full class path is a composite of:
  97. baseClassPath (user extension) : userClassPath : bootClassPath
  98. in that order.
  99. */
  100. private BshClassPath fullClassPath;
  101. // ClassPath Change listeners
  102. private Vector listeners = new Vector();
  103. private ReferenceQueue refQueue = new ReferenceQueue();
  104. /**
  105. This handles extension / modification of the base classpath
  106. The loader to use where no mapping of reloaded classes exists.
  107. The baseLoader is initially null meaning no class loader is used.
  108. */
  109. private BshClassLoader baseLoader;
  110. /**
  111. Map by classname of loaders to use for reloaded classes
  112. */
  113. private Map loaderMap;
  114. /**
  115. Used by BshClassManager singleton constructor
  116. */
  117. public ClassManagerImpl() {
  118. reset();
  119. }
  120. /**
  121. @return the class or null
  122. */
  123. public Class classForName( String name )
  124. {
  125. // check positive cache
  126. Class c = (Class)absoluteClassCache.get(name);
  127. if (c != null )
  128. return c;
  129. // check negative cache
  130. if ( absoluteNonClasses.get(name)!=null ) {
  131. if ( Interpreter.DEBUG )
  132. Interpreter.debug("absoluteNonClass list hit: "+name);
  133. return null;
  134. }
  135. if ( Interpreter.DEBUG )
  136. Interpreter.debug("Trying to load class: "+name);
  137. // Check explicitly mapped (reloaded) class...
  138. ClassLoader overlayLoader = getLoaderForClass( name );
  139. if ( overlayLoader != null )
  140. {
  141. try {
  142. c = overlayLoader.loadClass(name);
  143. } catch ( Exception e ) {
  144. // used to squeltch this... changed for 1.3
  145. // see BshClassManager
  146. } catch ( NoClassDefFoundError e2 ) {
  147. throw noClassDefFound( name, e2 );
  148. }
  149. // Should be there since it was explicitly mapped
  150. // throw an error?
  151. }
  152. // insure that core classes are loaded from the same loader
  153. if ( c == null ) {
  154. if ( name.startsWith( BSH_PACKAGE ) )
  155. try {
  156. c = Interpreter.class.getClassLoader().loadClass( name );
  157. } catch ( ClassNotFoundException e ) {}
  158. }
  159. // Check classpath extension / reloaded classes
  160. if ( c == null ) {
  161. if ( baseLoader != null )
  162. try {
  163. c = baseLoader.loadClass( name );
  164. } catch ( ClassNotFoundException e ) {}
  165. }
  166. // Optionally try external classloader
  167. if ( c == null ) {
  168. if ( externalClassLoader != null )
  169. try {
  170. c = externalClassLoader.loadClass( name );
  171. } catch ( ClassNotFoundException e ) {}
  172. }
  173. // Optionally try context classloader
  174. // Note that this might be a security violation
  175. // is catching the SecurityException sufficient for all environments?
  176. // or do we need a way to turn this off completely?
  177. if ( c == null )
  178. {
  179. try {
  180. ClassLoader contextClassLoader =
  181. Thread.currentThread().getContextClassLoader();
  182. if ( contextClassLoader != null )
  183. c = Class.forName( name, true, contextClassLoader );
  184. } catch ( ClassNotFoundException e ) { // fall through
  185. } catch ( SecurityException e ) { } // fall through
  186. }
  187. // try plain class forName()
  188. if ( c == null )
  189. try {
  190. c = plainClassForName( name );
  191. } catch ( ClassNotFoundException e ) {}
  192. // Try scripted class
  193. if ( c == null )
  194. c = loadSourceClass( name );
  195. // Cache result (or null for not found)
  196. // Note: plainClassForName already caches, so it will be redundant
  197. // in that case, however this process only happens once
  198. cacheClassInfo( name, c );
  199. return c;
  200. }
  201. /**
  202. Get a resource URL using the BeanShell classpath
  203. @param path should be an absolute path
  204. */
  205. public URL getResource( String path )
  206. {
  207. URL url = null;
  208. if ( baseLoader != null )
  209. // classloader wants no leading slash
  210. url = baseLoader.getResource( path.substring(1) );
  211. if ( url == null )
  212. url = super.getResource( path );
  213. return url;
  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. InputStream in = null;
  222. if ( baseLoader != null )
  223. {
  224. // classloader wants no leading slash
  225. in = baseLoader.getResourceAsStream( path.substring(1) );
  226. }
  227. if ( in == null )
  228. {
  229. in = super.getResourceAsStream( path );
  230. }
  231. return in;
  232. }
  233. ClassLoader getLoaderForClass( String name ) {
  234. return (ClassLoader)loaderMap.get( name );
  235. }
  236. // Classpath mutators
  237. /**
  238. */
  239. public void addClassPath( URL path )
  240. throws IOException
  241. {
  242. if ( baseLoader == null )
  243. setClassPath( new URL [] { path } );
  244. else {
  245. // opportunity here for listener in classpath
  246. baseLoader.addURL( path );
  247. baseClassPath.add( path );
  248. classLoaderChanged();
  249. }
  250. }
  251. /**
  252. Clear all classloading behavior and class caches and reset to
  253. initial state.
  254. */
  255. public void reset()
  256. {
  257. baseClassPath = new BshClassPath("baseClassPath");
  258. baseLoader = null;
  259. loaderMap = new HashMap();
  260. classLoaderChanged(); // calls clearCaches() for us.
  261. }
  262. /**
  263. Set a new base classpath and create a new base classloader.
  264. This means all types change.
  265. */
  266. public void setClassPath( URL [] cp ) {
  267. baseClassPath.setPath( cp );
  268. initBaseLoader();
  269. loaderMap = new HashMap();
  270. classLoaderChanged();
  271. }
  272. /**
  273. Overlay the entire path with a new class loader.
  274. Set the base path to the user path + base path.
  275. No point in including the boot class path (can't reload thos).
  276. */
  277. public void reloadAllClasses() throws ClassPathException
  278. {
  279. BshClassPath bcp = new BshClassPath("temp");
  280. bcp.addComponent( baseClassPath );
  281. bcp.addComponent( BshClassPath.getUserClassPath() );
  282. setClassPath( bcp.getPathComponents() );
  283. }
  284. /**
  285. init the baseLoader from the baseClassPath
  286. */
  287. private void initBaseLoader() {
  288. baseLoader = new BshClassLoader( this, baseClassPath );
  289. }
  290. // class reloading
  291. /**
  292. Reloading classes means creating a new classloader and using it
  293. whenever we are asked for classes in the appropriate space.
  294. For this we use a DiscreteFilesClassLoader
  295. */
  296. public void reloadClasses( String [] classNames )
  297. throws ClassPathException
  298. {
  299. // validate that it is a class here?
  300. // init base class loader if there is none...
  301. if ( baseLoader == null )
  302. initBaseLoader();
  303. DiscreteFilesClassLoader.ClassSourceMap map =
  304. new DiscreteFilesClassLoader.ClassSourceMap();
  305. for (int i=0; i< classNames.length; i++) {
  306. String name = classNames[i];
  307. // look in baseLoader class path
  308. ClassSource classSource = baseClassPath.getClassSource( name );
  309. // look in user class path
  310. if ( classSource == null ) {
  311. BshClassPath.getUserClassPath().insureInitialized();
  312. classSource = BshClassPath.getUserClassPath().getClassSource(
  313. name );
  314. }
  315. // No point in checking boot class path, can't reload those.
  316. // else we could have used fullClassPath above.
  317. if ( classSource == null )
  318. throw new ClassPathException("Nothing known about class: "
  319. +name );
  320. // JarClassSource is not working... just need to implement it's
  321. // getCode() method or, if we decide to, allow the BshClassManager
  322. // to handle it... since it is a URLClassLoader and can handle JARs
  323. if ( classSource instanceof JarClassSource )
  324. throw new ClassPathException("Cannot reload class: "+name+
  325. " from source: "+ classSource );
  326. map.put( name, classSource );
  327. }
  328. // Create classloader for the set of classes
  329. ClassLoader cl = new DiscreteFilesClassLoader( this, map );
  330. // map those classes the loader in the overlay map
  331. Iterator it = map.keySet().iterator();
  332. while ( it.hasNext() )
  333. loaderMap.put( (String)it.next(), cl );
  334. classLoaderChanged();
  335. }
  336. /**
  337. Reload all classes in the specified package: e.g. "com.sun.tools"
  338. The special package name "<unpackaged>" can be used to refer
  339. to unpackaged classes.
  340. */
  341. public void reloadPackage( String pack )
  342. throws ClassPathException
  343. {
  344. Collection classes =
  345. baseClassPath.getClassesForPackage( pack );
  346. if ( classes == null )
  347. classes =
  348. BshClassPath.getUserClassPath().getClassesForPackage( pack );
  349. // no point in checking boot class path, can't reload those
  350. if ( classes == null )
  351. throw new ClassPathException("No classes found for package: "+pack);
  352. reloadClasses( (String[])classes.toArray( new String[0] ) );
  353. }
  354. /**
  355. Unimplemented
  356. For this we'd have to store a map by location as well as name...
  357. public void reloadPathComponent( URL pc ) throws ClassPathException {
  358. throw new ClassPathException("Unimplemented!");
  359. }
  360. */
  361. // end reloading
  362. /**
  363. Get the full blown classpath.
  364. */
  365. public BshClassPath getClassPath() throws ClassPathException
  366. {
  367. if ( fullClassPath != null )
  368. return fullClassPath;
  369. fullClassPath = new BshClassPath("BeanShell Full Class Path");
  370. fullClassPath.addComponent( BshClassPath.getUserClassPath() );
  371. try {
  372. fullClassPath.addComponent( BshClassPath.getBootClassPath() );
  373. } catch ( ClassPathException e ) {
  374. System.err.println("Warning: can't get boot class path");
  375. }
  376. fullClassPath.addComponent( baseClassPath );
  377. return fullClassPath;
  378. }
  379. /**
  380. Support for "import *;"
  381. Hide details in here as opposed to NameSpace.
  382. */
  383. public void doSuperImport()
  384. throws UtilEvalError
  385. {
  386. // Should we prevent it from happening twice?
  387. try {
  388. getClassPath().insureInitialized();
  389. // prime the lookup table
  390. getClassNameByUnqName( "" ) ;
  391. // always true now
  392. //getClassPath().setNameCompletionIncludeUnqNames(true);
  393. } catch ( ClassPathException e ) {
  394. throw new UtilEvalError("Error importing classpath "+ e );
  395. }
  396. superImport = true;
  397. }
  398. protected boolean hasSuperImport() { return superImport; }
  399. /**
  400. Return the name or null if none is found,
  401. Throw an ClassPathException containing detail if name is ambigous.
  402. */
  403. public String getClassNameByUnqName( String name )
  404. throws ClassPathException
  405. {
  406. return getClassPath().getClassNameByUnqName( name );
  407. }
  408. public void addListener( Listener l ) {
  409. listeners.addElement( new WeakReference( l, refQueue) );
  410. // clean up old listeners
  411. Reference deadref;
  412. while ( (deadref = refQueue.poll()) != null ) {
  413. boolean ok = listeners.removeElement( deadref );
  414. if ( ok ) {
  415. //System.err.println("cleaned up weak ref: "+deadref);
  416. } else {
  417. if ( Interpreter.DEBUG ) Interpreter.debug(
  418. "tried to remove non-existent weak ref: "+deadref);
  419. }
  420. }
  421. }
  422. public void removeListener( Listener l ) {
  423. throw new Error("unimplemented");
  424. }
  425. public ClassLoader getBaseLoader() {
  426. return baseLoader;
  427. }
  428. /**
  429. Get the BeanShell classloader.
  430. public ClassLoader getClassLoader() {
  431. }
  432. */
  433. /*
  434. Impl Notes:
  435. We add the bytecode source and the "reload" the class, which causes the
  436. BshClassLoader to be initialized and create a DiscreteFilesClassLoader
  437. for the bytecode.
  438. @exception ClassPathException can be thrown by reloadClasses
  439. */
  440. public Class defineClass( String name, byte [] code )
  441. {
  442. baseClassPath.setClassSource( name, new GeneratedClassSource( code ) );
  443. try {
  444. reloadClasses( new String [] { name } );
  445. } catch ( ClassPathException e ) {
  446. throw new org.gjt.sp.jedit.bsh.InterpreterError("defineClass: "+e);
  447. }
  448. return classForName( name );
  449. }
  450. /**
  451. Clear global class cache and notify namespaces to clear their
  452. class caches.
  453. The listener list is implemented with weak references so that we
  454. will not keep every namespace in existence forever.
  455. */
  456. protected void classLoaderChanged()
  457. {
  458. // clear the static caches in BshClassManager
  459. clearCaches();
  460. Vector toRemove = new Vector(); // safely remove
  461. for ( Enumeration e = listeners.elements(); e.hasMoreElements(); )
  462. {
  463. WeakReference wr = (WeakReference)e.nextElement();
  464. Listener l = (Listener)wr.get();
  465. if ( l == null ) // garbage collected
  466. toRemove.add( wr );
  467. else
  468. l.classLoaderChanged();
  469. }
  470. for( Enumeration e = toRemove.elements(); e.hasMoreElements(); )
  471. listeners.removeElement( e.nextElement() );
  472. }
  473. public void dump( PrintWriter i )
  474. {
  475. i.println("Bsh Class Manager Dump: ");
  476. i.println("----------------------- ");
  477. i.println("baseLoader = "+baseLoader);
  478. i.println("loaderMap= "+loaderMap);
  479. i.println("----------------------- ");
  480. i.println("baseClassPath = "+baseClassPath);
  481. }
  482. }