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