001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package bsh;
033:
034: import java.net.*;
035: import java.util.*;
036: import java.io.IOException;
037: import java.io.*;
038: import java.lang.reflect.Method;
039: import java.lang.reflect.Modifier;
040:
041: /**
042: BshClassManager manages all classloading in BeanShell.
043: It also supports a dynamically loaded extension (bsh.classpath package)
044: which allows classpath extension and class file reloading.
045:
046: Currently the extension relies on 1.2 for BshClassLoader and weak
047: references.
048:
049: See http://www.beanshell.org/manual/classloading.html for details
050: on the bsh classloader architecture.
051: <p>
052:
053: Bsh has a multi-tiered class loading architecture. No class loader is
054: used unless/until the classpath is modified or a class is reloaded.
055: <p>
056: */
057: /*
058: Implementation notes:
059:
060: Note: we may need some synchronization in here
061:
062: Note on version dependency: This base class is JDK 1.1 compatible,
063: however we are forced to use weak references in the full featured
064: implementation (the optional bsh.classpath package) to accomodate all of
065: the fleeting namespace listeners as they fall out of scope. (NameSpaces
066: must be informed if the class space changes so that they can un-cache
067: names).
068: <p>
069:
070: Perhaps a simpler idea would be to have entities that reference cached
071: types always perform a light weight check with a counter / reference
072: value and use that to detect changes in the namespace. This puts the
073: burden on the consumer to check at appropriate times, but could eliminate
074: the need for the listener system in many places and the necessity of weak
075: references in this package.
076: <p>
077: */
078: public class BshClassManager {
079: /** Identifier for no value item. Use a hashtable as a Set. */
080: private static Object NOVALUE = new Object();
081: /**
082: The interpreter which created the class manager
083: This is used to load scripted classes from source files.
084: */
085: private Interpreter declaringInterpreter;
086:
087: /**
088: An external classloader supplied by the setClassLoader() command.
089: */
090: protected ClassLoader externalClassLoader;
091:
092: /**
093: Global cache for things we know are classes.
094: Note: these should probably be re-implemented with Soft references.
095: (as opposed to strong or Weak)
096: */
097: protected transient Hashtable absoluteClassCache = new Hashtable();
098: /**
099: Global cache for things we know are *not* classes.
100: Note: these should probably be re-implemented with Soft references.
101: (as opposed to strong or Weak)
102: */
103: protected transient Hashtable absoluteNonClasses = new Hashtable();
104:
105: /**
106: Caches for resolved object and static methods.
107: We keep these maps separate to support fast lookup in the general case
108: where the method may be either.
109: */
110: protected transient Hashtable resolvedObjectMethods = new Hashtable();
111: protected transient Hashtable resolvedStaticMethods = new Hashtable();
112:
113: protected transient Hashtable definingClasses = new Hashtable();
114: protected transient Hashtable definingClassesBaseNames = new Hashtable();
115:
116: /** @see #associateClass( Class ) */
117: protected transient Hashtable associatedClasses = 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(
127: Interpreter interpreter) {
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
134: .classExists("bsh.classpath.ClassManagerImpl"))
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
139: .forName("bsh.classpath.ClassManagerImpl");
140: manager = (BshClassManager) clas.newInstance();
141: } catch (Exception e) {
142: throw new InterpreterError(
143: "Error loading classmanager: " + e);
144: }
145: else
146: manager = new BshClassManager();
147:
148: if (interpreter == null)
149: interpreter = new Interpreter();
150: manager.declaringInterpreter = interpreter;
151: return manager;
152: }
153:
154: public boolean classExists(String name) {
155: return (classForName(name) != null);
156: }
157:
158: /**
159: Load the specified class by name, taking into account added classpath
160: and reloaded classes, etc.
161: Note: Again, this is just a trivial implementation.
162: See bsh.classpath.ClassManagerImpl for the fully functional class
163: management package.
164: @return the class or null
165: */
166: public Class classForName(String name) {
167: if (isClassBeingDefined(name))
168: throw new InterpreterError(
169: "Attempting to load class in the process of being defined: "
170: + name);
171:
172: Class clas = null;
173: try {
174: clas = plainClassForName(name);
175: } catch (ClassNotFoundException e) { /*ignore*/
176: }
177:
178: // try scripted class
179: if (clas == null)
180: clas = loadSourceClass(name);
181:
182: return clas;
183: }
184:
185: // Move me to classpath/ClassManagerImpl???
186: protected Class loadSourceClass(String name) {
187: String fileName = "/" + name.replace('.', '/') + ".java";
188: InputStream in = getResourceAsStream(fileName);
189: if (in == null)
190: return null;
191:
192: try {
193: System.out.println("Loading class from source file: "
194: + fileName);
195: declaringInterpreter.eval(new InputStreamReader(in));
196: } catch (EvalError e) {
197: // ignore
198: System.err.println(e);
199: }
200: try {
201: return plainClassForName(name);
202: } catch (ClassNotFoundException e) {
203: System.err.println("Class not found in source file: "
204: + name);
205: return null;
206: }
207: }
208:
209: /**
210: Perform a plain Class.forName() or call the externally provided
211: classloader.
212: If a BshClassManager implementation is loaded the call will be
213: delegated to it, to allow for additional hooks.
214: <p/>
215:
216: This simply wraps that bottom level class lookup call and provides a
217: central point for monitoring and handling certain Java version
218: dependent bugs, etc.
219:
220: @see #classForName( String )
221: @return the class
222: */
223: public Class plainClassForName(String name)
224: throws ClassNotFoundException {
225: Class c = null;
226:
227: try {
228: if (externalClassLoader != null)
229: c = externalClassLoader.loadClass(name);
230: else
231: c = Class.forName(name);
232:
233: cacheClassInfo(name, c);
234:
235: /*
236: Original note: Jdk under Win is throwing these to
237: warn about lower case / upper case possible mismatch.
238: e.g. bsh.console bsh.Console
239:
240: Update: Prior to 1.3 we were squeltching NoClassDefFoundErrors
241: which was very annoying. I cannot reproduce the original problem
242: and this was never a valid solution. If there are legacy VMs that
243: have problems we can include a more specific test for them here.
244: */
245: } catch (NoClassDefFoundError e) {
246: throw noClassDefFound(name, e);
247: }
248:
249: return c;
250: }
251:
252: /**
253: Get a resource URL using the BeanShell classpath
254: @param path should be an absolute path
255: */
256: public URL getResource(String path) {
257: URL url = null;
258: if (externalClassLoader != null) {
259: // classloader wants no leading slash
260: url = externalClassLoader.getResource(path.substring(1));
261: }
262: if (url == null)
263: url = Interpreter.class.getResource(path);
264:
265: return url;
266: }
267:
268: /**
269: Get a resource stream using the BeanShell classpath
270: @param path should be an absolute path
271: */
272: public InputStream getResourceAsStream(String path) {
273: InputStream in = null;
274: if (externalClassLoader != null) {
275: // classloader wants no leading slash
276: in = externalClassLoader.getResourceAsStream(path
277: .substring(1));
278: }
279: if (in == null)
280: in = Interpreter.class.getResourceAsStream(path);
281:
282: return in;
283: }
284:
285: /**
286: Cache info about whether name is a class or not.
287: @param value
288: if value is non-null, cache the class
289: if value is null, set the flag that it is *not* a class to
290: speed later resolution
291: */
292: public void cacheClassInfo(String name, Class value) {
293: if (value != null)
294: absoluteClassCache.put(name, value);
295: else
296: absoluteNonClasses.put(name, NOVALUE);
297: }
298:
299: /**
300: * Associate a persistent generated class implementation with this
301: * interpreter. An associated class will be used in lieu of generating
302: * bytecode when a scripted class of the same name is encountered.
303: * When such a class is defined in the script it will cause the associated
304: * existing class implementation to be initialized (with the static
305: * initializer field). This is utilized by the persistent class generator
306: * to allow a generated class to bootstrap an interpreter and rendesvous
307: * with its implementation script.
308: *
309: * Class associations currently last for the life of the class manager.
310: */
311: public void associateClass(Class clas) {
312: // TODO should check to make sure it's a generated class here
313: // just need to add a method to classgenerator API to test it
314: associatedClasses.put(clas.getName(), clas);
315: }
316:
317: public Class getAssociatedClass(String name) {
318: return (Class) associatedClasses.get(name);
319: }
320:
321: /**
322: Cache a resolved (possibly overloaded) method based on the
323: argument types used to invoke it, subject to classloader change.
324: Static and Object methods are cached separately to support fast lookup
325: in the general case where either will do.
326: */
327: public void cacheResolvedMethod(Class clas, Class[] types,
328: Method method) {
329: if (Interpreter.DEBUG)
330: Interpreter.debug("cacheResolvedMethod putting: " + clas
331: + " " + method);
332:
333: SignatureKey sk = new SignatureKey(clas, method.getName(),
334: types);
335: if (Modifier.isStatic(method.getModifiers()))
336: resolvedStaticMethods.put(sk, method);
337: else
338: resolvedObjectMethods.put(sk, method);
339: }
340:
341: /**
342: Return a previously cached resolved method.
343: @param onlyStatic specifies that only a static method may be returned.
344: @return the Method or null
345: */
346: protected Method getResolvedMethod(Class clas, String methodName,
347: Class[] types, boolean onlyStatic) {
348: SignatureKey sk = new SignatureKey(clas, methodName, types);
349:
350: // Try static and then object, if allowed
351: // Note that the Java compiler should not allow both.
352: Method method = (Method) resolvedStaticMethods.get(sk);
353: if (method == null && !onlyStatic)
354: method = (Method) resolvedObjectMethods.get(sk);
355:
356: if (Interpreter.DEBUG) {
357: if (method == null)
358: Interpreter.debug("getResolvedMethod cache MISS: "
359: + clas + " - " + methodName);
360: else
361: Interpreter.debug("getResolvedMethod cache HIT: "
362: + clas + " - " + method);
363: }
364: return method;
365: }
366:
367: /**
368: Clear the caches in BshClassManager
369: @see public void #reset() for external usage
370: */
371: protected void clearCaches() {
372: absoluteNonClasses = new Hashtable();
373: absoluteClassCache = new Hashtable();
374: resolvedObjectMethods = new Hashtable();
375: resolvedStaticMethods = new Hashtable();
376: }
377:
378: /**
379: Set an external class loader. BeanShell will use this at the same
380: point it would otherwise use the plain Class.forName().
381: i.e. if no explicit classpath management is done from the script
382: (addClassPath(), setClassPath(), reloadClasses()) then BeanShell will
383: only use the supplied classloader. If additional classpath management
384: is done then BeanShell will perform that in addition to the supplied
385: external classloader.
386: However BeanShell is not currently able to reload
387: classes supplied through the external classloader.
388: */
389: public void setClassLoader(ClassLoader externalCL) {
390: externalClassLoader = externalCL;
391: classLoaderChanged();
392: }
393:
394: public void addClassPath(URL path) throws IOException {
395: }
396:
397: /**
398: Clear all loaders and start over. No class loading.
399: */
400: public void reset() {
401: clearCaches();
402: }
403:
404: /**
405: Set a new base classpath and create a new base classloader.
406: This means all types change.
407: */
408: public void setClassPath(URL[] cp) throws UtilEvalError {
409: throw cmUnavailable();
410: }
411:
412: /**
413: Overlay the entire path with a new class loader.
414: Set the base path to the user path + base path.
415:
416: No point in including the boot class path (can't reload thos).
417: */
418: public void reloadAllClasses() throws UtilEvalError {
419: throw cmUnavailable();
420: }
421:
422: /**
423: Reloading classes means creating a new classloader and using it
424: whenever we are asked for classes in the appropriate space.
425: For this we use a DiscreteFilesClassLoader
426: */
427: public void reloadClasses(String[] classNames) throws UtilEvalError {
428: throw cmUnavailable();
429: }
430:
431: /**
432: Reload all classes in the specified package: e.g. "com.sun.tools"
433:
434: The special package name "<unpackaged>" can be used to refer
435: to unpackaged classes.
436: */
437: public void reloadPackage(String pack) throws UtilEvalError {
438: throw cmUnavailable();
439: }
440:
441: /**
442: This has been removed from the interface to shield the core from the
443: rest of the classpath package. If you need the classpath you will have
444: to cast the classmanager to its impl.
445:
446: public BshClassPath getClassPath() throws ClassPathException;
447: */
448:
449: /**
450: Support for "import *;"
451: Hide details in here as opposed to NameSpace.
452: */
453: protected void doSuperImport() throws UtilEvalError {
454: throw cmUnavailable();
455: }
456:
457: /**
458: A "super import" ("import *") operation has been performed.
459: */
460: protected boolean hasSuperImport() {
461: return false;
462: }
463:
464: /**
465: Return the name or null if none is found,
466: Throw an ClassPathException containing detail if name is ambigous.
467: */
468: protected String getClassNameByUnqName(String name)
469: throws UtilEvalError {
470: throw cmUnavailable();
471: }
472:
473: public void addListener(Listener l) {
474: }
475:
476: public void removeListener(Listener l) {
477: }
478:
479: public void dump(PrintWriter pw) {
480: pw.println("BshClassManager: no class manager.");
481: }
482:
483: /**
484: Flag the class name as being in the process of being defined.
485: The class manager will not attempt to load it.
486: */
487: /*
488: Note: this implementation is temporary. We currently keep a flat
489: namespace of the base name of classes. i.e. BeanShell cannot be in the
490: process of defining two classes in different packages with the same
491: base name. To remove this limitation requires that we work through
492: namespace imports in an analogous (or using the same path) as regular
493: class import resolution. This workaround should handle most cases
494: so we'll try it for now.
495: */
496: protected void definingClass(String className) {
497: String baseName = Name.suffix(className, 1);
498: int i = baseName.indexOf("$");
499: if (i != -1)
500: baseName = baseName.substring(i + 1);
501: String cur = (String) definingClassesBaseNames.get(baseName);
502: if (cur != null)
503: throw new InterpreterError(
504: "Defining class problem: "
505: + className
506: + ": BeanShell cannot yet simultaneously define two or more "
507: + "dependant classes of the same name. Attempt to define: "
508: + className + " while defining: " + cur);
509: definingClasses.put(className, NOVALUE);
510: definingClassesBaseNames.put(baseName, className);
511: }
512:
513: protected boolean isClassBeingDefined(String className) {
514: return definingClasses.get(className) != null;
515: }
516:
517: /**
518: This method is a temporary workaround used with definingClass.
519: It is to be removed at some point.
520: */
521: protected String getClassBeingDefined(String className) {
522: String baseName = Name.suffix(className, 1);
523: return (String) definingClassesBaseNames.get(baseName);
524: }
525:
526: /**
527: Indicate that the specified class name has been defined and may be
528: loaded normally.
529: */
530: protected void doneDefiningClass(String className) {
531: String baseName = Name.suffix(className, 1);
532: definingClasses.remove(className);
533: definingClassesBaseNames.remove(baseName);
534: }
535:
536: /*
537: The real implementation in the classpath.ClassManagerImpl handles
538: reloading of the generated classes.
539: */
540: public Class defineClass(String name, byte[] code) {
541: throw new InterpreterError("Can't create class (" + name
542: + ") without class manager package.");
543: /*
544: Old implementation injected classes into the parent classloader.
545: This was incorrect behavior for several reasons. The biggest problem
546: is that classes could therefore only be defined once across all
547: executions of the script...
548:
549: ClassLoader cl = this.getClass().getClassLoader();
550: Class clas;
551: try {
552: clas = (Class)Reflect.invokeObjectMethod(
553: cl, "defineClass",
554: new Object [] {
555: name, code,
556: new Primitive( (int)0 )/offset/,
557: new Primitive( code.length )/len/
558: },
559: (Interpreter)null, (CallStack)null, (SimpleNode)null
560: );
561: } catch ( Exception e ) {
562: e.printStackTrace();
563: throw new InterpreterError("Unable to define class: "+ e );
564: }
565: absoluteNonClasses.remove( name ); // may have been axed previously
566: return clas;
567: */
568: }
569:
570: protected void classLoaderChanged() {
571: }
572:
573: /**
574: Annotate the NoClassDefFoundError with some info about the class
575: we were trying to load.
576: */
577: protected static Error noClassDefFound(String className, Error e) {
578: return new NoClassDefFoundError("A class required by class: "
579: + className + " could not be loaded:\n" + e.toString());
580: }
581:
582: protected static UtilEvalError cmUnavailable() {
583: return new Capabilities.Unavailable(
584: "ClassLoading features unavailable.");
585: }
586:
587: public static interface Listener {
588: public void classLoaderChanged();
589: }
590:
591: /**
592: SignatureKey serves as a hash of a method signature on a class
593: for fast lookup of overloaded and general resolved Java methods.
594: <p>
595: */
596: /*
597: Note: is using SignatureKey in this way dangerous? In the pathological
598: case a user could eat up memory caching every possible combination of
599: argument types to an untyped method. Maybe we could be smarter about
600: it by ignoring the types of untyped parameter positions? The method
601: resolver could return a set of "hints" for the signature key caching?
602:
603: There is also the overhead of creating one of these for every method
604: dispatched. What is the alternative?
605: */
606: static class SignatureKey {
607: Class clas;
608: Class[] types;
609: String methodName;
610: int hashCode = 0;
611:
612: SignatureKey(Class clas, String methodName, Class[] types) {
613: this .clas = clas;
614: this .methodName = methodName;
615: this .types = types;
616: }
617:
618: public int hashCode() {
619: if (hashCode == 0) {
620: hashCode = clas.hashCode() * methodName.hashCode();
621: if (types == null) // no args method
622: return hashCode;
623: for (int i = 0; i < types.length; i++) {
624: int hc = types[i] == null ? 21 : types[i]
625: .hashCode();
626: hashCode = hashCode * (i + 1) + hc;
627: }
628: }
629: return hashCode;
630: }
631:
632: public boolean equals(Object o) {
633: SignatureKey target = (SignatureKey) o;
634: if (types == null)
635: return target.types == null;
636: if (clas != target.clas)
637: return false;
638: if (!methodName.equals(target.methodName))
639: return false;
640: if (types.length != target.types.length)
641: return false;
642: for (int i = 0; i < types.length; i++) {
643: if (types[i] == null) {
644: if (!(target.types[i] == null))
645: return false;
646: } else if (!types[i].equals(target.types[i]))
647: return false;
648: }
649:
650: return true;
651: }
652: }
653: }
|